Skip to content

Web edition foundation: Phase 1 core extraction + scaffolding#27

Open
forever8896 wants to merge 13 commits into
masterfrom
feat-web-foundation
Open

Web edition foundation: Phase 1 core extraction + scaffolding#27
forever8896 wants to merge 13 commits into
masterfrom
feat-web-foundation

Conversation

@forever8896

Copy link
Copy Markdown
Owner

Built by a multi-agent workflow, independently verified: cargo test 30/0, cargo build green, core-purity guard passes.

Phase 1 — core extraction (desktop behavior unchanged):

  • New starchild_core crate — pure engine, zero tauri/sqlite/tokio/reqwest (CI-enforced).
  • Storage + InferenceSender traits; desktop impls (SQLite + reqwest AiClient); e2ee net split out; old paths shimmed; lib.rs slimmed.

Web foundation (additive, doesn't touch desktop):

  • web/ Vite shell + src/platform/ seam (the no-Tauri-in-components contract).
  • Encrypted versioned .starchild export/import (Argon2id+AES-GCM) + 6 tests.
  • Playwright harness + no-logging trial proxy.

⚠️ Before merge: please run npm run tauri dev and confirm the desktop app behaves identically — that's the one gate no agent can self-verify. Then this merges and Phase 2 (WASM compile) follows.

🤖 Built with Claude Code multi-agent workflow

forever8896 and others added 13 commits June 26, 2026 00:41
…n scope

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for the web edition (docs/web-app-prd.md), built by a multi-agent
workflow and independently verified (cargo test 30/0, cargo build green).

Phase 1 — core extraction (desktop behavior unchanged):
- New ownerless `starchild_core` crate: pure engine logic (types, ModelRouter,
  PromptBuilder, PhaseDetector, game state with the clock injected, knowing,
  memory recall, e2ee crypto) with NO tauri/sqlite/tokio/reqwest.
- `Storage` + `InferenceSender` traits in core; desktop impls = SQLite (db) and
  the reqwest AiClient; e2ee network split into e2ee_net.rs.
- Old module paths kept as re-export shims; lib.rs slimmed. CI guard
  (scripts/check-core-purity.sh, wired into build.yml) fails if core/ gains a
  Tauri/SQLite/tokio/reqwest dep.

Web foundation (all-new, additive — does not touch the desktop app):
- web/ Vite+React shell + platform bootstrapper; src/platform/ seam
  (interface + desktop/web impls) so components never import Tauri directly.
- web/src/export.ts: encrypted, versioned .starchild export/import
  (Argon2id + AES-256-GCM, schema migrations) + 6 passing vitest tests.
- tests/web-e2e/ Playwright harness; web/api/proxy.ts no-logging trial proxy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- starchild_core gains a wasm32 cdylib target with wasm-bindgen +
  serde-wasm-bindgen (all wasm deps target-gated; native build + purity guard
  untouched). Every core dep — incl. k256/aes-gcm/chrono/rand — compiles to wasm.
- #[cfg(wasm)] wasm.rs exposes the PURE functions only (detect_phase, route_model,
  build_prompt, tick_game_state/new_game_state with injected clock, postprocess) —
  no Storage/InferenceSender, no networking.
- web/src/wasm-bridge.ts: typed loadCore() over the wasm-pack output; build:wasm
  script. Smoke test loads the real .wasm and runs the pure fns (5/5).
