Skip to content

Checking access...

FAS Suite Creation Guide

This guide walks through creating a new suite from the suite-template.

For detailed template usage instructions, see also:

Step 1: Create Repository from Template

On GitHub, navigate to suite-template and click "Use this template" -> "Create a new repository".

  • Owner: Hello-World-Co-Op
  • Name: <suite-name> (e.g., marketing-suite, dao-suite)
  • Visibility: Private (org repos)

Then clone locally:

bash
git clone https://github.com/Hello-World-Co-Op/<suite-name>.git
cd <suite-name>

Step 2: Update Package Identity

Edit package.json:

json
{
  "name": "@hello-world-co-op/<suite-name>",
  "version": "0.1.0",
  "description": "Your suite description"
}

Step 3: Update dfx.json

Rename the canister to match your suite:

json
{
  "canisters": {
    "<suite_name>_assets": {
      "type": "assets",
      "source": ["dist"],
      "build": ["npm run build"]
    }
  }
}

Step 4: Create IC Asset Canister

bash
# Install dfx if needed
sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"

# Create canister on mainnet (requires cycles)
dfx canister create <suite_name>_assets --network ic

# Record the canister ID in canister_ids.json

Step 5: Configure Environment

bash
cp .env.example .env.local

Edit .env.local with your canister IDs and service URLs. At minimum:

bash
VITE_IC_HOST=https://ic0.app
VITE_AUTH_SERVICE_CANISTER_ID=<auth-service-canister-id>
VITE_USER_SERVICE_CANISTER_ID=<user-service-canister-id>

Create .env.staging (Required for Auth-Integrated Suites)

If your suite integrates with oracle-bridge for cookie SSO, you must create .env.staging alongside .env.local. Vite bakes VITE_* variables at build time — if .env.staging is missing, VITE_ORACLE_BRIDGE_URL will be undefined and getBaseUrl() will fall back to same-origin, causing session checks to hit the IC canister instead of oracle-bridge.

bash
# .env.staging
VITE_IC_HOST=https://ic0.app
VITE_ORACLE_BRIDGE_URL=https://staging-oracle.helloworlddao.com
VITE_THINK_TANK_URL=https://staging-think-tank.helloworlddao.com
VITE_AUTH_SERVICE_CANISTER_ID=gexqh-jqaaa-aaaae-acsxq-cai
VITE_USER_SERVICE_CANISTER_ID=j4rvr-3aaaa-aaaao-qkvfq-cai

Lesson from FAS-8.1: dao-suite and dao-admin-suite shipped without .env.staging. SSO appeared to work on first login (localStorage), but cross-suite cookie flow failed silently. Always create .env.staging when adding oracle-bridge integration.

Step 6: Set Up CI/CD

The template includes .github/workflows/ci.yml and a deploy workflow. Update:

  1. Workflow suite name if referenced in workflow
  2. GitHub Actions secrets -- ensure org-level secrets are available:
    • DFX_IDENTITY_PEM -- dfx identity for deployment
  3. GitHub Packages access -- grant org-level Read access to published packages in GitHub UI (Settings -> Packages -> Manage Access for each package)

IC Asset Canister Deployment Pattern

When deploying Vite-based suites to IC asset canisters, dfx auto-detects vite.config.ts and triggers a rebuild. This overwrites staging/production env vars with local values. To prevent this:

bash
# Before dfx deploy: hide vite config
mv vite.config.ts vite.config.ts.bak 2>/dev/null || true
mv package.json package.json.bak
echo '{"name":"deploy-temp","private":true,"scripts":{"build":"echo No build needed"}}' > package.json

# Deploy pre-built dist
dfx deploy <suite-name> --network ic --yes

# Restore files
mv vite.config.ts.bak vite.config.ts 2>/dev/null || true
mv package.json.bak package.json

This pattern is already implemented in:

  • All 4 custom deploy-staging.yml workflows (think-tank, dao, dao-admin, governance)
  • The shared suite-deploy.yml reusable workflow

The CI/CD workflows build with the correct .env.staging or .env.production file, then hide vite.config.ts and package.json before running dfx deploy so that dfx treats the dist/ directory as pre-built static assets. The if: always() restore step ensures files are restored even if deployment fails.

Why not just remove the build command from dfx.json? The "build" key in dfx.json is required for type: "assets" canisters. If omitted, dfx falls back to auto-detection, which finds vite.config.ts and runs npm run build anyway. The hide-and-restore pattern is the only reliable way to skip the rebuild.

See also: Troubleshooting -- dfx deploy fails with "vite auto-detected"

Step 7: Configure Custom Domain (Optional)

If your suite needs a custom subdomain (e.g., marketing.helloworlddao.com):

  1. Create public/.well-known/ic-domains with the domain name
  2. Configure DNS CNAME to point to <canister-id>.icp0.io
  3. Register the domain with IC boundary nodes:
    bash
    # Register domain
    curl -sL -X POST "https://icp0.io/custom-domains/v1/<domain>" | jq .
    # Check registration status (wait for "registered")
    curl -sL "https://icp0.io/custom-domains/v1/<domain>" | jq .data.registration_status
  4. Verify with dig +short <domain> and browser test

Step 8: Install and Verify

bash
npm install
npm run dev          # Dev server on localhost:5174
npm test             # Unit tests
npm run build        # Production build
npx tsc --noEmit     # Type check
npm run lint         # ESLint

Suite-Specific Patterns

When to Use the Bridge Pattern

Use AuthProviderBridge when your suite needs:

  • User login/logout functionality
  • Protected routes via ProtectedRoute
  • Session management with the auth-service canister
  • Access to @hello-world-co-op/auth hooks (useAuth())

