FAS Suite Creation Guide
This guide walks through creating a new suite from the suite-template.
For detailed template usage instructions, see also:
- suite-template/USAGE.md -- Step-by-step template usage
- suite-template/TEMPLATE_CHECKLIST.md -- Post-creation checklist
- suite-template/MIGRATION_GUIDE.md -- Migrating existing code to the template
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:
git clone https://github.com/Hello-World-Co-Op/<suite-name>.git
cd <suite-name>Step 2: Update Package Identity
Edit package.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:
{
"canisters": {
"<suite_name>_assets": {
"type": "assets",
"source": ["dist"],
"build": ["npm run build"]
}
}
}Step 4: Create IC Asset Canister
# 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.jsonStep 5: Configure Environment
cp .env.example .env.localEdit .env.local with your canister IDs and service URLs. At minimum:
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.
# .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-caiLesson 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.stagingwhen adding oracle-bridge integration.
Step 6: Set Up CI/CD
The template includes .github/workflows/ci.yml and a deploy workflow. Update:
- Workflow suite name if referenced in workflow
- GitHub Actions secrets -- ensure org-level secrets are available:
DFX_IDENTITY_PEM-- dfx identity for deployment
- 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:
# 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.jsonThis pattern is already implemented in:
- All 4 custom
deploy-staging.ymlworkflows (think-tank, dao, dao-admin, governance) - The shared
suite-deploy.ymlreusable 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 indfx.jsonis required fortype: "assets"canisters. If omitted, dfx falls back to auto-detection, which findsvite.config.tsand runsnpm run buildanyway. 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):
- Create
public/.well-known/ic-domainswith the domain name - Configure DNS CNAME to point to
<canister-id>.icp0.io - 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 - Verify with
dig +short <domain>and browser test
Step 8: Install and Verify
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 # ESLintSuite-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/authhooks (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/authinterface - 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)
How to Integrate oracle-bridge for Cookie SSO
If your suite needs cross-suite SSO:
- Add
VITE_ORACLE_BRIDGE_URLto.env.local:bashVITE_ORACLE_BRIDGE_URL=http://localhost:3000 # local VITE_ORACLE_BRIDGE_URL=https://staging-oracle.helloworlddao.com # staging - Create an
authCookieClientservice that calls oracle-bridge endpoints - Wrap your app with
AuthProviderBridge - Implement
ProtectedRoutefor authenticated pages - Add
LoginRedirectpage that redirects to think-tank login withreturnUrl
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-asyncfor 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.tsxandgenerate-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_datafrom localStorage passively
dao-suite (FAS-6) -- Cross-Suite SSO
- Pattern: Auth Bridge with cookie SSO
- Key decisions:
authCookieClientbridges cookie session to localStorage for legacy components,ProtectedRoutewithuseCallbackfor stable refs - Lesson learned: Infinite redirect loop caused by unstable
useCallbackrefs inProtectedRoute-- 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
validateReturnUrlto 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:
returnUrlmust be validated against allowed admin domains to prevent redirect attacks
Post-Creation Checklist
Basic Setup
- [ ]
package.jsonname updated to@hello-world-co-op/<suite-name> - [ ]
dfx.jsoncanister name updated - [ ] IC asset canister created
- [ ]
.env.localconfigured 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.ymlruns successfully (lint, typecheck, test, build) - [ ]
deploy-staging.ymldeploys 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
digand 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.comdomain - [ ]
ProtectedRouteredirects 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:
- Add pages to
src/pages/ - Add components to
src/components/ - Import shared components:
import { Button, Card } from '@hello-world-co-op/ui' - Import auth:
import { useAuth } from '@hello-world-co-op/auth' - Import API utilities:
import { createActor } from '@hello-world-co-op/api' - Add route definitions in
src/App.tsx - Write co-located tests (
Component.test.tsxnext toComponent.tsx)
npm link for Local Package Development
When developing a suite alongside package changes, use npm link:
# 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/uiSee Local Setup Guide for the full workflow.
Related Documentation
- Architecture Overview -- Package and suite architecture
- Repository Map -- All FAS repositories
- Local Setup Guide -- Getting started locally
- Troubleshooting -- Common issues
- Cross-Suite Auth Debugging -- SSO troubleshooting
- Rollback Procedures -- Suite-specific rollback steps