Commit Graph

10 Commits

Author SHA1 Message Date
yuanzonghao cf6e08aec4 fix(persistence): harden coerceEpoch null guard + autosave catch rollback
- coerceEpoch: early-return fallback on null/undefined input (was falling
  through to new Date(null) → epoch 0, surfacing as 1970-01-01 in the
  stories list); also unify the tail branch to Number.isFinite for
  consistency with the number-type fast path
- autosave .catch: roll back lastSavedFingerprintRef on unexpected throw
  so the next session change retries instead of silently marking the
  content as saved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-27 18:36:26 +08:00
Kai ki 610dba78b7 feat(persistence): local-first story persistence (IndexedDB + Supabase skeleton)
Remove Cloudflare D1 entirely (4 API routes, lib/db/, Drizzle config/migrations,
drizzle-orm/drizzle-kit deps, wrangler D1/R2/KV bindings) and replace with
browser-local-first architecture:

Open-source build (IndexedDB, no auth):
- lib/persistence/ 5-file module: types, idb adapter (zero-dep, fault-tolerant,
  post-open invalidation retry), localStore (CRUD + sync-reserved metadata +
  slim/rebuild + retention-cap eviction with tombstone reap + sync-state
  protection + last-resort bounded fallback), sessionSlim (voice strip +
  styleRef absent-delete), cloudStore (Supabase skeleton, server-only)
- Autosave: persistence fingerprint (history.length:lastBeatCount:playerName),
  serial saveChain, failure rollback retry, replaySourceRef guard (prevents
  replayed shared stories from clobbering user saves)
- clientStoryPersistence.ts: thin facade (SaveResult discriminated union)
- Stories page: /[locale]/stories with 3-language i18n (zh-CN/en/ja)
- Homepage: book icon entry point in header

Commercial build (Supabase, skeleton only):
- Single table public.stories (JSONB + RLS 4 policies on auth.uid()=user_id)
- supabase/migrations/ DDL (idempotent)
- cloudStore.ts server-only repository, AUTH_ENABLED short-circuit
- Not wired to client this phase

Featured stories: pure fallback (buildFallbackCards + localizeCards), no D1

Includes fixes from 3 rounds of subagent code-review (tasks 16-30):
- CR1: autosave restructure, coerceOrientation, D1 comment cleanup
- CR2: fingerprint+serial+rollback+replay guard, idb post-open retry,
  enforceRetentionCap latent defense, sessionSlim absent invariant
- CR3: single-scene share guard (replaySourceRef), insert-beat fingerprint
  (beats.length), pass3 overflow double-count fix, detach gate unification
2026-06-25 18:19:08 +08:00
Zonghao Yuan 0643f185ac feat(web): move feedback entry from floating button to about section (#112)
Replace the fixed bottom-right ember-500 floating button with a
dedicated fourth column in the about grid, matching existing visual
language (smallcaps heading + icon link). Adds submitFeedback and
feedbackDescription i18n keys for zh-CN, en, ja.

Grid uses md:grid-cols-2 lg:grid-cols-4 for better mid-screen
responsiveness.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 22:10:43 +08:00
Zonghao Yuan df3b1d3307 Merge pull request #106 from zonghaoyuan/feat/freeform-always-new-scene
feat(play): freeform input always generates new scene + enhanced insert-beat
2026-06-24 19:40:04 +08:00
yuanzonghao d5b4a02cb3 refactor(engine): remove follow-up choices from insert-beat, keep multi-beat only
Insert-beat is a pure in-scene micro-interaction — adding choices that
lead to change-scene contradicted its purpose. Now insert-beat generates
1-3 richer beats then loops back to the original options, which is the
natural UX for "you glanced at something decorative."

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 19:09:09 +08:00
Zonghao Yuan 56ed505554 Merge pull request #108 from zonghaoyuan/feat/feedback-fab
feat(web): add feedback FAB on homepage
2026-06-24 19:01:07 +08:00
yuanzonghao 8964155b26 feat(web): add floating feedback FAB on homepage linking to Tally form
Collect beta user feedback via Tally.so — fixed-position button in the
bottom-right corner opens the form in a new tab. Ember-orange default,
clay-dark on hover, i18n labels for zh-CN / en / ja.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 18:59:08 +08:00
yuanzonghao d4f6b18682 fix(play): skip fullscreen shortcut when typing in text inputs
The window-level 'F' keydown listener now ignores events from INPUT,
TEXTAREA, and contentEditable elements so freeform input is not
interrupted by the presentation-mode toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 18:51:26 +08:00
yuanzonghao 6f8125570a feat(play): always generate new scene for freeform text input + enhance insert-beat
User feedback: custom interactions rarely produce new story content because
the classifier heavily biased toward insert-beat (single reaction, no scene
change). Three changes to fix this:

1. Freeform text input now always triggers a full scene generation (skips
   the classify step entirely) — users who type expect the story to advance.

2. Vision (background click) classifier de-biased: prompt now favors
   change-scene when uncertain, and the code fallback flipped from
   insert-beat to change-scene. insert-beat narrowed to pure observation.

3. Insert-beat enhanced: generates 1-3 beats (was 1) with follow-up
   choices (was: loop back to original beat). Even when vision classifies
   as insert-beat, the player gets richer content and new options.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 18:36:35 +08:00
yuanzonghao 0a7076d5b9 fix(i18n): overhaul i18n with [locale] routing, SSR translations, and hreflang SEO
Rewrites the i18n system introduced in PR #94 to use Next.js App Router
[locale] dynamic segments with SSR-rendered translations and proper
middleware locale routing.

- Add middleware locale detection: / rewrites to /zh-CN/ internally,
  /en and /ja pass through, /zh-CN/... redirects to bare path
- Move all 7 pages under app/[locale]/ with SSR translation injection
- Fix server→client serialization: pre-evaluate function-valued
  translations (makeSerializable) to eliminate hydration flash
- Fix language switch key flash: use hard navigation with localStorage-
  only persistence, avoiding React state update before page reload
- Add <link rel="alternate" hreflang> tags for multilingual SEO
- Fix Supabase setAll overwriting locale rewrite response
- Trim locales from 22 to 3 (zh-CN/en/ja), delete 19 incomplete files
- LLM-translate 240 firstact game preset JSONs (en + ja, landscape +
  portrait) and story titles via gemini-3.5-flash
- Delete 11 one-off migration scripts and outdated i18n docs
- Add useLocalePath hook and navigation utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 23:16:17 +08:00