- Verified: wasm-pack build green, src-tauri cargo check clean, core purity OK.
  (wasm output under web/src/wasm/ is gitignored — regenerate via build:wasm.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The web edition now runs the core experience locally in a browser tab.

- src/platform/web.ts fully implements the conversation path: loadCore (WASM) →
  tick creature → persist user msg → detectPhase + buildPrompt → stream tokens
  via venice-proxy → postprocess → persist assistant msg + ticked state. Plus
  getMessages, generateFirstMessage, getState, hasInferenceKey (trial/BYOK),
  onboarding, settings, and export/import (encrypted .starchild round-trip).
- web/src/storage.ts: IndexedDB adapter (messages, creature state w/ decay clock,
  settings, quests, transactional replaceAll for import).
- web/src/venice-proxy.ts: browser inference client — trial (/api/proxy) + BYOK,
  streaming SSE → async iterable, graceful rest-mode handling.
- Shared components (Onboarding, ChatWindow, StarchildAvatar, ActiveQuest)
  refactored off direct Tauri onto usePlatform() — zero @tauri-apps imports
  remain. desktop.ts extended with the SAME invokes (behavior unchanged);
  src/main.tsx now wraps App in the shared PlatformProvider.
- Web shell mounts the real components + the clay design system.

Web-only limits (desktop unchanged): TTS/voice hidden, quests read-only,
lightweight creature model. Verified: root+web tsc clean, vite build green,
cargo check clean, web vitest 11/11, Playwright onboarding spec green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…port UX

The web Starchild is now usable + portable. (Desktop backend untouched.)

- Vision Tree on web: SkillTree refactored off direct Tauri onto usePlatform()
  (reuses existing getQuests/getSetting/subscribe — no platform-interface change,
  desktop wiring byte-for-byte unchanged). Mounted in the shell via a "Your
  Journey" toggle; real data where available, graceful empty constellation else.
- BYOK: web/src/Settings.tsx lets a user paste/save their Venice key (IndexedDB
  venice_api_key); web.ts resolveInference() picks BYOK when a key is set, else
  the bounded trial — so live replies stream in-browser without the proxy.
- Export/import UX: web/src/DataSettings.tsx — passphrase-encrypted .starchild
  download + import (transactional replaceAll → reload), with clear local-first
  copy + no-recovery warning. storage-roundtrip vitest proves the cycle.

Verified: root+web tsc clean, vite build green, cargo check clean, vitest 13/13,
Playwright onboarding green. (Desktop GUI still wants a human run — SkillTree is
a shared component.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The web/ app bundled its own React 19 while the shared components + store
(imported from ../../src/) resolved React/zustand from the repo-root node_modules
— two React copies → "Invalid hook call" → blank page at runtime. tsc/vite
build/vitest can't catch it, and the Playwright spec was pointed at the ROOT
shell (:5173), not the web/ shell (:5174), so the real render was never exercised.

Fix: web/vite.config.ts dedupes + aliases react/react-dom to the root copy so the
whole tree uses one React instance. Verified headlessly: #root renders the
onboarding screen, no console errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Quests (the gamified core) now work end-to-end in the browser, and the test
harness finally guards the actual web shell.

Quest loop (web, via the platform seam — no Platform interface change, desktop
invokes untouched):
- OFFER: web.ts sendMessage runs the same quest-marker check as desktop and
  emits 'quest-offered'; App.tsx surfaces the shared accept/decline UI.
- ACCEPT: extractOfferedQuest mirrors desktop's extract-from-conversation (LLM
  JSON + clamp via quests.ts, heuristic fallback so it never dead-ends offline);
  persists an active quest to IndexedDB.
- COMPLETE: two-turn proof handshake → mark done, award XP + feed the creature
  exactly as core game.rs, emit celebration → chat HUD + ActiveQuest + Vision
  Tree all fire. The tree populates from real quests now.

Test harness (closes the blank-page gap):
- playwright.config.ts now boots the real web/ shell (:5174), not the legacy root
  shell. onboarding.spec asserts the screen RENDERS real copy and FAILS on any
  pageerror / React invalid-hook-call. (Agent proved the guard catches both.)

Verified independently: root+web tsc clean, vite build green, cargo check clean,
vitest 21/21, web-shell Playwright 2/2, and a clean-cache headless load of :5174
renders error-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Phase-5 web work had reimplemented quest detection/extraction, the awakening
message, and XP/feed math in TypeScript — drift that would force editing logic
twice. This consolidates all of it into starchild_core (one source), exposes it
via WASM, and refactors BOTH shells to CALL core. Improving the core Starchild,
the skill tree, or quest logic now reaches web AND desktop automatically.

Moved into core (pure, native + WASM, unit-tested):
- quest.rs: is_quest_offer + markers, extraction system/prompt, parse, normalize
  (+clamp), offline fallback; messages.rs: awakening_message; game:
  quest_complete_reward + mood_for_hunger; recall.rs: rank_memories (keyword +
  recency, no clock); knowing.rs: from_facts, extraction input, parse_facts.
- Surfaced through wasm.rs + web/src/wasm-bridge.ts.

Both shells refactored to call core (duplication deleted):
- Desktop (lib.rs/knowing) now calls core for quest extraction, first message,
  knowing profile, memory extraction — behavior-preserving (cargo test 30/0).
- Web (quests.ts/web.ts) deletes the TS reimplementations and calls core; this
  also fixed two silent web-only divergences (mood thresholds, prompt whitespace).

Closed a hidden gap: web had NO memory recall and NO knowing profile (empty
prompt slots). Both now work on web via shared core — the web Starchild remembers
you across sessions and builds the 7-dimension understanding, same as desktop.

Residual (flagged): recall BACKEND differs (desktop FTS5 vs web core-ranker) but
the ranking logic is shared in core and feeds the same prompt slot; two desktop
sites keep a narrower 2-marker quest check (pre-existing quirk, left to preserve
desktop behavior).

Verified: cargo test core 80 + desktop 30, purity OK, root+web tsc, vite build,
web vitest 20, web-shell Playwright 2/2, clean-cache headless render error-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + Phase 7 polish

CRITICAL FIX: every real conversation send on web threw a serde error — web.ts
forwarded the live creature's fractional (decayed) stats into core build_prompt,
whose StarchildState is u32. Rounded the stats before the WASM boundary. Verified
headlessly with a real send under natural decay: reply renders, zero serde errors.
The quest-loop E2E had masked this by pinning whole-number stats; it now pins
FRACTIONAL stats so a regression re-breaks the test.

Phase 7 polish:
- Bundle code-split (web/vite.config.ts manualChunks + lazy SkillTree/DataSettings/
  Settings): main entry 776 kB → 113 kB; vendors split; >500 kB warning gone.
- Full quest-loop Playwright E2E (offer → accept → Vision Tree → proof → complete,
  inference mocked) — the heart of the experience is now guarded in CI.
- Marker consistency: both desktop sites now call the shared core quest::is_quest_offer
  (3-marker), so offer detection is identical across web + desktop.

Verified: cargo test 30/0, core green, purity OK, root+web tsc, vite build (lean),
web vitest 20, web-shell Playwright 3/3 incl. quest-loop, headless render error-free,
and a real fractional-decay send works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e key

PRD §6 fleshed into three concrete, buildable tiers (resolution: BYOK → locked
→ demo): sponsored demo (founder-funded proxy + a dev shim), lock $STARCHILD →
minted private key (the token utility, ties to docs/inference-access-spec.md),
and BYOK. Added to the build phases + appendix.

Scaffolding (web-only, low-risk):
- web/dev-proxy.ts + wired into vite.config.ts — Vite dev middleware serves
  /api/proxy from VENICE_TRIAL_KEY so the sponsored demo works in `npm run dev`
  WITHOUT a key (no content logging, cheap model, token cap); graceful rest-mode
  when unconfigured. Verified responding.
- web/src/access.ts — lock $STARCHILD → claim a minted Venice key. The claim POST
  to the token-site /api/access/claim is wired; connect/lock/sign are stubbed
  pending viem + the StarchildLock deploy address. The minted key drops into the
  same local slot as BYOK (E2EE; backend never in the conversation path).

Remaining to make them live: deploy the edge proxy + set VENICE_TRIAL_KEY (demo);
wire viem wallet+lock+EIP-712 in access.ts + build token-site /api/access/claim
(needs a Venice ADMIN key) + a "Free private access" Settings panel (token-lock).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cture

- Remove the dead web/src/platform/ seam (superseded by the shared src/platform/;
  confirmed no imports). Web still builds.
- AGENTS.md: document the web edition (how to run, BYOK/demo inference, .starchild
  portability, web E2E), the "two shells, one core" architecture + the
  composability rule (improve core once → both shells; no reimplementing core
  logic in web/), and refresh the Key Files for the post-extraction structure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ss page + web link

Gets the token-lock inference tier past scaffolding into a real, working feature
on token.starchild.software (docs/inference-access-spec.md). Endpoints degrade
gracefully until go-live (503 without the admin key; lockLive:false until the
contract is deployed), so this is safe to ship dark.

Token site (token.starchild.software):
- src/lib/access.ts — shared, secret-free: EIP-712 ClaimAccess (distinct domain so
  a claim can't be replayed as a vote), cap mapping (§4.5, DAO-tunable), on-chain
  lockInfo read, and client lock/sign helpers (reuses the governance wallet infra).
- app/api/access/claim/route.ts — POST: verify signature → read lock on-chain →
  mint a capped (USD/EPOCH), expiring (=unlockAt) Venice INFERENCE key via the
  admin key. Idempotent per (wallet, amount, unlockAt); revokes + re-mints on
  top-up/extend; nonce replay-protected.
- app/api/access/status/route.ts — GET: lock + key status for the UI.
- app/access/page.tsx — connect → lock $STARCHILD → sign & claim → copy the key.

Web demo:
- Stays walletless; Settings now links to .../access ("Free private access — lock
  $STARCHILD"); the claimed key pastes into the existing BYOK slot and just works.
  The app talks to Venice directly (E2EE); the mint backend is never in the path.

Contract: contracts/script/DeployLock.s.sol (StarchildLock(token); $STARCHILD on Base).

Verified: forge test 7/7, contract+script build, token `next build` (routes /access,
/api/access/claim, /api/access/status registered), web tsc+build, and a local smoke
test of the graceful-degradation paths.

Go-live (founder): deploy StarchildLock → set NEXT_PUBLIC_STARCHILD_LOCK; generate a
Venice admin key → set VENICE_ADMIN_KEY (Vercel, server-only); confirm cap tiers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Encodes the funded-access security guarantee: a locker can never withdraw while
their key is still live. Asserts withdraw() reverts mid-lock (day 15) and one
second before unlockAt, and succeeds exactly at unlockAt — which is the minted
key's expiry. So there is no window where the tokens are withdrawn AND the key
still works. 8/8 lock tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant