Arrowstack Character
Mobile-first Vue 3 / TypeScript character-sheet PWA. Pinia manages state, Dexie stores characters in IndexedDB, Zod validates saves and JSON imports. A small Express gateway protects the application with a confidential Keycloak OIDC client using authorization code flow, PKCE, state and nonce verification.
v0.2
Create/edit multiple characters, abilities, skill/expertise and save proficiency, HP/temp HP, armour class, speed, manually entered attacks, spellbook/preparation, generic resources, rest preview, inventory, notes, d20 rolls, account-separated local saves, JSON import/export. No copyrighted spell or rule compendium is bundled. Account-backed PostgreSQL storage and offline-first sync are available. Existing local characters upload after sign-in; conflicts retain offline edits as a new local copy instead of overwriting remote changes.
Short rest resets declared short-rest resources. Long rest also resets long-rest resources, restores HP and clears temporary HP. Hit dice and class-specific rest rules are manual. These are configurable sheet operations, not a complete edition-specific rules engine.
Authentication and offline behavior
Use existing Arrowstack accounts in the dev realm. Any authenticated realm user can use the app; no control-plane role is required. Keycloak tokens and the confidential client secret stay server-side. The encrypted 12-hour session uses a Secure, HttpOnly, SameSite=Lax, host-only cookie. Protected HTML requires a session; unauthenticated /api/session returns 401. Static JS/CSS and PWA metadata are public, but contain no character data or secrets.
After successful sign-in, the worker caches the app shell and immutable build assets for offline use. The browser retains only the last subject/name/expiry, not OIDC tokens. Browser offline access expires with the session after at most 12 hours. Revocation cannot be checked while offline; reconnecting rechecks the gateway session. Character storage is namespaced by Keycloak subject, not encrypted at rest. Treat the device as trusted. Sign-out removes offline identity and cached shell while preserving per-account character backups. Sign-out ends the app session; the Arrowstack SSO session may still exist.
Clearing site data removes unsynced device saves. Account-synced sheets download after sign-in. Export JSON backups before clearing browser storage.
Build and deploy
npm ci, npm test, npm run build. Docker executes tests and type-check/build, then runs as an unprivileged node user on 8080. No Gitea Actions builder is required.
Runtime configuration: DATABASE_URL (injected by Arrowstack tenant provisioning), APP_ORIGIN=https://dnd.arrowstack.dev, KEYCLOAK_ISSUER, KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET. Provision confidential client dnd-character with standard authorization code flow only, exact redirect https://dnd.arrowstack.dev/auth/callback, PKCE S256. Use Arrowstack's secret handoff to inject credentials directly into Coolify. /health exposes status/version/revision, never secrets. Canonical source is private in Admin/dnd-character-pwa. For now Coolify builds the public Admin/dnd-character-deploy mirror main branch over HTTPS; runtime secrets are never committed. See deployment.md for resource IDs and release steps. Redeploy a prior mirror commit through Coolify to roll back; database tables initialize additively at startup. Keep the database when rolling back; old app versions cannot expose synced sheets.
Local production smoke test needs the same HTTPS origin and Keycloak configuration. Do not add an authentication bypass to production. Vite development uses its proxy for /auth and /api.
MCP
Connect a Streamable HTTP client to https://dnd.arrowstack.dev/mcp. Discovery is at /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server. Public/confidential OAuth client registration is supported at /register; authorization code with mandatory S256 PKCE, resource binding and exact registered redirects is handled by the MCP SDK. Keycloak sign-in uses the existing confidential app client, followed by a per-client consent screen. HTTPS redirects or HTTP loopback redirects only.
Scopes: characters:read (default) and characters:write (explicit consent). Tokens are encrypted, audience-bound, short-lived and checked against persisted grants. Refresh tokens rotate; authorization codes and refresh tokens are one-time use. Grants expire with the 12-hour browser sign-in period. /revoke revokes a client grant; browser sign-out ends the browser session and does not revoke previously approved MCP grants. MCP access tokens cannot act as browser sessions.
Tools: list_characters, get_character, create_character, update_character, change_hit_points, apply_rest, set_resource, delete_character, roll_dice. Resource: character://collection. Writes require an expected revision. Read a sheet again after a conflict; do not retry stale writes. Character rules remain manually configured, without a bundled spell/rule compendium.
PWA sync runs after sign-in, after edits, on reconnect and every 30 seconds. Local IndexedDB remains available offline; server data is account-scoped by Keycloak subject. Sync stores deletion tombstones to prevent another device reviving deleted sheets. Stale offline modifications are preserved as a new local copy; deleting a stale sheet retains the newer account version. Database initialization is additive; do not remove tables or character data during rollback.