Examples: think-tank-suite, dao-suite, dao-admin-suite

When to Use Direct Nanostores

Use direct nanostores when your suite:

  • Only needs to read auth state (cookie-based)
  • Does not need the full @hello-world-co-op/auth interface
  • Manages auth state through its own patterns

Example: governance-suite

When to Go Standalone

Go standalone (no shared auth/api packages) when your suite:

  • Is public-facing and does not require authentication
  • Has its own rendering engine (e.g., Phaser.js for games)
  • Benefits from a minimal dependency footprint

Examples: marketing-suite (SEO-focused, no auth), otter-camp-suite (Phaser.js game, no IC canister calls)

If your suite needs cross-suite SSO:

  1. Add VITE_ORACLE_BRIDGE_URL to .env.local:
    bash
    VITE_ORACLE_BRIDGE_URL=http://localhost:3000       # local
    VITE_ORACLE_BRIDGE_URL=https://staging-oracle.helloworlddao.com  # staging
  2. Create an authCookieClient service that calls oracle-bridge endpoints
  3. Wrap your app with AuthProviderBridge
  4. Implement ProtectedRoute for authenticated pages
  5. Add LoginRedirect page that redirects to think-tank login with returnUrl

See Cross-Suite Auth Debugging for troubleshooting SSO issues.

Real-World Examples

The following suites were built from the suite-template and demonstrate specific patterns:

marketing-suite (FAS-4) -- SEO Pre-Rendering

  • Pattern: Standalone (no auth dependency)
  • Key decisions: Custom pre-rendering via ReactDOMServer.renderToString() instead of framework-level SSG, sitemap generation via build script, react-helmet-async for dynamic meta tags
  • Lesson learned: SEO pre-rendering adds ~30s to build time (acceptable per NFR1 < 3 min total build)
  • Gotcha: Routes must be added to both entry-prerender.tsx and generate-sitemap.ts

otter-camp-suite (FAS-5) -- Game Bundle Optimization

  • Pattern: Fully standalone (no shared packages)
  • Key decisions: Phaser.js extracted to standalone lazy-loaded chunk (340KB), game code on separate chunk (132KB), initial bundle only 58KB
  • Lesson learned: Game suites do not need auth packages -- keep them standalone for fast iteration
  • Gotcha: Phaser.AUTO config must be set correctly for canvas rendering; game reads user_data from localStorage passively

dao-suite (FAS-6) -- Cross-Suite SSO

  • Pattern: Auth Bridge with cookie SSO
  • Key decisions: authCookieClient bridges cookie session to localStorage for legacy components, ProtectedRoute with useCallback for stable refs
  • Lesson learned: Infinite redirect loop caused by unstable useCallback refs in ProtectedRoute -- always memoize auth check functions
  • Gotcha: Cookie domain must be .helloworlddao.com (leading dot critical for cross-subdomain sharing)

dao-admin-suite (FAS-7) -- Admin RBAC

  • Pattern: Auth Bridge with AdminGuard
  • Key decisions: Admin-specific validateReturnUrl to prevent open redirect attacks, desktop-only design (no mobile)
  • Lesson learned: AdminGuard RBAC enforcement deferred to bl-007 -- use placeholder guard during initial build
  • Gotcha: returnUrl must be validated against allowed admin domains to prevent redirect attacks

Post-Creation Checklist

Basic Setup

  • [ ] package.json name updated to @hello-world-co-op/<suite-name>
  • [ ] dfx.json canister name updated
  • [ ] IC asset canister created
  • [ ] .env.local configured with canister IDs
  • [ ] GitHub Actions secrets available
  • [ ] Cross-repo package access granted (GitHub Packages)
  • [ ] README.md updated with suite-specific content

CI/CD Verification

  • [ ] ci.yml runs successfully (lint, typecheck, test, build)
  • [ ] deploy-staging.yml deploys to IC canister
  • [ ] GitHub Packages authentication working in CI

Production Deployment

  • [ ] Custom domain configured with public/.well-known/ic-domains
  • [ ] DNS CNAME pointing to <canister-id>.icp0.io
  • [ ] Domain registered with IC boundary nodes
  • [ ] SSL certificate provisioned (automatic after registration)
  • [ ] Verify domain with dig and browser test
  • [ ] Canister has sufficient cycles (check with dfx canister status)

SSO Verification (if auth-integrated)

  • [ ] Login redirects to think-tank and back
  • [ ] Session cookie set on .helloworlddao.com domain
  • [ ] ProtectedRoute redirects unauthenticated users correctly
  • [ ] Logout clears session across suites
  • [ ] No infinite redirect loops

Performance Verification

  • [ ] Bundle size within 500KB gzipped (NFR6)
  • [ ] Build time under 3 minutes (NFR1)
  • [ ] Lighthouse performance score acceptable
  • [ ] No console errors in production build

Adding Suite-Specific Code

After the template is set up:

  1. Add pages to src/pages/
  2. Add components to src/components/
  3. Import shared components: import { Button, Card } from '@hello-world-co-op/ui'
  4. Import auth: import { useAuth } from '@hello-world-co-op/auth'
  5. Import API utilities: import { createActor } from '@hello-world-co-op/api'
  6. Add route definitions in src/App.tsx
  7. Write co-located tests (Component.test.tsx next to Component.tsx)

When developing a suite alongside package changes, use npm link:

bash
# In the package repo
cd ../ui && npm install && npm run build && npm link

# In your suite
cd ../<suite-name> && npm link @hello-world-co-op/ui

See Local Setup Guide for the full workflow.

Hello World Co-Op DAO