README
¶
100 - AI Chat Wizard
A streaming AI chat workspace built with Go WASM + gRPC + GoGRPCBridge + provider runtime.
The browser client and server are both written in Go. The chat UI and background render worker compile to
WASM, and chat messages stream over a gRPC tunnel carried by WebSocket (/socket). The server can resolve
models from OpenAI, Anthropic, and Cerebras catalogs through one runtime provider registry.
Why this example matters for GWC
Example 100 is the flagship integration test for the GoWebComponents framework. It is not primarily a SaaS demo — it exists to verify that every major framework pattern works together in one coherent, full-stack application.
The specific GWC capabilities it demonstrates:
| Pattern | Where it lives |
|---|---|
| Single-shell routed SPA | client/app/app.go + client/app/routes.go — one WASM app, multiple distinct route surfaces |
| Server-rendered public routes | server/app/server.go — shell-first HTML delivery for /, /home, /pricing, /signup |
| WASM-authenticated workspace shell | client/app/app_shell.go — session-gated workspace, settings, and dashboard surfaces |
| Typed gRPC bridge | client/app/auth.go, client/app/stream.go, client/app/runtime.go, server/app/server.go — typed RPCs over GoGRPCBridge WebSocket tunnel |
| Worker-backed rendering | client/backgroundworker/ — markdown and render metadata tasks offloaded to a background WASM worker |
| Cross-tab preference sync | client/app/model_preferences.go, client/app/scroll_memory.go, state/ — sidebar state, scroll memory, and model selection persist and sync across tabs |
| Streaming progressive render | client/app/thread*.go — server-streamed ChatChunk deltas applied incrementally to the thread view |
| Route-scoped async state | client/app/settings_*.go, client/app/dashboard_shell.go — per-route data that loads on activation and redraws on RPC update |
If a refactor silently breaks any row in this table, the example has stopped being a useful framework signal — regardless of whether product tests still pass.
Developer showcase controls
RelayDesk includes two route-gated, developer-only teaching surfaces for local framework readers:
- Add
?gwc-dev=tourto any public or authenticated route to open a dismissible tour that maps major visible surfaces to the GWC pattern and primary source file they demonstrate. - Add
?gwc-dev=demo-helperor?gwc-dev=panelto any route to open a dismissible runtime helper panel with the current route ID, active shell section, async resource states, selected model, dashboard slice, and other runtime signals.
These controls are query-param gated and do not appear in the normal product path.
Start here
These are the four highest-signal files. Read them in order before exploring anything else.
1. client/app/app.go — top-level route dispatcher
Teaches: how GWC routes WASM UI output based on URL path. ParseApp owns the single render path from "URL changed" to "correct surface rendered". Every public, auth, and workspace view forks from this one function. Understanding it is the prerequisite for understanding anything else.
2. client/app/app_shell.go — authenticated workspace composition
Teaches: multi-region UI composition under one stateful shell. renderWorkspaceShell assembles the sidebar, thread panel, settings modal, and canvas surface as a layered set of GWC nodes driven by one appViewState. Shows how large UI surfaces stay composable without a global state hack.
3. client/app/routes.go + server/app/server.go — full routing contract
Teaches: the split between client-side route selection and server-side shell delivery. The client defines what path maps to what view; the server defines which request paths get the SPA shell vs direct static responses. Reading both together is the only way to understand why public routes, auth routes, and app routes behave differently despite sharing one HTML document.
4. client/app/dashboard_shell.go — representative complex surface
Teaches: a complete async-loading surface pattern: RPC fetch on activation, typed view-model derivation, multi-panel tile grid, and role-gated content — all in one self-contained file. Once you understand dashboard, settings panels and admin surfaces follow the same structure.
Systems Writeup Chapter Order
Read the full systems writeup in this order when you want the narrative version of Example 100 before drilling into source files:
docs/HOW_EXAMPLE_100_WORKS.md- whole-system overview from public request through authenticated workspace, chat runtime, admin flow, data, observability, and growth.docs/PUBLIC_ROUTE_DELIVERY.md- server shell delivery, boot shell, i18n bootstrap, hydration, and auth-entry transitions.docs/AUTHENTICATED_SHELL.md- route ownership, shell composition, persistent preferences, model selection, cross-tab sync, worker usage, and gRPC tunnel lifecycle.docs/CHAT_REQUEST_LIFECYCLE.md- draft state, send handling, provider selection, streaming, route normalization, persistence, replay, and failure branches.docs/ADMIN_DASHBOARD_SUBSYSTEM.md- role resolution, workspace-admin versus superuser scope, shared slice patterns, typed RPC boundaries, drill-downs, mutations, and diagnostics.docs/DATA_LAYER.md- schema ownership, query loading, store boundaries, migration expectations, seed data, and policy placement.docs/OBSERVABILITY_FAILURE_HANDLING.md- logs, audit events, support IDs, customer-safe errors, operator diagnostics, and outage/stale-state behavior.docs/EXTENSION_SEAMS.md- how to add providers, dashboard slices, routes, worker tasks, settings sections, and RPCs.docs/SYSTEMS_GLOSSARY.md- local terminology for shell, slice, bootstrap, worker task, provider snapshot, control plane, scoped admin view, and public conversation route.
Quality Proofs
Example 100 has three focused proof gates for the areas most likely to regress as the showcase grows:
| Gate | Document | What it proves |
|---|---|---|
| Accessibility | docs/ACCESSIBILITY_CHECKLIST.md |
Keyboard navigation, landmarks/headings, form names/help/errors, live-region behavior, reduced motion, contrast/non-color states, and screen-reader smoke paths for login, first chat, settings save, and one admin mutation. |
| Performance | docs/PERFORMANCE_PROOF.md |
Fast public first paint, authenticated shell startup, streamed first token, worker-backed markdown isolation, paged sidebar behavior, and dashboard slice responsiveness with local commands and thresholds. |
| Bridge churn | docs/BRIDGE_CHURN_HARDENING.md |
Typed bridge states, central RPC policy, bounded best-effort queue, retry/idempotency rules, settings-save churn UX, observability counters, browser regressions, and the no-round-robin architecture decision. |
The server-side benchmark details remain in docs/PERFORMANCE.md. Use docs/PERFORMANCE_PROOF.md for browser/runtime proof and regression thresholds.
Architecture
Browser (Go WASM, port 8095) Go Server (in-process gRPC)
+--------------------------------------------+ +---------------------------------------------+
| App shell routes: / /home /pricing /app... | | HTTP mux |
| - Auth + chat UI state | | - GET /, /home, /pricing, /app/* -> shell |
| - Composer -> ChatService.Send() stream |<-->| - GET /app/chat.wasm, /worker/*.wasm |
| WS/gRPC| - GET /chat-bootstrap.js, /healthz |
| Background worker (WASM) | | - /socket -> GoGRPCBridge tunnel |
| - Markdown/render metadata tasks | | |
+--------------------------------------------+ | gRPC ChatService |
| - Auth/session RPCs (Login/GetSession/...) |
| - Chat RPCs (Send/List/Load/Resolve) |
| - Admin/superuser RPCs |
| |
| Store + provider runtime |
| - SQLite tables (auth, chat, usage, ops) |
| - Provider registry + model catalog |
+---------------------------------------------+
Data flow
- Visitor lands on a public route (
/,/home,/pricing,/signup) and bootstraps the shell. - User authenticates via
Login/Signup; client persists token and validates withGetSession. - Client opens the gRPC tunnel at
/socket; runtime marksgrpc readyand worker readiness. - Authenticated shell hydrates profile/settings/catalog/conversation state from typed RPCs.
- User sends a prompt; WASM client calls
ChatService.Sendwith history, model, tone, and thinking settings. - Server resolves provider/model, streams
ChatChunkdeltas, and persists usage and conversation updates. - Client renders streamed output incrementally, normalizes canonical thread route, and keeps state resumable.
Prerequisites
| Tool | Purpose |
|---|---|
| Go ≥ 1.25 | Build server and WASM client |
protoc |
Proto compiler (generate Go stubs) |
protoc-gen-go |
Go protobuf plugin |
protoc-gen-go-grpc |
Go gRPC plugin |
| Tailwind CSS CLI | Built via go run ./tools/gwc tailwind (cached under third_party/tailwindcss/bin) |
The third_party/GoGRPCBridge git submodule must be initialised:
git submodule update --init --recursive
Quick start
1. Initialize submodules (first clone only)
git submodule update --init --recursive
2. Pick one shared local DB path
The server and cmd/seed-test-db must use the same CHAT_DB_PATH.
$env:CHAT_DB_PATH = "examples/server/ai-chat-wizard/bin/runtime/chat_history.db"
3. Build both WASM artifacts
go run ./tools/gwc build -app .\examples\server\ai-chat-wizard\client\main.go -root .\examples\server\ai-chat-wizard\client -out .\examples\server\ai-chat-wizard\bin\client\app\chat.wasm -json
go run ./tools/gwc build -app .\examples\server\ai-chat-wizard\client\backgroundworker\main.go -root .\examples\server\ai-chat-wizard\client\backgroundworker -out .\examples\server\ai-chat-wizard\bin\client\worker\background-worker.wasm -json
The server serves Brotli sidecars when present, but raw .wasm artifacts are enough for local development.
4. Seed local login accounts
go run ./examples/server/ai-chat-wizard/cmd/seed-test-db
The managed chat-wizard start path now seeds the target CHAT_DB_PATH automatically when the local runtime DB is missing or empty, but running the seed command explicitly is still the deterministic way to reset the baseline demo state.
Seeded credentials:
customer@email.com / passwordadmin@email.com / password
What cmd/seed-test-db creates today:
- two auth users (
customer@email.com,admin@email.com) - two user-profile rows with default model/tone/thinking preferences
- three demo conversations and messages for
customer@email.com - schema defaults (including
su_rolesdefinitions) fromsql/store/schema.sql
What it does not create:
- no
su_user_rolesgrants (soadmin@email.comis not automatically superuser) - no seeded workspace-admin membership graph for dashboard mutations
- no seeded superuser control-plane mutations or override history
5. Choose provider mode
Use real provider keys:
$env:OPENAI_API_KEY = "sk-..."
$env:ANTHROPIC_API_KEY = "sk-ant-..."
$env:CEREBRAS_API_KEY = "csk-..."
Or run fully local provider stubs:
$env:CHAT_PROVIDER_STUBS = "all"
6. Start the managed example server
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server start -json
Useful lifecycle commands:
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server status -json
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server restart -json
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server stop -json
Open http://127.0.0.1:8095/.
7. Verify auth and first chat
- Log in with
customer@email.com / password. - Create a new thread and send one prompt.
- Refresh and confirm the thread reopens at
/app/thread/:publicID.
8. Run focused browser verification
go test -tags playwrightgo ./test/playwrightgo/examples -run TestExample100StartupBoot -v
go test -tags playwrightgo ./test/playwrightgo/examples -run TestExample100AuthenticatedHappyPath -v
go test -tags playwrightgo ./test/playwrightgo/examples -run TestExample100RouteSmokePricingAuthDashboard -v
Superuser-only verification path:
admin@email.comcan validate auth and denied-role behavior by default.- For allowed superuser browser checks, grant
suto that user in the sameCHAT_DB_PATHdatabase (seeOPERATOR_RUNBOOK.md, section6) and restart the managed server.
Route model
The server serves one shell-first SPA surface, and the client router decides which public/auth/chat view to render.
| Route class | Paths | Behavior |
|---|---|---|
| Public landing routes | /, /home, /capabilities, /pricing, /signup |
Server returns the chat shell bootstrap HTML; client renders marketing/auth-facing views for unauthenticated users. |
| Auth entry | / (login default), /signup (signup mode) |
Auth form submits to gRPC (Login/Signup), then authenticated sessions pivot into /app. |
| App root and thread routes | /app, /app/thread/:publicID, /app/thread/:publicID/canvas/:canvasID |
Authenticated shell routes for conversation list, active thread replay, streaming replies, and canvas artifacts. |
| Settings routes | /app/settings?panel=settings-* |
Still part of the SPA app shell; query panel selects profile/tone/prompt/intelligence/speech/memories/language section. |
| Legacy entry aliases | /login, /logout, /thread/:legacyID |
Server still serves shell for compatibility; client normalizes into current /app route model. |
| Admin/dashboard surfaces | /app/dashboard, /app/dashboard/business, /app/dashboard/customers, /app/dashboard/chats, /app/dashboard/providers, /app/dashboard/ops |
Dedicated dashboard routes are part of the SPA route model; server still enforces role gates at RPC boundaries for admin/superuser data access. |
| Static/runtime assets | /chat-bootstrap.js, /app/chat.wasm, /worker/background-worker.wasm, /static/*, /healthz, /socket |
Not SPA routes; served directly by HTTP mux or gRPC bridge (/socket). |
SPA vs server-shell behavior
- For route-like paths (public routes plus
/app*paths without file extensions), the server returns the same shell document. - The client-side router selects the active view and can normalize thread routes after conversation resolution.
- Asset paths (WASM, JS, CSS, images, static files) bypass SPA shelling and are served directly.
- Legacy
/chat.wasmand/background-worker.wasmrequests are rewritten to/app/chat.wasmand/worker/background-worker.wasm.
Authenticated shell UI region → GWC pattern map
This table maps the visible regions of the /app workspace shell to the GWC patterns and packages each one exercises. Use it as a reading guide when exploring client/app/app_shell.go.
| UI region | Primary files | GWC pattern demonstrated |
|---|---|---|
| Top control bar | client/app/app_shell.go, client/app/panel.go |
ui composition + route-aware conditional rendering; control state derived from appViewState without prop drilling |
| Sidebar | client/app/sidebar.go |
Async conversation list pagination, scroll-position memory (cacheScrollPosition), reactive open/closed toggle via state.Atom |
| Thread body | client/app/thread*.go |
Streaming partial render — ChatChunk deltas applied incrementally; background-worker markdown task dispatch over worker message bridge |
| Composer | client/app/composer.go, client/app/composer_runtime2.go |
Controlled text input with handler composition; disabled-during-stream state; starter-prompt injection; runtime2-backed display-only cost summary via ui.ParallelRegion(...) |
| Settings modal | client/app/settings_*.go, app_shell.go |
Route-scoped panel selection via ?panel= query param; persisted preference RPCs (UpdateProfile); cross-tab preference sync via state package |
| Canvas surface | client/app/canvas*.go |
Conditionally-mounted sub-route (/canvas/:canvasID); iframe sandboxing for untrusted HTML artifact rendering |
| Dashboard tiles | client/app/dashboard_shell.go |
Multi-surface async-loading pattern: RPC on activation, typed view-model derivation, role-gated content, tile grid |
| Admin drawers | client/app/admin_*.go |
Shared drawer/table composition reused across five slice surfaces; mutation banner pattern (confirm → submit → receipt) |
Public-route delivery and hydration model
Public routes (/, /home, /pricing, /signup, /security, /privacy, /terms, /status, /about, /contact) are delivered as one HTML shell document and hydrated by the same WASM binary as the authenticated app. The ownership split:
| Responsibility | Owner | File(s) |
|---|---|---|
| Shell HTML document | Server | server/app/server.go — GET /home, /pricing, etc. return the bootstrap shell |
| Bootstrap loader | Server-owned JS | chat-bootstrap.js — shows loading state, fetches app/chat.wasm, hides on mount |
| i18n bundle delivery | Server (with embedded fallback) | server/app/catalog_loader.go; client embeds catalog via client/catalog/* as compile-time fallback |
| Route selection once mounted | WASM client | client/app/app.go ParseApp — reads window.location.pathname to select landing vs auth vs workspace shell |
| Public-route rendering | WASM client | client/app/landing_shell.go, client/app/landing_info.go, client/app/pricing_shell.go, client/app/signup_shell.go |
| Journey progress band | WASM client | client/app/journey_state.go — shared multi-step progress indicator shown on all public marketing routes |
| Marketing footer / header | WASM client | client/app/marketing_shared.go, client/app/landing_sections.go — shared across all public routes via renderMarketingHeader(...) and renderMarketingFooter(...) |
Key points for framework readers:
- There is no separate SSR build. The server delivers a static shell; the WASM client owns all rendering including the public marketing pages.
- Route normalization happens client-side: the server maps
/,/home,/pricingetc. to the same shell document; the WASM client decides what to render based on the path. - i18n is embedded at WASM compile time today (planned: server-delivered lazy namespace fetch to reduce WASM binary size).
Pricing philosophy and billing vocabulary
Plan boundary
Pro: one serious operator, personal workspace scope, individual execution depth.Team: shared workspace with collaboration and admin relief for coordinated operators.Enterprise: contract/security/compliance path with negotiated controls and procurement flow.
Billing model
Monthly bill must be represented as:
platform fee + usage + service premium
platform fee: fixed recurring subscription charge for the selected plan/workspace tier.usage: raw provider model usage cost from metered token/tool consumption.service premium: platform premium multiplier or basis-point markup applied to raw usage.
Customer-facing vocabulary contract
Use these terms consistently in docs and UI:
platform feeusageservice premiumtotal
Avoid stale pricing language in customer copy:
freeunlimitedall models includedno token caps
Runtime pieces
This is the current runtime chain from first paint to streamed reply.
| Runtime piece | Responsibility | Depends on |
|---|---|---|
Boot shell (/chat-bootstrap.js) |
Shows startup state, loads chat.wasm, then hides once the app mounts. |
HTTP shell route, WASM artifacts, wasm_exec.js. |
WASM client (client/main.go, client/app/*) |
Owns router state, auth/session UX, thread state, composer send, stream rendering, and settings panels. | gRPC tunnel readiness, model/profile/conversation RPCs. |
Background worker (client/backgroundworker/main.go) |
Offloads markdown/render metadata tasks and async render helpers. | Worker WASM artifact and worker message bridge. |
gRPC tunnel (/socket) |
Carries unary + streaming RPCs over WebSocket between browser and in-process gRPC server. | GoGRPCBridge handler, client dial/reconnect loop. |
Server HTTP handlers (server/app/server.go) |
Serves shell routes, static assets, health probe, wasm_exec, and tunnel endpoint. | Runtime config, static dirs, bridge handler. |
| Chat/auth/admin RPC handlers | Implement auth/session, chat send/list/load, model/profile preferences, and admin/superuser surfaces. | Store layer, auth manager, provider registry. |
| Store layer (SQLite) | Persists auth sessions, conversations/messages, usage events, and control-plane records. | SQL query files under sql/store, DB path/config. |
Provider layer (server/provider/*) |
Resolves provider/model runtime, streams completions, and reports health/rate-limit metadata. | API keys or CHAT_PROVIDER_STUBS, model catalog rows. |
Interaction sequence
- Shell HTML and bootstrap JS load, then fetch
app/chat.wasm. - Client mounts, starts worker lifecycle, and opens gRPC tunnel to
/socket. - Auth/session bootstrap resolves (
GetSession, optionalRefreshSession), then app state hydrates (ListModelOptions, profile/settings, conversations). - Composer submit calls
Send; server resolves provider/model, streamsChatChunkdeltas, and writes usage + conversation state. - Client applies streamed deltas, updates canonical thread route, and keeps session/thread state resumable across reloads.
Local Truth vs Planned Future State
This section separates what is already real in example 100 from what is still roadmap material.
| Area | Local truth (implemented now) | Planned future state |
|---|---|---|
| Admin surfaces | Dedicated dashboard routes exist (/app/dashboard + slice routes). Core admin/superuser reads and diagnostics are wired with role guards and audit events. |
Denser end-to-end operator journeys with fully completed UI + mutation loops for every slice. |
| Workspace-admin scope | Workspace-admin scope resolution exists and can read scoped Customers/Chats slices with superuser-only surfaces denied. |
Broader workspace-admin control plane (full action coverage, richer scoped queues/details, fewer read-heavy gaps). |
| Superuser scope | Superuser-only RPCs (GetAdminDashboard, GetSuperuserControlPlane, GetSuperuserSlices, GetLogTail) are enforced and test-covered. |
Full production-grade operator workflows with complete preview/rollback UX and exhaustive browser regressions. |
| Server-owned i18n | Architecture and namespace ownership are documented; client still ships broad embedded catalogs while migration is in progress. | Server-delivered layered catalogs (boot + lazy namespace fetch), minimal embedded fallback strings, and publish/override workflows. |
Stub-Removal Status
This status table tracks placeholder seams so docs do not claim behavior is still stubbed once code goes live.
| Seam | Status | Current truth |
|---|---|---|
server tools (SetServerToolPolicy, RunServerTool) |
Live implementation | server/app/server_tool_stub.go now enforces superuser authz, fresh-session checks, policy validation, and bidirectional server-tool runtime execution. |
provider memory extraction |
Live implementation | Runtime extraction pipeline is live in server/app/server.go; OpenAI, Anthropic, and Cerebras providers all normalize extraction candidates through the shared contract, while the stub provider remains a deterministic no-op for local flows. |
entitlement fail-open guard |
Live implementation | parseRequireUserEntitlement in server/app/authz_entitlement.go now fails closed: unavailable billing state returns codes.Unavailable, and missing or denied entitlements return codes.PermissionDenied. |
control-mutation authz helper |
Live implementation | parseAuthorizeAdminControlMutationScope in server/app/admin_control_mutation_authz.go now enforces the platform and workspace scope rules used by the live control-mutation RPCs. |
Provider Memory Extraction Parity Contract
Intended cross-provider contract for ParseExtractUserMemories:
| Contract element | Required behavior |
|---|---|
| Candidate shape | Each candidate follows provider.UserMemoryCandidate: key, category, summary, detail, usefulness_score (0-100), confidence_score (0-1), rubric_reason. |
| Score semantics | Server clamps scores and only persists candidates meeting current thresholds (usefulness >= 60, confidence >= 0.55) after dedupe and normalization. |
| Malformed-response fallback | OpenAI path uses strict JSON schema first, then fallback object extraction (parseExtractJSONObject) before failing. Parse failures are logged and do not block chat reply flow. |
| Provider failure behavior | Upstream or parse failures return an extraction error to the background enrichment path; runtime logs memory extraction failed and continues without blocking the chat reply flow. |
| User-visible expectation | Memory extraction is asynchronous best-effort enrichment. Chat replies continue even when extraction is skipped, queue-limited, unavailable, or parse-failed. |
Current provider status snapshot:
- OpenAI: live extraction path with strict schema + fallback parsing.
- Anthropic: live extraction path using tool-use output normalization.
- Cerebras: live extraction path using JSON-object extraction plus fallback parsing.
- Stub provider: deterministic no-op extraction (
nilcandidates, no error) for local flows.
Release Notes and Upgrade Caveats (Placeholder Removal)
Use this section when landing placeholder-removal changes so local/dev assumptions and tests are updated in the same release.
Entitlement fail-open fallback removal
- Current behavior:
parseRequireUserEntitlementnow fails closed when billing access control cannot be resolved. - Local/dev impact: seeded or local runs need explicit billing entitlement rows when chat send should succeed.
- Test impact: assert
codes.Unavailableorcodes.PermissionDeniedinstead of expecting chat send to continue when billing state is missing or denied.
Legacy control-mutation authz stub removal
- Current behavior:
parseAuthorizeAdminControlMutationScopenow returns real allow or deny results for feature-flag, experiment, and incident mutations. - Local/dev impact: callers and operator flows should expect real permission and scope validation outcomes instead of
Unimplemented. - Test impact: keep assertions aligned to live allow or deny behavior and shared mutation coverage instead of stub-only expectations.
Release checklist for these removals:
- Update this section with exact rollout date/commit and changed behavior.
- Update manual smoke and operator runbook checks for new deny/error expectations.
- Replace obsolete stub-only tests with real allow/deny/outcome assertions.
Auth Architecture End State
Target model for external auth work:
- One user account per person in
users, with many linked login methods (password,google_oidc, generic workspaceoidc, latersaml) attached as identities, not separate account systems. - Workspace-level auth policy decides which login methods are allowed (
password allowed/blocked,external allowed,sso required) for the selected workspace context. - Password and external providers share one session/token issuance path (
auth_sessionsplus token version checks), so refresh/revoke/audit behavior stays consistent across methods. - Authz remains role-based after login (normal user, workspace admin, superuser); login method does not bypass role scope.
External Auth Product Policy Decisions
| Policy area | Decision |
|---|---|
| Same-email account auto-linking | Auto-link only when external provider email is verified and exactly matches one existing local user with no conflicting existing external identity for that provider+subject. |
| Workspace-required SSO vs password | If workspace policy is sso required, password login is denied for that workspace context. Password can still be allowed for workspaces that do not require SSO. |
| Multi-workspace policy conflicts | Resolve policy against the target workspace selected at login entry. If the workspace requires SSO, enforce SSO for that entry even if another workspace allows password. |
| Superuser break-glass path | Local password remains required for superuser break-glass access in local/dev and emergency recovery flows; external login is optional and never the only superuser path. |
Privileged External-Auth Decision (Resolved)
This is the explicit repo policy for privileged identities:
| Privileged role | Password login | External login (google_oidc / oidc / saml) |
Sensitive mutation gate | Fresh-session rule |
|---|---|---|---|---|
| Superuser | allowed and required as break-glass | allowed for normal session access | must re-auth with local password before sensitive superuser mutations | mutation session must be within superuserMutationSessionMaxAge |
| Workspace admin | allowed | allowed when workspace policy allows external | role/scope authz still enforced; no login-method bypass | standard session validity rules apply |
Implementation references:
server/app/auth_privileged_policy.go(parseAuthorizePrivilegedLoginMethod,parseAuthorizePrivilegedMutationSession)server/app/superuser_control.go(parseRequireSuperuserMutationUserID)server/app/auth_service.goJWTamrclaim used for privileged mutation policy checks
Local Password Rollout Guardrail
Google/OIDC rollout must preserve local email/password as a first-class path for:
- quick QA (
customer@email.com / password,admin@email.com / password) - seeded local testing on one shared
CHAT_DB_PATH - offline development without mandatory external-provider dependencies
Any auth change that breaks local password login in this example is a regression unless explicitly gated by a documented workspace policy test case.
Auth Provider Capability Matrix
| Provider path | Current status | Target users | Required backend/runtime pieces | Required UI/runtime pieces |
|---|---|---|---|---|
| Password | fully wired | local QA, general users, superuser break-glass | user/password auth store, session issuance/revoke, reset/verify token lifecycle | login/signup/reset/update forms, session restore/logout |
| Google OIDC | planned | consumer/prosumer users that prefer social login | start/callback handlers, state+nonce persistence, verified-email extraction, link-or-create, shared session issuance | login entry option, callback route handling, clear deny/error states |
| Generic workspace OIDC | config-only | enterprise/workspace SSO | provider config + policy resolution, workspace callback binding, JIT membership rules | workspace SSO entry, workspace-aware callback and policy messaging |
| SAML (future) | config-only (storage only) | enterprise IdP programs | canonical provider model compatibility, assertion/subject validation, policy enforcement | enterprise entry surfaces and callback/failure messaging |
External-Auth Data Model Comparison
auth_identities + auth_oidc_states + workspace_auth_policies are the current runtime seam that is most migration-resilient for Google/OIDC/SAML direction:
-
Shape A (runtime identity-linking seam):
auth_identities: canonical person-level identity row with provider key/type, provider subject, verified email, and shared session target.auth_oidc_states: callback state/nonce persistence, expected-subject guard, return URL, and consumed tracking.workspace_auth_policies: per-workspace policy gate for local password, external login, required-provider enforcement, and JIT membership behavior.- Shape A keeps account identity and workspace policy close together so provider-specific runtimes can plug in without changing downstream authorization contracts.
-
Shape B (enterprise SSO config bucket):
workspace_sso_configs: per-workspace SSO config rows with provider key, SAML fields, domains, andis_enabled.- Shape B is clean as long-term enterprise config storage and avoids adding provider runtime assumptions into the identity tables.
This split lowers remigration risk when Google OIDC, generic OIDC, and SAML runtime paths land:
Google OIDCandgeneric workspace OIDCmap to Shape A via state+nonce and identity/provider-subject rows.SAMLstarts in Shape B as config-only metadata, then can move to active runtime with one dedicated callback/auth pipeline migration path.- shared identity resolution and workspace policy logic stay stable across current implementation and future enterprise provider additions.
Memory Extraction Provider Parity Decision (Resolved)
Final implementation decision for remembered-preferences extraction:
| Provider | Structured output approach | Parse fallback approach | Shared parity contract |
|---|---|---|---|
| OpenAI | Responses API with strict JSON-schema output | Extract JSON object from text if wrapper noise appears, then parse | []UserMemoryCandidate normalized by category/score/key |
| Anthropic | Messages API tool-use schema (extract_user_memories) with explicit tool choice |
Fallback text payload parse for compatibility; malformed payload resolves to empty candidates | Same normalized candidate contract |
| Cerebras | Chat Completions with response_format.type = json_object plus deterministic prompt contract |
Deterministic strict parse, then JSON-object extraction fallback before surfacing parse failure | Same normalized candidate contract |
Implementation invariants:
- provider output must converge to one typed
UserMemoryCandidateshape with stable field names (key,category,summary,detail,usefulness_score,confidence_score,rubric_reason) - normalization/clamping and key derivation are shared (
parseNormalizeMemoryCandidates) so providers differ only in transport/runtime semantics - provider-specific extraction failures must not silently change stored schema or field meaning
Marketed Capability vs Implemented Capability (Auth/Trust)
This note prevents docs and marketing copy from overstating auth/trust support before runtime exists.
| Capability area | Marketed capability statement (allowed wording) | Implemented capability (current truth) | Risk if overstated |
|---|---|---|---|
| Google login | "Google login is planned for this example." | Not wired end to end yet: no live Google start/callback runtime in example 100 server handlers. | Users/operators expect a button/path that cannot complete login. |
| Enterprise SSO | "Enterprise SSO configuration groundwork exists; runtime flow is not complete." | workspace_sso_configs and related policy direction exist, but generic OIDC/SAML runtime login flow is not fully implemented. |
Enterprise trial flows fail at callback/policy steps and appear broken. |
| Account linking | "Account linking rules are defined as target behavior." | Policy/design intent documented, but full provider identity linking and conflict resolution are not yet fully live. | Duplicate or ambiguous account assumptions leak into support docs. |
| Trust controls (state/nonce/replay, policy denials, audit detail) | "Trust controls are partially implemented and expanding." | Password/session/authz controls are live; external-auth state/nonce/replay/return-to/provider-subject policy seams are implemented, while full external-auth start/callback runtime and complete auth audit trails remain pending. | Security posture appears stronger in docs than in executable runtime. |
Operator documentation rule:
- When a feature is not wired end to end, label it
plannedorconfig-onlyand avoid "available now" language.
Paid Launch Truth Table
This matrix is the release gate for public surface claims on /home, /pricing, /signup, and auth entry.
| Surface | External claim | Status | Current truth |
|---|---|---|---|
/home |
Faster answers, fewer repeat questions, and one workspace for support, ops, and customer-facing teams. | shipped | The chat workspace, model routing, and public landing shell are live. |
/home |
Internal docs, document Q&A, and team knowledge base. | future | The example does not ship live docs search or a knowledge-base runtime yet. |
/home |
Enterprise-ready controls such as SSO, retention controls, and SLA guarantees. | config-only | Workspace auth-policy and SSO configuration storage exist, but the enterprise login runtime is still incomplete. |
/pricing |
Seat-based pricing with a clear platform fee and usage-based billing. | shipped | The pricing and billing model is usage-based and surfaced in the app. |
/pricing |
All models included. | config-only | Model availability is driven by the server catalog and provider configuration, not an unconditional blanket guarantee. |
/pricing |
Enterprise review, procurement, SSO, and contract controls. | future | Those are still launch-direction claims rather than fully delivered runtime guarantees. |
/signup |
Create a workspace and start solo before adding a team. | shipped | Local workspace signup and the quick-QA password path are live. |
/signup |
Team onboarding and collaboration expansion. | future | Team collaboration exists in the product direction, but it is not a launch guarantee for the public signup flow. |
| Auth entry | Local email/password login, password reset, and refresh-safe session restore. | shipped | Password auth, token lifecycle, and session persistence are live. |
| Auth entry | Google login and workspace SSO entry paths. | config-only | External-auth policy/config storage exists, but the public entry copy must not imply universal runtime availability. |
Pre-Launch Product Review Checklist
Use this checklist before approving public copy changes for example 100.
- Block release if
/home,/pricing,/signup, or auth entry claims unsupported auth behavior such as Google login or enterprise SSO without a shipped or config-backed row in the paid-launch truth table. - Block release if marketing copy claims live docs search, document Q&A, or team knowledge base behavior that is not backed by the current runtime.
- Block release if provider-brand or model-family copy does not match the live server catalog and the provider labels surfaced by the runtime.
- Block release if any public capability claim lacks a
shipped,config-only, orfuturestatus in the paid-launch truth table.
Demo-Readiness Code Review Checklist
Use this checklist when reviewing code changes for demo polish instead of prototype behavior.
- Verify names follow the repo rules: verb-first helpers, domain subjects, and boolean prefixes where needed.
- Verify functions stay small enough to scan quickly and that larger workflows are split into clear helpers.
- Verify files live in the right domain area and avoid one-off historical dumping grounds.
- Verify GoDoc and intent comments explain why the code exists, not just what the next line does.
- Verify diagnostics are clear, customer-safe where needed, and actionable for operators.
- Verify dead code, stale branches, and placeholder behavior are removed instead of left behind.
- Verify server, transport, store, provider, and client layers agree on names, payload shapes, and error behavior.
Variable-Name Quality Review Checklist
Use this checklist when reviewing example 100 hot paths so name cleanup stays deliberate instead of turning into style churn.
- Flag locals that hide their subject behind vague nouns like
data,info,result,state, oritemwhen a domain name is available. - Prefer verb-first helper names and domain subjects so auth, dashboard, billing, chat send, and logging paths read like actions instead of buckets.
- Keep booleans explicit with
is,has,can, orshouldso guard branches stay readable at call sites. - Rename helper inputs only when the new name clarifies the owned domain, the direction of the value, or the boundary being crossed.
- Leave stable protocol, schema, and API field names alone unless the code is wrapping them in a clearer domain-specific alias.
- Review the surrounding call chain before renaming so the new name matches the actual responsibility instead of one local line of code.
- Prefer one small name cleanup per review pass when the code path is already changing for another reason.
SQL-backed model catalog pattern
RelayDesk keeps provider and model discovery in SQLite instead of hard-coding model enums into the WASM client.
sql/store/schema.sqldefinesmodel_catalogas the source of truth for provider ID, display name, pricing, throughput, onboarding state, and capability flags such as thinking and speech support.server/app/model_catalog_store.goloads those rows into per-provider catalogs plus the global default, title-generation, and memory-extraction model picks during server startup.server/app/server.goexposes the runtime query contract throughListModelOptions, so the authenticated shell can ask the server which providers and models are currently live.
That pattern means a provider rollout is usually a data change, not a WASM rebuild: add or update rows in model_catalog, restart the server, and the next ListModelOptions refresh advertises the new catalog to connected clients.
For apps that want a zero-RPC first paint, use ui.SSRBootstrap.Data to ship the same catalog shape the RPC already returns:
{
"modelCatalog": {
"defaultModel": "gpt-5.4-mini",
"generatedAt": "2026-03-25T02:28:00-04:00",
"models": [
{
"id": "gpt-5.4-mini",
"label": "GPT-5.4 mini",
"providerId": "openai",
"providerLabel": "OpenAI",
"supportsThinking": true,
"supportsSpeech": true,
"pricing": {
"inputCostPerMillionUsd": 0.25,
"outputCostPerMillionUsd": 2.0,
"currency": "USD"
}
}
]
}
}
RelayDesk treats that bootstrap payload, or the cached chat-wizard:model-catalog local-storage entry, as last-known-good UI state only. The authenticated shell still revalidates through ListModelOptions when the gRPC session comes up so long-lived tabs converge back to the server-owned catalog without a full page reload.
Optional: one-command managed server start
Start the default managed profile directly:
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server start -json
Then inspect or restart as needed:
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server status -json
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server restart -json
go run ./tools/gwc examples .\examples\server\ai-chat-wizard\cmd\server stop -json
Source-linked UI inventory
Each row maps a visible UI surface to its primary source files. Use this as a navigation index when reading or modifying a specific region.
| Surface | Primary client files | Primary server files | SQL / data |
|---|---|---|---|
| Marketing shell | client/app/landing_shell.go, client/app/landing_sections.go |
server/app/server.go (static route) |
— |
| Auth shell | client/app/auth_shell.go, client/app/auth.go, client/app/auth_shell_flows.go |
server/app/auth_service.go, server/app/auth_workspace_policy.go, server/app/auth_google_oidc.go |
sql/store/auth/*.sql, server/app/store_auth.go |
| Workspace entrypoint | client/app/app.go, client/app/app_shell.go, client/app/routes.go |
server/app/server.go boot RPC |
— |
| Chat thread | client/app/thread.go, client/app/panel.go, client/app/stream.go |
server/app/server_conversation_rpc.go, server/app/tunnel_handler.go, server/app/authz_entitlement.go |
sql/store/chat/*.sql |
| Composer | client/app/composer.go, client/app/composer_runtime2.go, client/app/model_preferences.go |
server/app/server_conversation_rpc.go send RPC |
— |
| Canvas pane | client/app/canvas.go, client/app/canvas_workspace.go, client/app/panel.go |
server/app/server.go |
— |
| Settings panel | client/app/settings_route.go, client/app/profile.go, client/app/account_costs.go, client/app/app_shell.go |
server/app/auth_service.go, server/app/server_preferences.go, server/app/billing_formula_guard.go |
sql/store/billing/*.sql |
| Admin dashboard | client/app/dashboard_shell.go, client/app/admin_data.go, client/app/admin_customers.go, client/app/admin_workspaces.go, client/app/admin_server_tools.go |
server/app/admin_dashboard.go, server/app/admin_list_query.go, server/app/admin_billing_ops.go, server/app/admin_support_ops.go |
sql/store/admin/*.sql, sql/store/ops/*.sql |
| Admin mutations | client/app/admin_server_tools.go, client/app/admin_server_tools_render.go, client/app/admin_workspaces.go |
server/app/admin_mutation_authz.go, server/app/admin_mutation_effects.go, server/app/admin_control_ops.go, server/app/admin_provider_mutation_ops.go |
sql/store/admin/*.sql, sql/store/ops/*.sql |
| Worker markdown | client/backgroundworker/main.go, client/backgroundworker/render_tasks.go, client/app/worker_render_types.go |
— | — |
File layout
examples/server/ai-chat-wizard/
+-- docs/
| +-- HOW_EXAMPLE_100_WORKS.md
| +-- PUBLIC_ROUTE_DELIVERY.md
| +-- AUTHENTICATED_SHELL.md
| +-- CHAT_REQUEST_LIFECYCLE.md
| +-- ADMIN_DASHBOARD_SUBSYSTEM.md
| +-- DATA_LAYER.md
| +-- OBSERVABILITY_FAILURE_HANDLING.md
| +-- EXTENSION_SEAMS.md
| +-- SYSTEMS_GLOSSARY.md
| +-- BUG_REPORT_TEMPLATES.md
| +-- PERFORMANCE.md
+-- bin/
| +-- client/
| | +-- app/chat.wasm
| | +-- worker/background-worker.wasm
| +-- runtime/
| | +-- chat_history.db
| | +-- logs/server.stderr.log
| | +-- logs/server.stdout.log
| | +-- legacy-artifacts/
| +-- server/
| +-- chat-wizard.exe
| +-- chat-wizard-server.exe
+-- scripts/ # reserved for example-local helpers
+-- client/
| +-- main.go
| +-- app/
| +-- backgroundworker/
+-- cmd/
| +-- server/
| +-- seed-test-db/
+-- proto/
| +-- chat.proto
| +-- chat.pb.go
| +-- chat_grpc.pb.go
+-- server/
| +-- app/
| +-- provider/
+-- sql/
+-- testdata/
+-- README.md
+-- FLOWS.md
+-- MANUAL_SMOKE.md
+-- SCHEMA_TABLES.md
+-- DESIGN.md
+-- DOCS_MAP.md
+-- OPERATOR_RUNBOOK.md
+-- TODO.md
+-- CHANGELOG.md
Walkthrough Path Map
If you want the cleanest end-to-end explanation of the example, start here:
- Routing and boot:
client/main.go,client/app/routes.go,client/app/app_shell.go, andclient/app/route_sync.goshow how the shell decides which surface to render and how it keeps URL state stable. - Auth:
client/app/auth.go,client/app/auth_shell.go,server/app/auth_service.go,server/app/auth_workspace_policy.go,server/app/auth_identity_linking.go, andserver/app/auth_google_oidc.goshow login, policy, linking, and external-auth flow. - Chat streaming:
client/app/thread.go,client/app/stream.go,client/app/panel.go,server/app/server.go, andserver/app/tunnel_handler.goshow send, stream, reconnect, and thread-state handling. - runtime2 display region:
client/app/composer.goandclient/app/composer_runtime2.goshow the current Example 100ui.ParallelRegion(...)integration for the composer cost summary. - Billing:
client/app/settings_route.go,client/app/account_costs.go,server/app/billing_formula_guard.go,server/app/store_billing.go, andserver/app/superuser_pricing_ops.goshow usage formulas, plan state, and operator pricing controls. - Dashboard reads:
client/app/dashboard.go,client/app/admin_data.go,server/app/admin_dashboard.go,server/app/admin_list_query.go,server/app/admin_business_ops.go, andserver/app/admin_billing_ops.goshow the operator read path. - Admin mutations:
server/app/admin_mutation_authz.go,server/app/admin_mutation_effects.go,server/app/admin_control_ops.go,server/app/admin_provider_mutation_authz.go,server/app/admin_ops_action_authz.go, andserver/app/admin_chat_mutation_authz.goshow the guarded mutation path.
Dashboard slice pattern — one structure reused five times
The admin dashboard has five slices (Business, Customers, Chats, Providers, Ops). Each follows the same four-step structure. Once you can read one, you can read all five.
| Step | Client | Server |
|---|---|---|
| 1. Route activation triggers data load | dashboard_shell.go dispatches the admin-data RPC on mount |
admin_dashboard.go gate-checks role before running the query |
| 2. Typed view-model derivation | admin_data.go maps the proto response into slice-specific view structs |
admin_list_query.go builds paginated, typed results per slice |
| 3. Tile grid render | dashboard_shell.go (overview) + admin_customers.go, admin_workspaces.go, admin_server_tools.go, etc. render the per-slice grid |
— |
| 4. Role gate | dashboard_shell.go checks parseView.CanAccessAdmin / parseView.IsSuperuser before rendering admin-only sections |
admin_mutation_authz.go + admin_ops_action_authz.go enforce before every mutating RPC |
What this teaches for GWC: route-scoped async state where each tab activates its own data fetch without blocking other tabs, and role-gated regions that are controlled at both the UI node level and the server RPC level. The same shape applies to settings panels: each settings section fetches its own data on activation and submits changes through a typed RPC.
The short teaching version is:
client/main.gostarts the shell.server/app/server.goowns the gRPC entrypoint and routes.client/app/*owns the browser story.server/app/*owns the runtime truth, policy, and data access.
Framework Pattern Map
This map turns visible regions into the GWC pattern they demonstrate.
| Visible region | GWC pattern | Owning files |
|---|---|---|
| Top control bar | UI composition plus route-aware shell state | client/app/app_shell.go, client/app/model_preferences.go |
| Sidebar conversation list | Async pagination plus scroll-memory restoration | client/app/conversations.go, client/app/scroll_memory.go, client/app/sidebar.go |
| Thread body | Streaming render plus worker-assisted markdown metadata | client/app/thread.go, client/app/stream.go, client/backgroundworker/render_tasks.go |
| Composer | Controlled inputs, typed events, runtime2 display region | client/app/composer.go, client/app/composer_runtime2.go |
| Settings panels | Route-scoped panels and persisted preferences | client/app/settings_route.go, client/app/profile.go, client/app/memory_editor.go |
| Dashboard slices | Async resource load, role gate, shared table/detail patterns | client/app/dashboard_shell.go, client/app/admin_data.go, client/app/admin_customers.go |
| Canvas pane | Conditional route layer and preserved app-shell context | client/app/canvas.go, client/app/canvas_workspace.go |
Public Route Pattern Map
Public routes are server-owned for delivery and client-owned for rendering.
| Route family | Server delivery | Client render owner | Bootstrap/i18n responsibility |
|---|---|---|---|
/, /home |
server/app/server.go returns the same shell document. |
client/app/landing_shell.go, client/app/landing_sections.go |
Boot shell loads chat.wasm; embedded catalog renders first paint today. |
/pricing, /plans |
Server treats both as shell routes. | client/app/pricing_shell.go, client/app/landing_sections.go |
Pricing copy must use the billing vocabulary contract in this README and the operator runbook. |
/signup |
Server returns shell; client starts auth in signup mode. | client/app/signup_shell.go, client/app/auth_shell.go |
Auth copy comes from the embedded catalog until server-owned namespace migration completes. |
| Trust/static info routes | Server returns shell for /security, /privacy, /terms, /status, /about, /contact. |
client/app/landing_info.go |
Static trust copy should not claim unsupported runtime features. |
Paired Mini-Examples
Use these smaller examples after reading RelayDesk when you want the isolated version of a pattern.
| RelayDesk pattern | Smaller example to open next |
|---|---|
| Single-shell route selection | examples/public/browser-router, examples/public/nested-routes, examples/public/single-shell-auth |
| Server shell and hydration handoff | examples/server/server-side-rendering-bootstrap, examples/public/server-side-rendering-cache-bootstrap |
| Route loaders and async resources | examples/public/route-loaders, examples/public/use-resource, examples/public/use-fetch |
| Forms and settings-style panels | examples/public/form, examples/public/advanced-form, examples/public/use-form |
| Worker-backed client work | examples/public/worker-text-index, examples/public/browser-interop |
| Cross-tab or persisted state | examples/public/cross-tab-sync, examples/public/state-atoms, examples/public/use-atom |
| Overlays, drawers, and portals | examples/public/overlay-stack, examples/public/portal-target, examples/public/portals |
Framework-Focused Smoke Checklist
Run this after refactors that touch routing, boot, settings, dashboard, worker, or transport code.
- Route shell: direct-load
/,/home,/pricing,/signup, and/appstill explain one shell with distinct rendered regions. - Async resource: one settings panel or dashboard slice still has an obvious load, empty, denied, and success path.
- Worker path: one thread with markdown still exercises background worker render metadata or its fallback.
- Typed RPC path: login, first send, settings save, or dashboard load still has a source-linked client RPC and server handler.
- Persisted preference: provider/model/intelligence selection survives route changes and reconnects.
- Cross-tab or route state: thread route and sidebar state remain stable after reload/back-forward.
- Teaching signal: README maps still point to the current files for each visible surface.
Current Best-Practice Notes
- Prefer typed RPC/server-function style integration over ad-hoc fetches for authenticated app behavior.
- Prefer route-scoped settings/dashboard panels over one global modal switch that owns unrelated state.
- Prefer server/store-owned billing totals over client-derived billing math.
- Prefer documented GoGRPCBridge tunnel behavior over direct browser global access.
- Legacy or compatibility-only helpers should stay out of the primary walkthrough unless a section explicitly labels them as historical.
Route-To-Code Map
| Route family | Rendering owner | Route normalization | Data loading | Server delivery |
|---|---|---|---|---|
Public marketing (/, /home) |
client/app/landing_shell.go |
client/app/routes.go |
Embedded catalog today | server/app/server.go |
Pricing (/pricing, /plans) |
client/app/pricing_shell.go |
client/app/routes.go |
Billing/pricing copy contract | server/app/server.go |
Auth (/signup, login entry) |
client/app/auth_shell.go, client/app/signup_shell.go |
client/app/routes.go |
Login, Signup, GetSession RPCs |
server/app/server.go, server/app/auth_service.go |
App shell (/app) |
client/app/app_shell.go |
client/app/route_sync.go |
Session, profile, catalog, conversations | server/app/server.go, /socket tunnel |
Thread (/app/thread/:publicID) |
client/app/thread.go, client/app/panel.go |
client/app/route_sync.go |
LoadConversation, Send, ListConversations |
server/app/server_conversation_rpc.go |
Settings (/app/settings*) |
client/app/settings_route.go, client/app/profile.go |
client/app/settings_route.go |
Profile, memory, billing, preference RPCs | server/app/server_preferences.go, server/app/store_billing.go |
Dashboard (/app/dashboard*) |
client/app/dashboard_shell.go, client/app/admin_*.go |
client/app/routes.go |
Admin/superuser list, slice, and diagnostics RPCs | server/app/admin_*.go |
Hot-Path Data Flow Traces
| Flow | UI event | Client owner | Transport call | Server/store owner | UI update |
|---|---|---|---|---|---|
| Login | Submit auth form | client/app/auth.go |
Login, then GetSession |
server/app/auth_service.go, server/app/store_auth.go |
Auth shell becomes workspace shell. |
| First chat send | Composer submit | client/app/composer.go, client/app/stream.go |
Send stream |
server/app/server_conversation_rpc.go, provider runtime, chat SQL |
Thread appends deltas and normalizes route. |
| Settings save | Save in settings panel | client/app/profile.go, client/app/memory_editor.go |
Profile/preference RPC | server/app/server_preferences.go, settings SQL |
Panel shows success or typed unavailable/error state. |
| Dashboard load | Dashboard route activation | client/app/dashboard_shell.go, client/app/admin_data.go |
Admin dashboard/slice RPC | server/app/admin_dashboard.go, server/app/admin_list_query.go |
Slice grid/table renders role-scoped data. |
| Provider switch | Provider/model select change | client/app/model_preferences.go |
Preference persistence RPC when authenticated | server/app/server_preferences.go, provider catalog |
Toolbar and composer use repaired selected model. |
| Speech playback | Speech/provider control | client/app/settings_route.go, speech helpers |
Settings/preference RPCs | Provider/catalog and user preferences | Speech controls reflect selected provider and fallback policy. |
SQL Ownership Map
| Area | Owns | Query/store files |
|---|---|---|
| Auth/session | Users, sessions, external identities, token versioning. | server/app/store_auth.go, sql/store/auth/*.sql |
| Conversations | Conversations, messages, public IDs, usage event links. | server/app/server_conversation_rpc.go, sql/store/chat/*.sql |
| Billing/usage | Customers, subscriptions, invoices, line items, usage totals, overrides, quotas. | server/app/store_billing.go, server/app/admin_business_ops.go, sql/store/billing/*.sql |
| Admin control plane | Dashboard lists, scoped slices, customer/workspace/support views. | server/app/admin_list_query.go, server/app/admin_customers.go, sql/store/admin/*.sql |
| Ops/reliability | Incidents, SLOs, jobs, webhooks, diagnostics, server-tool policy. | server/app/admin_ops_*.go, server/app/store_server_tool_policy.go, sql/store/ops/*.sql |
| Provider/cost guardrails | Model catalog, provider health, routing, limits. | server/app/model_catalog_store.go, server/provider/*, provider/ops SQL |
Where To Put New Code
| New work | Put it here first | Add or update |
|---|---|---|
| Public route | client/app/landing_* or specific route shell file |
client/app/routes.go, server shell route allowlist, README route map |
| Auth or session RPC | server/app/auth_service.go and auth store files |
Proto contract, auth tests, route/auth docs |
| Chat feature | client/app/thread.go, client/app/stream.go, server/app/server_conversation_rpc.go |
Chat SQL/store tests and browser smoke |
| Worker task | client/backgroundworker/ plus client/app/worker_render_types.go |
Worker dispatch tests and fallback behavior |
| Settings panel | client/app/settings_route.go plus focused panel file |
Preference RPC/store path, i18n keys, settings smoke |
| SQL query | sql/store/<area>/ and the owning server/app/store_*.go |
SQL inventory/loader test and source map row |
| Dashboard slice | client/app/admin_*.go, server/app/admin_*.go |
Role-gate tests, empty/denied/error states, dashboard pattern map |
| Operator docs | OPERATOR_RUNBOOK.md, MANUAL_SMOKE.md, or docs/* |
DOCS_MAP.md if it becomes a new entry point |
Source-Map Verification Checklist
Before calling a visible Example 100 feature documented:
- Every new route family has a route-to-code row.
- Every new visible surface has a source-linked UI inventory row.
- Every new RPC used by a visible surface appears in the hot-path trace or the relevant route map.
- Every new SQL/query area has an ownership row or an existing row that clearly covers it.
- Every new operator workflow points to either
OPERATOR_RUNBOOK.md,MANUAL_SMOKE.md, or a dedicated docs file. - The paired mini-example list still gives readers a smaller follow-up example for any newly promoted GWC pattern.
Repo Structure Map
This is the purpose-first view of example 100:
| Purpose | Primary folders | Representative files |
|---|---|---|
| Auth | client/app/, server/app/, sql/store/ |
client/app/auth.go, client/app/auth_shell.go, server/app/auth_service.go, server/app/auth_workspace_policy.go, server/app/auth_identity_linking.go, server/app/auth_google_oidc.go, server/app/store_auth.go, server/app/store_auth_external.go |
| Chat | client/app/, server/app/ |
client/app/thread.go, client/app/stream.go, client/app/composer.go, client/app/panel.go, server/app/funnel_first_chat.go, server/app/tunnel_handler.go, server/app/authz_entitlement.go |
| Dashboard | client/app/, server/app/ |
client/app/dashboard.go, client/app/admin_data.go, client/app/admin_customers.go, server/app/admin_dashboard.go, server/app/admin_list_query.go, server/app/admin_business_ops.go, server/app/admin_billing_ops.go |
| Canvas | client/app/ |
client/app/canvas.go, client/app/canvas_workspace.go, client/app/panel.go, client/app/worker_render_types.go |
| Billing | client/app/, server/app/, sql/store/ |
client/app/account_costs.go, server/app/billing_formula_guard.go, server/app/store_billing.go, server/app/superuser_pricing_ops.go, sql/store/*.sql billing queries |
| SQL | sql/store/ |
sql/store/schema.sql, query files grouped by auth, billing, admin, growth, and ops concerns |
| Runtime assets | bin/ |
bin/runtime/, bin/client/, bin/server/, managed logs and generated artifacts |
| Docs | repo root + docs/ |
README.md, FLOWS.md, MANUAL_SMOKE.md, SCHEMA_TABLES.md, DESIGN.md, DOCS_MAP.md, OPERATOR_RUNBOOK.md, docs/BUG_REPORT_TEMPLATES.md, docs/PERFORMANCE.md |
Comment And GoDoc Style Checklist
Use this style guide when adding or polishing comments in example 100.
- Keep GoDoc short, direct, and purpose-first.
- Start each GoDoc comment with the function name it documents.
- Use package docs to explain why the package exists and what it owns.
- Use file headers only when they add useful orientation for a reader.
- Use intent comments for tricky blocks, invariants, and cross-layer boundaries.
- Avoid mechanics comments that just repeat the next line of code.
- Keep the tone consistent across client, server, store, provider, and SQL-adjacent helpers.
Repo layout rules
- Keep primary entry docs at root (
README.md,FLOWS.md,MANUAL_SMOKE.md,SCHEMA_TABLES.md,TODO.md,CHANGELOG.md). - Keep secondary/supporting docs under
docs/(for exampledocs/BUG_REPORT_TEMPLATES.md,docs/PERFORMANCE.md). - Keep runtime state and logs under
bin/runtime/(chat_history.db,logs/, managed state files). - Keep generated binaries and build artifacts under
bin/subfolders (bin/client,bin/server,bin/runtime/legacy-artifacts). - Keep helper scripts under
scripts/only; avoid scattering executable helpers at root. - Keep source code under
client/,server/,cmd/,proto/,sql/, andinternal/.
Regenerating proto stubs
cd examples/server/ai-chat-wizard
protoc --go_out=. --go_opt=paths=source_relative `
--go-grpc_out=. --go-grpc_opt=paths=source_relative `
proto/chat.proto
RelayDesk is the repo's reference implementation for runtime AI provider switching. It demonstrates:
- a server-backed runtime model catalog loaded into the authenticated shell at startup
- provider and model switching without a page reload
- per-user selected-model persistence through the chat RPC surface
- live cross-tab synchronization of the active provider/model selection
- local stub-provider workflows so provider switching stays testable without real upstream keys
Benchmarks
Use the server benchmark suite to get a quick read on store cost and synthetic active-chat pressure by clients-per-core:
go test ./examples/server/ai-chat-wizard/server/app -run '^$' -bench 'Benchmark(StoreCorePaths|SendClientsPerCore)' -benchmem
BenchmarkSendClientsPerCore uses a synthetic provider and reports clients/core sub-benchmarks at 1, 2, and 4.
For an SLA sweep that pins the server to 1..8 cores and increases concurrent clients until the p95 request latency breaches the target:
$env:CHAT_WIZARD_BENCH_SLA_MS = "100"
go test ./examples/server/ai-chat-wizard/server/app -run TestSendSLASweep -v
Optional knobs:
CHAT_WIZARD_BENCH_MAX_CORESdefault8CHAT_WIZARD_BENCH_MAX_CLIENTSdefault64CHAT_WIZARD_BENCH_BURST_RUNSdefault3CHAT_WIZARD_BENCH_PREDICT_CORESdefault32
The sweep logs:
- measured
max_clients_under_slafor each core count clients_per_corescaling_vs_1_core- cumulative rollup totals across core levels
- a linear-regression projection for the requested prediction core count using uncensored cumulative rollup points
For a fuller explanation of the output fields, interpretation, and latest measured sample data, see PERFORMANCE.md.
Key packages
| Package | Role |
|---|---|
github.com/monstercameron/GoGRPCBridge/pkg/grpctunnel |
WebSocket↔gRPC tunnel (server + WASM client) |
github.com/monstercameron/GoWebComponents/ui |
Hooks-based WASM UI (state, effects, events) |
github.com/monstercameron/GoWebComponents/html |
Typed HTML node builders |
google.golang.org/grpc |
gRPC runtime |
google.golang.org/protobuf |
Protobuf serialisation |
The GoGRPCBridge module lives at third_party/GoGRPCBridge (git submodule) and is
referenced via a replace directive in the root go.mod.
Environment variables
| Variable | Default | Description |
|---|---|---|
LISTEN_ADDR |
127.0.0.1:8095 |
Server listen address |
CHAT_ENV |
development |
Runtime environment label. production enforces stricter auth-secret startup validation. |
CHAT_AUTH_SECRET |
(empty) | Primary JWT signing secret. Required when CHAT_ENV=production unless insecure fallback is explicitly enabled. |
CHAT_ALLOW_INSECURE_AUTH_FALLBACK |
false |
When truthy (1/true/yes/on), permits startup with development fallback auth secret even without CHAT_AUTH_SECRET. |
CHAT_AUTH_SIGNING_KEYS |
(optional) | Comma-separated key ring (kid=secret,kid2=secret2) used for JWT verification/rotation in addition to the primary secret. |
CHAT_AUTH_ACTIVE_KID |
primary |
Active JWT signing key ID. If missing or unknown, server falls back to the first available configured key ID. |
CHAT_MODEL |
(optional) | Default model override for runtime startup. |
OPENAI_MODEL |
(optional) | Legacy fallback default model used only when CHAT_MODEL is empty. |
OPENAI_API_KEY |
(optional) | OpenAI provider key. Required unless CHAT_PROVIDER_STUBS includes openai/all. |
ANTHROPIC_API_KEY |
(optional) | Anthropic provider key. Required unless CHAT_PROVIDER_STUBS includes anthropic/all. |
CEREBRAS_API_KEY |
(optional) | Cerebras provider key. Required unless CHAT_PROVIDER_STUBS includes cerebras/all. |
CHAT_PROVIDER_STUBS |
(empty) | Comma-separated stub provider list (openai,anthropic,cerebras) or all for full local stubs. |
CHAT_DB_PATH |
examples/server/ai-chat-wizard/bin/runtime/chat_history.db |
SQLite database path for auth and conversation persistence |
CHAT_USAGE_PREMIUM_PERCENT |
5 |
Service-premium percent added on top of raw usage costs. Invalid/negative values fall back to default; values above 1000 are clamped. |
Usage-premium behavior:
- Parsed once at server startup and applied to billing total calculations.
- Exposed to the boot payload as
window.__relaydesk_usage_premium_percentfor client-side display parity.
What this example demonstrates
- gRPC over WebSocket in the browser — no HTTP polling, no custom wire format
- Server-streaming RPC — tokens flow from OpenAI → gRPC server → browser in real time
- Go WASM UI — the entire frontend is Go; no JavaScript application code
- Hooks pattern for async state —
UseState,UseEffect,UseRef, goroutines - Companion submodule pattern —
GoGRPCBridgeconsumed viathird_party/+go.mod replace
Directories
¶
| Path | Synopsis |
|---|---|
|
backgroundworker
command
|
|
|
cachecore
Package cachecore provides reusable client-side cache and outbox mechanics.
|
Package cachecore provides reusable client-side cache and outbox mechanics. |
|
catalog
Package catalog provides the WASM-side emergency fallback localization bundle.
|
Package catalog provides the WASM-side emergency fallback localization bundle. |
|
cmd
|
|
|
build-client
command
|
|
|
seed-test-db
command
|
|
|
server
command
|
|
|
internal
|
|
|
app
auth_rpc.go owns the session bootstrap and auth RPC cluster.
|
auth_rpc.go owns the session bootstrap and auth RPC cluster. |