Web edition foundation: Phase 1 core extraction + scaffolding#27
Open
forever8896 wants to merge 13 commits into
Open
Web edition foundation: Phase 1 core extraction + scaffolding#27forever8896 wants to merge 13 commits into
forever8896 wants to merge 13 commits into
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Built by a multi-agent workflow, independently verified:
cargo test30/0,cargo buildgreen, core-purity guard passes.Phase 1 — core extraction (desktop behavior unchanged):
starchild_corecrate — pure engine, zero tauri/sqlite/tokio/reqwest (CI-enforced).Storage+InferenceSendertraits; desktop impls (SQLite + reqwest AiClient); e2ee net split out; old paths shimmed;lib.rsslimmed.Web foundation (additive, doesn't touch desktop):
web/Vite shell +src/platform/seam (the no-Tauri-in-components contract)..starchildexport/import (Argon2id+AES-GCM) + 6 tests.npm run tauri devand 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