Skip to content

feat(web): scaffold apps/web — SolidJS live sheet on Convex#31

Merged
StreamDemon merged 3 commits into
mainfrom
feat/web-scaffold
Jul 4, 2026
Merged

feat(web): scaffold apps/web — SolidJS live sheet on Convex#31
StreamDemon merged 3 commits into
mainfrom
feat/web-scaffold

Conversation

@StreamDemon

@StreamDemon StreamDemon commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Closes #8. M2 kickoff: the SolidJS web app, wired end to end to the Convex backend, with /characters/:id rendering the full derived sheet live.

What's here

apps/web (SolidJS + Vite+ + Tailwind v4, in the pnpm workspace with vp run web#dev/#check/#test/#build):

  • Route architecture per the issue — marketing shell for / (placeholder landing) and app shell for /characters..., split from day one so the landing page and future VTT (/table/:id) each grow their own chrome.
  • Hand-rolled Convex bindings (src/lib/convex.ts, ~50 lines + tests): createQuery maps a ConvexClient.onUpdate subscription onto a Solid signal — reactive args (e.g. route params) resubscribe and reset to loading; onError is threaded through because an errored subscription otherwise never resolves and the page would hang on "Loading…" silently (caught during browser verification). Plus createMutation/createAction helpers. 6 unit tests against a fake client (subscribe/update/null/error/resubscribe/dispose).
  • /characters — live roster + "Create Vesper" dev seed button (same fixture the rules/backend tests pin) hitting characters.create.
  • /characters/:id — the proof of life: identity, attributes + derived attribute bonuses, combat profile, vitals (ranges until rolled), saves, skills, spells, and a Roll vitals button hitting characters.rollVitals with results streaming back over the subscription.
  • VITE_CONVEX_URL committed in apps/web/.env pointing at the anonymous local deployment (not a secret; dev works out of the box).

Backend:

  • New characters.list roster query (identity fields only, newest first, bounded with .take(50)) + test.
  • package.json exports for ./api (generated function refs) and ./dataModel (types) so the app gets typed end to end.

Housekeeping:

  • CI now runs check/test for backend and web alongside rules — backend was never wired into CI when feat(backend): Convex backend — characters table + deriveSheet query #29 merged.
  • convex/_generated committed as convex dev regenerates it (prettier-style), which ends the recurring dirty-tree noise on main.
  • README updated to the three-package layout + dev-server commands.

Verification

Verified in a real browser against the local anonymous deployment: landing → /characters → seed Vesper → sheet renders every section with the expected fixture numbers (Native Tongue 98%, Wilderness Survival 44%, P.P.E. 64–214, spell strength 12) → Roll vitals pins concrete values live (no reload) → invalid-id route shows the error instead of hanging. Zero console errors on the happy path.

105/105 tests across the workspace; vp check and the production build are clean.

Out of scope (per the issue): builder wizard (#9), sheet visual design (#10), landing content, VTT.


Summary by cubic

Scaffolded a SolidJS web app on convex with a live character sheet and simple roster, delivering the end-to-end flow in #8. Also adds Solid–Convex bindings, a backend roster query, CI for web/backend, and moves query error state into the bindings for reliable recovery.

  • New Features

    • apps/web (SolidJS + Vite + Tailwind): routes for / (placeholder), /characters (roster + “Create Vesper” seed), and /characters/:id (derived sheet with live updates and “Roll vitals”).
    • Route architecture per Scaffold the web app (apps/web) #8: separate marketing and app shells so landing and future /table/:id can evolve independently.
    • Hand-rolled Solid bindings for convex (createQuery, createMutation, createAction): reactive args resubscribe; errors surface instead of hanging; covered by unit tests.
    • Backend: characters.list roster query (identity only, newest first, capped at 50); package exports ./api and ./dataModel for typed imports in @riftforge/backend.
    • Dev setup: committed VITE_CONVEX_URL for the anonymous local backend so vp run web#dev works out of the box; committed convex/_generated to stop churn.
    • CI: runs check/test for rules, backend, and web; builds web.
  • Bug Fixes

    • Bindings: createQuery now returns { data, error } and clears stale errors on resubscription and on successful updates; pages drop manual onError wiring.
    • Roster and sheet: surface errors (seed create failures and rollVitals rejections) instead of silent hangs.
    • CI: exclude packages/backend/convex/_generated/** from formatter to stop generator vs. fmt conflicts.
    • Docs: clarify that rules#check only checks the rules package.

Written for commit 6b7efec. Summary will update on new commits.

Review in cubic

M2 kickoff, per #8. SolidJS + Vite+ + Tailwind app wired to the local
Convex deployment, with the marketing/app layout split so the landing
page and future VTT (/table/:id) can grow their own chrome.

- apps/web: routes / (placeholder landing), /characters (roster +
  "Create Vesper" dev seed), /characters/:id (full derived sheet:
  identity, attributes + bonuses, combat, vitals, saves, skills,
  spells, with a Roll Vitals button pinning concrete rolls live)
- lib/convex.ts: hand-rolled Solid bindings over ConvexClient —
  createQuery maps a live-query subscription onto a signal (reactive
  args resubscribe; onError surfaces subscription failures instead of
  hanging on "Loading"), createMutation/createAction helpers; 6 unit
  tests against a fake client
- backend: characters.list roster query (newest first, bounded at 50)
  + test; package exports for the generated api (and dataModel types)
  so the app imports typed function refs
- CI now checks/tests backend and web alongside rules (backend was
  never wired in); README updated to the three-package layout
- convex/_generated committed as `convex dev` regenerates it, ending
  the fmt flip-flop noise

105/105 tests, vp check clean, prod build clean. Verified in a real
browser against the anonymous local deployment: seed -> live sheet ->
roll vitals streaming in, plus the invalid-id error path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Rv4wV7c73SkfkHbFcGygu

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 27 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/web/src/pages/characters.tsx
Comment thread apps/web/src/pages/characters.tsx
Comment thread apps/web/src/pages/character-sheet.tsx Outdated
Comment thread README.md Outdated
- root vite.config fmt.ignorePatterns excludes convex/_generated: the
  files are committed as `convex dev` regenerates them (prettier
  style), so oxfmt checking them just fights the code generator. This
  was the CI failure — the root `vp check` I validated with resolves
  ignores differently than the per-package `vp run backend#check` CI
  runs. (Formatting is root-configured in Vite+ monorepos; a package-
  level fmt block doesn't apply.)
- /characters: wire onError into the roster query so a failed
  subscription surfaces instead of hanging on "Loading…" (same fix the
  sheet page already had), and catch create-seed rejections into a
  visible error instead of an unhandled rejection
- /characters/:id: catch rollVitals rejections into a visible error
- README: `rules#check` comment no longer implies it checks the whole
  workspace

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Rv4wV7c73SkfkHbFcGygu

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/web/src/pages/characters.tsx Outdated
Cubic follow-up: a stale query error could mask recovered data because
nothing cleared the page-level error signal when the subscription
pushed a good value again (and the sheet page had the same latent bug).

Instead of patching each page, the bindings now own both signals:
createQuery returns { data, error }, clears the error on every
successful update and on resubscription, and the pages drop their
hand-rolled error wiring. New test pins error -> recovery clears it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Rv4wV7c73SkfkHbFcGygu

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 4 files (changes from recent commits).

Requires human review: This PR introduces substantial new code and infrastructure with a large blast radius, so it should be reviewed by a human.

Re-trigger cubic

@StreamDemon StreamDemon merged commit 0698bac into main Jul 4, 2026
2 checks passed
@StreamDemon StreamDemon deleted the feat/web-scaffold branch July 4, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scaffold the web app (apps/web)

1 participant