Appearance
Dispatch β Phase 1 (MVP Core) Implementation Plan β
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Each task below is one Linear issue; expand it into red β green β commit steps at execution. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Take Dispatch from the design-system reference to a deployed, usable MVP β Google Workspace auth + role gating, admin CRUD, the member app (Dispatch/Mandates/Tasks), skills + registration + validation, completion derivation, connection indicator + PWA β on the real Go/Supabase/Angular stack.
Architecture: One Go (Gin) API on Fly fronts a Supabase Postgres domain; Angular (latest, standalone/signals/zoneless, SSR+SSG) on Cloudflare consumes it via a typed client generated from the API's OpenAPI spec. Auth is Supabase Google Workspace OIDC with role-based authorization. See docs/dispatch-production-design.md.
Tech Stack: Nx 22 monorepo (bun) Β· Go 1.26 + Gin + swaggo/swag/gin-swagger (via @nx-go/nx-go, multi-module go.work) Β· Supabase (Postgres + pgvector + Auth + Realtime + Storage) Β· Angular 21 (standalone, signals, zoneless, SSR/SSG; via @nx/angular) + Tailwind v4/DaisyUI v5 Β· Upstash Redis Β· Fly.io Β· Cloudflare Β· GitHub Actions.
Architecture as built (WS0 β supersedes the path wording below). The repo is an Nx workspace managed with bun. Projects:
apps/web(Angular),apps/api(Go),apps/web-e2e(Playwright). Shared Go packages are@nx-golibraries underlibs/(each its own module), notapps/api/internal/*; shared TS (generated API client, cross-surface rules) also lives underlibs/. The original/api/...and/web/...paths in the tasks below map toapps/api/...andapps/web/.... Tasks run as nx targets (bunx nx test api,bunx nx build web,bunx nx run-many -t lint test build).design-system/anddocs/are excluded from the graph via.nxignore.
Out of scope (later phases): Dispatch AI / RAG / Python worker (Phase 2) Β· document conversion PDF / Word / Excel β Markdown for AI ingestion (MarkItDown) (Phase 2) Β· Pulse Β· OKRs Β· governance voting (Phase 2) Β· GACx wallet / ledger / on-chain (Phase 3). In Phase 1, gacx_reward is a display-only column on tasks.
File structure (monorepo) β
text
apps/
web/ Angular 21 app (standalone, signals, zoneless, SSR/SSG)
src/app/core/ auth, http, connection, theme, generated API client
src/app/features/ dispatch, mandates, tasks, admin, register
src/app/shared/ schema-driven table/form, design-system components
src/styles.css imports design-system/src/app.css (theme + bespoke)
web-e2e/ Playwright e2e for web
api/ Go (Gin) API β own go.mod (go.work multi-module)
main.go bootstrap: config + router
internal/ app-private packages (config, httpx, router)
docs/ generated swagger.json/yaml (swag init output)
libs/ shared @nx-go Go libs (auth, crud, domain, completion,
skills, notifications) + shared TS (api-client, rules)
supabase/
migrations/ SQL migrations (schema + RLS + seed)
config.toml supabase CLI config
infra/
fly/api.fly.toml Go API Fly app (apps/api/Dockerfile builds it)
cloudflare/wrangler.toml Angular Pages/Workers config
.github/workflows/ ci.yml (lint/test/build) + deploy.yml (skeleton)
nx.json Β· package.json Β· bun.lock Β· tsconfig.base.json Β· go.work Β· .nxignoreDesign principle: files that change together live together; one responsibility per file; follow Go and Angular idioms. App-private Go (router wiring, config) stays in apps/api/internal/; reusable domain/services become @nx-go libraries under libs/. The design-system theme is imported from design-system/src/app.css (Tailwind v4 + DaisyUI v5 entry: themes, fonts, bespoke layers), not rewritten.
Workstream 0 β Foundation & infra scaffolding β
Task 0.1: Monorepo + tooling skeleton β
Files: Create /api/go.mod, /web/ (Angular workspace), /supabase/config.toml, /.github/workflows/ci.yml, root README.md, .editorconfig, .gitignore updates. Build: go mod init for the API; ng new (standalone, zoneless, SSR) for /web; supabase init; CI skeleton that runs go vet/go test, ng lint/ng test, and a no-op build. Verify: go build ./... succeeds in /api; ng build succeeds in /web; CI green on an empty PR. Commit.
Task 0.2: Go API skeleton + health + Swagger β
Files: /api/cmd/server/main.go, /api/internal/config/config.go, /api/internal/httpx/health.go, /api/internal/httpx/health_test.go. Build: Gin router; GET /health returns {"status":"ok"}; config from env (PORT, SUPABASE_URL, SUPABASE_JWT_SECRET, DATABASE_URL); wire gin-swagger to serve /swagger/*any; add a swag init make target. Verify (TDD): httptest test asserts GET /health β 200 {"status":"ok"} (write failing first). swag init generates /api/docs/swagger.json. curl localhost:7070/swagger/index.html renders. Commit.
Task 0.3: Angular app shell + design-system theme port β
Files: /web/src/styles.css, /web/src/app/app.config.ts, /web/src/app/app.config.server.ts, /web/angular.json, /web/postcss.config.js. Build: provideZonelessChangeDetection(), provideRouter, provideClientHydration(withEventReplay()), provideHttpClient(withFetch()); import design-system/styles.css + bespoke.css; Tailwind v4 + DaisyUI v5 via PostCSS (no CDN); data-theme="dispatch" default with light/dark toggle persisted to dispatch_theme. Verify: ng build + SSR build succeed; a rendered page shows brand tokens (neon primary var(--color-primary), Instrument Serif/Inter Tight/JetBrains Mono fonts). Component test asserts theme toggle flips data-theme and persists. Commit.
Task 0.4: Supabase project + migration tooling + Fly/Cloudflare configs β
Files: /supabase/config.toml, /infra/fly/api.fly.toml, /infra/cloudflare/wrangler.toml (or Pages config), /.github/workflows/deploy.yml (skeleton). Build: supabase start runs locally; a trivial first migration (extensions: pgcrypto, vector) applies; fly.toml for the Go API (internal port 7070, health check /health); Cloudflare config for the Angular SSR/SSG build. Verify: supabase db reset applies migrations clean; fly config validate passes; ng build output is Cloudflare-deployable. Commit (no live deploy yet).
Workstream 1 β Auth & roles β
Task 1.1: Supabase Google Workspace OIDC + hosted-domain gate β
Files: /supabase/config.toml (auth providers), /supabase/migrations/*_auth_profile.sql, /docs/auth-setup.md. Build: Enable Google provider; restrict to the workspace hosted domain; a public.users row is created/linked on first sign-in carrying membership_role (default Member Partner), function, primary_org, derived from the auth user + OIDC name claim. Verify: Sign-in with an in-domain Google account creates a users row with the OIDC name; an out-of-domain account is rejected. Documented in auth-setup.md. Commit.
Task 1.2: Go JWT verification + role-guard middleware β
Files: /api/internal/auth/jwt.go, /api/internal/auth/middleware.go, /api/internal/auth/middleware_test.go. Build: Verify the Supabase-issued JWT (HS256 via SUPABASE_JWT_SECRET), extract user_id + membership_role; RequireAuth() and RequireRole(roles...) middleware. Roles: Member Partner / Managing Partner / Admin. Verify (TDD): Tests (failing first) β valid token β context carries user_id+role; missing/invalid β 401; RequireRole(ManagingPartner) with a Member token β 403, with an MP token β passes. Commit.
Task 1.3: Angular auth (login, callback, guards, SSO gate) β
Files: /web/src/app/core/auth/auth.service.ts, auth.guard.ts, role.guard.ts, /web/src/app/features/login/. Build: Supabase JS client; signal-based AuthService (current user + role); login + OIDC callback; authGuard on app routes; roleGuard(['Managing Partner','Admin']) on /admin; admin button hidden unless role qualifies. Browser-only session handling deferred to afterNextRender. Verify: Component/route tests β unauthenticated β redirected to login; Member hitting /admin β blocked; MP β allowed; admin button visibility matches role. Commit.
Workstream 2 β Domain schema & migrations β
Task 2.1: Core domain tables β
Files: /supabase/migrations/*_core_domain.sql. Build: organizations, mandates (org, rate, duration, assignee, status, scope), tasks (mandate, assignee, due, gacx_reward numeric display-only, status enum Queued/Progress/Live/Passed/Failed, updated), skills (catalog), user_skills (self_score numeric(4,2), validated_score numeric(4,2), coefficient int default 0, status enum pending/approved/rejected), votes + vote_ballots, notifications. All with created_at/updated_at. Verify: supabase db reset applies clean; constraint tests β user_skills scores constrained 0.00β10.00; task status enum enforced. Commit.
Task 2.2: Role-aware authorization policies β
Files: /supabase/migrations/*_rls_policies.sql, /docs/authz-matrix.md. Build: Role-aware RLS: Members read their own queue/mandates/tasks/profile; Managing Partner/Admin read+write all; only MP/Admin write user_skills.validated_score. Document the matrix. Verify: SQL policy tests via supabase β a Member JWT cannot read another member's tasks; an MP JWT can; a Member cannot set a validated score. Commit.
Task 2.3: Seed data (reference parity) β
Files: /supabase/migrations/*_seed.sql (or seed script gated to non-prod). Build: Seed orgs/mandates/tasks/users/skills matching the reference demo so screens have realistic data; mirrors the dispatch_admin_v3 seed. Verify: After db reset, counts and a sample mandate's tasks match the documented seed; completion of the sample mandate computes to the expected % (cross-checks Task 3.3). Commit.
Workstream 3 β Go API: CRUD engine + domain services β
Task 3.1: Generic crud[T] engine + entity schemas β
Files: /api/internal/crud/engine.go, /api/internal/crud/schema.go, /api/internal/domain/*.go, /api/internal/crud/engine_test.go. Build: Generic typed CRUD over Postgres (list/get/create/update/delete) with validation from a per-entity schema (mirrors admin.js SCHEMA); register organizations, mandates, tasks, users. Routes wired under RequireAuth + appropriate RequireRole. Verify (TDD): Handler tests via httptest against a test DB β createβgetβlistβupdateβdelete round-trip for organizations; required-field validation returns 400; Member role blocked from admin-only writes. Commit.
Task 3.2: OpenAPI annotations + generated spec + CI drift check β
Files: annotations across /api/internal/**/*.go, /api/docs/ (generated), /.github/workflows/ci.yml (add swag step). Build: Annotate every handler (request/response schemas, auth, error shapes); swag init generates /api/docs/swagger.json; CI runs swag init and fails if the working tree drifts. Verify: swag init clean; /swagger/index.html lists all CRUD + domain routes with schemas; CI fails on a deliberately stale annotation, passes when fixed. Commit.
Task 3.3: Completion service β
Files: /api/internal/completion/completion.go, completion_test.go. Build: Derive mandate completion from its tasks, weighted: Passed 100% Β· Live 60% Β· Progress 50% Β· Queued/Failed 0%. Exposed on the mandate read model (not stored). Verify (TDD): Table-driven tests covering empty mandate (0%), all-passed (100%), mixed weights, and the seeded sample mandate's expected %. Commit.
Task 3.4: Skills + user-skills + validation queue β
Files: /api/internal/skills/handlers.go, validation.go, skills_test.go; routes /users/:id/skills, /validation-queue. Build: Skills catalog CRUD; per-user-skill self/validated scores (0.00β10.00, 2 decimals) + coefficient; validation queue listing all pending skills; MP-only Approve (set validated)/Reject/Re-open. Verify (TDD): Tests β adding a skill stores it pending with coefficient 0; validation queue lists pending across members; Approve as MP sets validated_score and clears from queue; Approve as Member β 403; score outside 0β10 β 400. Commit.
Task 3.5: Notifications + Supabase Realtime publish β
Files: /api/internal/notifications/handlers.go, realtime.go, notifications_test.go. Build: Notification create on member-registration/events; GET /notifications (list/unread) + mark-read; publish via Supabase Realtime channel so MP clients receive live. Verify (TDD): Tests β registration creates a "needs verification" notification targeted to MPs; mark-read flips unread count. Manual: a second browser as MP receives the notification live. Commit.
Workstream 4 β Angular app β
Task 4.1: Generated typed API client + HTTP wiring β
Files: /web/src/app/core/api/ (generated), /web/package.json (codegen script), /web/src/app/core/http/auth.interceptor.ts. Build: Generate a typed client from /api/docs/swagger.json (orval or openapi-generator); auth interceptor attaches the Supabase JWT; error interceptor maps API error shapes to toasts. Verify: Codegen produces typed models/services; an interceptor test asserts the Authorization header is attached; a smoke call to /health via the client succeeds against a running API. Commit.
Task 4.2: Schema-driven CRUD components β
Files: /web/src/app/shared/crud/ (table, form-modal, delete-confirm), /web/src/app/shared/crud/*.spec.ts. Build: A schema-driven table + create/edit modal + delete confirmation + live search + toasts, driven by an entity-schema object (Angular analog of admin.js SCHEMA), using DaisyUI classes. Verify: Component tests β table renders columns from schema; form enforces required fields; delete asks for confirmation; search filters rows. Commit.
Task 4.3: Admin console β
Files: /web/src/app/features/admin/ (dashboard, organizations, mandates, tasks, users, validation-queue, notifications slide-over). Build: Sidebar nav 1:1 with routes (/dashboard, /dashboard/mandate/:id, /organizations, /mandates, /tasks, /users, /users/:id, /validation-queue); dashboard stat strip + completion rings + mandate breakdown + People & activity; entities via the schema-driven CRUD; validation queue; notifications slide-over (full-viewport on mobile). Styling uses the .adm-*/.dash-* bespoke classes. Verify: Route tests for the 1:1 mapping; completion ring reflects the API completion value; validation-queue Approve updates the row; e2e covered in Task 5.2. Commit.
Task 4.4: Member app (Dispatch / Mandates / Tasks) β
Files: /web/src/app/features/dispatch/, mandates/, tasks/, navbar. Build: Fixed navbar + tabbed panels: Dispatch (AI-curated daily task queue cards β org/reward/status; AI chat panel stubbed/disabled until Phase 2), Mandates (active + matched cards), Tasks. Tab switching as signal state; role-gated Admin button. (Pulse/OKRs/Vote/Wallet tabs are placeholders deferred to later phases.) Verify: Component tests β queue renders from the API; tab switching updates the active panel; Admin button hidden for Members. Commit.
Task 4.5: Registration first-run flow β
Files: /web/src/app/features/register/ (profile, skills, review steps). Build: 3-step flow: Profile (full name pre-filled from OIDC name, editable; SSO email read-only; primary org) β Skills (tag typeahead fed from the skills catalog; self-score each 0.00β10.00; each stored coefficient 0/pending) β Review β Start; success β "Enter Dispatch β". On submit: writes the user, marks skills pending, fires the MP notification, populates the validation queue. Verify: Component tests β name pre-fills from the auth profile; skill typeahead pulls from the catalog; Start submits and routes into the app. e2e in Task 5.2. Commit.
Task 4.6: Connection indicator + PWA β
Files: /web/src/app/core/connection/connection.service.ts, connection-indicator.component.ts, ngsw-config.json, manifest.webmanifest. Build: ConnectionService (signals) polling Go /health + navigator.onLine β Connected/Reconnecting/Offline pill, mounted in the root layout, deferred to afterNextRender; @angular/service-worker PWA (manifest + icons ported from the design system), registered browser-only after hydration. Verify: Service test β health-OK β Connected, offline event β Offline, failing poll β Reconnecting. PWA: production build registers the SW; app is installable. Commit.
Workstream 5 β Tests, CI/CD & deploy β
Task 5.1: Shared-rule parity tests β
Files: /api/internal/completion/completion_test.go (extend), /web/src/app/shared/rules/competence.spec.ts. Build: Lock the cross-surface invariants: completion weighting (Passed/Live/Progress/Queued/Failed) and the 0.00β10.00 / 2-decimal competence rule, tested identically on both Go and Angular sides. Verify: Both suites pass with matching fixtures; a deliberate weight mismatch fails. Commit.
Task 5.2: Playwright e2e for critical flows β
Files: /web/e2e/login-dispatch.spec.ts, registration-validation.spec.ts. Build: e2e against a seeded local stack: (1) login β land on Dispatch with the task queue; (2) registration β member appears in admin Users + validation queue β MP approves a skill. Verify: Both specs pass headless locally and in CI. Commit.
Task 5.3: CI/CD pipeline + deploy β
Files: /.github/workflows/ci.yml, deploy.yml. Build: CI: Go (vet/test), swag drift check, Angular (lint/test/build), Playwright e2e. Deploy on main: Supabase migrations (supabase db push), Go API β Fly (flyctl deploy), Angular β Cloudflare. Secrets (Supabase keys, JWT secret) from GitHub/Fly/Cloudflare secret stores β never in the repo. Verify: CI green on a PR; a manual deploy.yml run brings up the API (/health 200 on Fly), the Angular app on Cloudflare, and a live login works end-to-end against the deployed stack. Commit + tag phase-1.
Self-review (spec coverage) β
- Auth + roles β WS1 (D4, D5). Admin CRUD β WS3.1 + WS4.3. Member app β WS4.4. Skills/registration/validation β WS3.4 + WS4.5. Completion β WS3.3 + WS5.1. Connection indicator + PWA β WS4.6. CI/CD + tests β WS5. Swagger/OpenAPI (D13) β WS0.2 + WS3.2 + WS4.1. Theme port (D11) β WS0.3. Hosting (D10) β WS0.4 + WS5.3.
- Deferred correctly: Dispatch AI/RAG/Python worker (Phase 2), Pulse/OKRs/voting (Phase 2), GACx wallet/ledger/on-chain (Phase 3);
gacx_rewardpresent as display-only (WS2.1, WS4.4 cards). - pgvector/Voyage/Claude/Upstash are not exercised in Phase 1 (no AI), but the
vectorextension is enabled in WS0.4 so Phase 2 needs no migration churn.
Execution: use superpowers:subagent-driven-development β one subagent per task, review between tasks.