Commit Graph

308 Commits

Author SHA1 Message Date
Zonghao Yuan 3d80ac7f1c Merge pull request #120 from zonghaoyuan/readme-restructure
docs(readme): restructure for star conversion optimization
2026-06-29 12:20:07 +08:00
yuanzonghao 7dac77e200 docs(readme): restructure for star conversion optimization
- Restructure overview with scannable bullet lists for capabilities
- Move screenshots up (after overview), reduce from 14 to 6
- Extract configuration guide to docs/configuration{,.en,.ja}.md
- Update Vercel deploy button envLink to point to new config docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 12:16:03 +08:00
yuanzonghao c66ee38ddd docs(readme): mark story save & cloud sync as done in roadmap
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 23:33:49 +08:00
Zonghao Yuan 801e9004da Merge pull request #117 from zonghaoyuan/cloudflare-migration
feat(persistence): bidirectional local/cloud story sync (Supabase)
2026-06-28 23:09:43 +08:00
Kai ki 6ba5307c6c fix(persistence): address PR #117 review feedback
Adopt 8 PR-agent (Qodo) findings; 4 declined (concurrency already guarded by
the putSyncedRecord/markRecordSynced guards + RPC optimistic concurrency;
SQL-injection / won-equality / microtask-race are false positives — see PR reply).

- markRecordSynced: guard on updatedAt too — softDeleteStory doesn't bump rev,
  so a same-rev newer local tombstone must not be marked synced by an older
  push's ack (symmetric with putSyncedRecord's guard)
- recordToEnvelope: fallback timestamps to 0 not Date.now() (a corrupt record
  should lose LWW, not win as "now")
- push/delete routes: validate rev/updatedAt as finite -> 400 (was silent 200);
  push: Content-Length pre-check before buffering the body
- pushDeletion: idbGet a single record instead of a full-store scan
- manifest: Cache-Control private,no-store + client fetch cache:no-store
- cloudSyncClient: Array.isArray narrowing on items/blobs
- RPC: `if found` instead of `v_row.id is not null` after RETURNING INTO

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:52:09 +08:00
Kai ki 739af60848 Merge remote-tracking branch 'origin/staging' into cloudflare-migration 2026-06-28 11:21:20 +08:00
Kai ki ff12b2759f feat(persistence): bidirectional local/cloud story sync (Supabase)
Connect the previously-skeleton cloudStore to the client with a full
bidirectional reconcile engine. Commercial build (AUTH_ENABLED) only; the
open-source build is byte-for-byte unchanged — all cloud paths short-circuit
when AUTH_ENABLED is false.

- cloudSync.ts: reconcile engine — decideAction (pure, LWW rev->updatedAt with
  tombstone priority) + syncOnLogin/pushOnSave/pushDeletion (best-effort,
  serialized, isAuthed-gated)
- cloudSyncClient.ts: browser fetch bridge (short-circuit + fault-tolerant)
- /api/stories/{manifest,pull,push,delete}: RLS-guarded sync endpoints
- upsert_story_if_newer RPC: optimistic concurrency (SECURITY INVOKER,
  auth.uid() injection, rev->updated_at guard, revoked from public)
- cloudStore: +manifest/pullBlobs, save->RPC {stored,won}, softDelete w/ rev
- localStore: +listAllRecordsForSync/putSyncedRecord/markRecordSynced
  (concurrency-guarded sync writes); types: +StorySyncMeta/StorySyncEnvelope
- facade + UserChip: inject pushOnSave/pushDeletion + login-triggered reconcile

Sync model: full reconcile on login + background push on save (no Realtime;
eventual consistency). Conflict resolution: last-write-wins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:20:47 +08:00
Zonghao Yuan cb5daf58ce fix(ci): grant CLA bot pull-request write permission (#115)
The CLA Assistant workflow had `pull-requests: read`, which prevented
the GITHUB_TOKEN from posting the sign-CLA comment on PRs. Change to
`pull-requests: write` so the bot can comment.

Also removed the `protect-cla-signatures` ruleset (GitHub-side) that
marked the signatures branch as protected, blocking the bot from
pushing signature records.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-27 20:04:10 +08:00
Zonghao Yuan fbd02588a1 Merge pull request #114 from zonghaoyuan/cloudflare-migration
feat(persistence): 存档本地优先架构(移除 D1 → IndexedDB + Supabase 骨架)
2026-06-27 18:41:53 +08:00
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 da74e3e763 fix(persistence): address PR review feedback (4 low-cost improvements)
From PR #114 external review agent — adopted the real, low-cost findings;
remaining items (false positives / design trade-offs) explained in PR replies:

- coerceEpoch: !Number.isNaN → Number.isFinite — reject ±Infinity, which
  previously slipped through and produced Invalid Date via new Date(Infinity)
- enforceRetentionCap pass2/pass3: decrement overflow only when idbDelete
  actually succeeds — a failed best-effort delete no longer under-evicts
- cloudListStories: explicit column list instead of select() — avoid pulling
  the bulky session_jsonb when only metadata is needed
- Supabase stories: composite primary key (user_id, id) + onConflict user_id,id
  — avoid a cross-user Session.id collision rejecting the second user's save
  (skeleton not yet deployed, so the migration is edited in place)

typecheck + build:cf green.
2026-06-25 19:20:55 +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
Kai ki be39fcc77e Merge remote-tracking branch 'origin/staging' into cloudflare-migration 2026-06-25 18:08:46 +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
Zonghao Yuan 5591ae91a9 Merge pull request #111 from zonghaoyuan/chore/cla-whitelist-anthropic
chore(ci): add noreply@anthropic.com to CLA allowlist
2026-06-24 19:39:41 +08:00
Zonghao Yuan 4c0d033586 Merge pull request #110 from zonghaoyuan/feat/split-firstact-beats
refactor(presets): split mixed narration+dialogue beats
2026-06-24 19:30:42 +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
yuanzonghao b39452ab2f chore(ci): add noreply@anthropic.com to CLA allowlist
Co-authored commits from Claude Code were failing the CLA check
because the bot email had not signed the CLA. Whitelist it so
AI-assisted PRs pass without requiring --admin merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 19:08:24 +08:00
yuanzonghao f340ab69b5 refactor(presets): split mixed narration+dialogue beats into separate beats
Preset firstact JSONs had 37% of beats bundling narration and dialogue
on the same beat object, while the current engine (Paradigm D prose
splitter) produces strictly one-type-per-beat output. This mismatch
caused the preset card experience to feel different from actual gameplay.

Split 275 mixed beats across 94 files (120 total) into independent
narration→dialogue beat pairs, preserving all images, characters, voices,
and graph references. Beat count: 738→1013.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 19:06:43 +08:00
Zonghao Yuan eb79f3b272 Merge pull request #109 from zonghaoyuan/feat/play-font-size
style(play): increase dialogue and choice font sizes
2026-06-24 19:03:59 +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
Zonghao Yuan fa524c19e4 Merge pull request #105 from zonghaoyuan/feat/readme-update
docs(readme): update architecture, add OpenDeploy sponsor
2026-06-24 19:00:23 +08:00
Zonghao Yuan 3a16eb35b5 Merge pull request #107 from zonghaoyuan/fix/fullscreen-fkey-in-input
fix(play): skip fullscreen shortcut when typing in text inputs
2026-06-24 18:59:23 +08:00
yuanzonghao 94050b82c5 style(play): increase dialogue and choice font sizes by 3px
Bump all in-game text sizes for better readability:
- Dialogue body: 16/13/15px → 19/16/18px
- Narration: 15/12/14px → 18/15/17px
- Speaker name: 13/11/12px → 16/14/15px
- Choice label: 15/13/14px → 18/16/17px
- Choice index: 13/11px → 16/14px
- Freeform input: 14px → 17px
- Freeform button: 13px → 16px

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 18:59:21 +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 4603a7df71 docs(readme): update architecture, add OpenDeploy sponsor, fix LINUX DO link
- Remove deprecated Architect agent from pipeline diagrams (zh/en/ja SVGs)
  and README text — Writer now handles story architecture
- Add OpenDeploy as sponsor section and one-click deploy option
- Change LINUX DO badge link to the actual forum post
- Move Open Deploy to completed in Roadmap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 18:53:26 +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 b5f5ebc353 fix(engine): filter invalid choices before slicing to preserve valid ones
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-24 18:51:23 +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
Kai ki e31bd16b15 fix(engine): prevent directScene hang + enforce segment ID uniqueness in prod
Two defensive fixes surfaced by the PR #95 review (PR-Agent), applied on
top of the staging sync:

1. directScene: routeTaggedStream rejecting BEFORE onPlan fires would leave
   planPromise unsettled, hanging `await planPromise` — and thus the whole
   /api/start and /api/scene request — forever. Add a .catch that settles
   the plan with a minimal fallback and resolves routing to a degraded
   result, so the pipeline produces a playable fallback scene (graceful
   degradation) instead of hanging.

2. prompts/registry: the duplicate-segment-ID guard only ran under
   NODE_ENV=development, so a bad merge introducing a duplicate ID would
   silently shadow a segment in production. Run the check in all
   environments (once at module load; negligible cost).
2026-06-23 19:06:19 +08:00
Zonghao Yuan ae4d9f8873 Merge pull request #103 from zonghaoyuan/feat/selfhost-font-awesome
perf(web): self-host Font Awesome instead of cdnjs CDN
2026-06-19 01:36:56 +08:00
yuanzonghao 8468d5da3a chore: revert auto-generated next-env.d.ts change
Drop the unintended path change (.next/dev/types → .next/types)
that `next build` auto-wrote — this file is managed by Next.js
and should not carry build-vs-dev path diffs across branches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-19 01:36:15 +08:00
yuanzonghao 05a5251658 perf(web): self-host Font Awesome instead of cdnjs CDN
Replace the external cdnjs.cloudflare.com <link> with a bundled
npm import so FA CSS+fonts ship as static assets through the
same Cloudflare CDN that serves the app — eliminates an extra
DNS lookup and third-party CDN dependency, improves reliability
for China users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-19 01:33:40 +08:00
yuanzonghao f8cfa90d4c chore: remove stale GALLERY_SECRET from .dev.vars.example
GALLERY_SECRET was removed in 64cf9c3 — .infiplot files now use
plaintext + SHA-256 integrity, no encryption secret needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 23:50:35 +08:00
Zonghao Yuan 69908862ef Merge pull request #101 from zonghaoyuan/staging
chore: merge staging into main
2026-06-18 23:21:38 +08:00
Zonghao Yuan 61ef027a0e fix(cla): route signatures to unprotected cla-signatures branch (#100)
The CLA bot run on PR #99 failed with 'Resource not accessible by
integration' because it tried to commit cla-signatures/version-1.json
to the branch-protection-protected main branch. The CLA Assistant
action cannot push to protected branches (contributor-assistant/
github-action#150); PAT identity is not granted bypass even though
the PAT owner is repo admin.

Fix: store signatures on a dedicated orphan branch 'cla-signatures'
that has no protection rules, so the PAT can commit freely. This is
the workaround recommended by the action maintainers and the dominant
community solution.

Also brings the staging copy of cla.yml up to parity with main (the
guard step + SHA pin from PR #93 had not been back-merged to staging):
- Add the 'fail on missing CLA_BOT_TOKEN' guard step
- Pin action to contributor-assistant/github-action@ca4a40a7... (v2.6.1);
  the old 'cla-assistant-action' path 404s and was a second reason the
  bot never fired
2026-06-18 23:19:20 +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
Zonghao Yuan 4a0072bffb Merge pull request #93 from zonghaoyuan/sync/staging-to-main
chore(release): sync staging → main
2026-06-18 22:52:45 +08:00
yuanzonghao 366b84e2fb chore(repo): harden CLA workflow — pin SHA, fix action repo, fail on missing token
Addresses pr-agent review on PR #93 (the two CLA-specific items). The
other 9 suggestions targeted code carried in by the sync (middleware,
gender-x) and are out of scope here.

- uses: contributor-assistant/cla-assistant-action@v2.6.1  (404 — wrong name)
+ uses: contributor-assistant/github-action@ca4a40a7...       (canonical, pinned)

  The action's real home is contributor-assistant/github-action; the
  'cla-assistant-action' path we had resolves to 404 and may itself be
  why the bot never fired on PR #92. Pin to the full SHA of v2.6.1 so a
  movable tag can't slip a malicious update under pull_request_target +
  a writable PAT. The repo is archived but v2.6.1 still functions.

- Add a guard step that FAILS the job on the canonical repo
  (zonghaoyuan/infiplot) when CLA_BOT_TOKEN is missing. Previously a
  missing token silently skipped the job and it 'succeeded' — dangerous
  once this becomes a required status check (a lost/expired token would
  let CLA enforcement degrade invisibly). Forks still skip cleanly.
2026-06-18 21:59:19 +08:00
yuanzonghao 941b54c3f8 fix(share): require at least 1 byte of content in .infiplot files
Reject header-only files (37 bytes, empty plaintext) at the unpack
boundary instead of letting them through as empty strings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 21:56:38 +08:00
yuanzonghao 64cf9c330d refactor(share): remove GALLERY_SECRET, use plaintext + SHA-256 integrity for .infiplot files
The encrypted .infiplot format (AES-256-GCM via GALLERY_SECRET) provided no
meaningful security — the payload is AI-generated story content with no
credentials or PII, and the project is open source. Replace with plaintext +
SHA-256 integrity check (format v2). Story share is now always enabled without
requiring a server secret.

- galleryCrypto.ts: AES-256-GCM → plaintext + SHA-256 hash; remove secret param
- 4 API routes: remove GALLERY_SECRET guard and 503 fallback
- story-unpack: forward specific error messages (v1 compat, hash mismatch)
- gallery/page.tsx: remove stale AES-GCM comment
- AGENTS.md: document gallery-pack/gallery-unpack routes
- .env.example, wrangler.jsonc: remove GALLERY_SECRET references

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 21:41:56 +08:00
Zonghao Yuan 03dccd7c74 chore(deploy): add keep_vars and remove placeholder vars from wrangler.jsonc (#97)
Add "keep_vars": true so Dashboard-set variables survive redeployments
(Vercel-like behavior). Remove empty-string placeholder vars — provider
config (TEXT_BASE_URL, IMAGE_*, VISION_*, TTS_*) is now managed entirely
via Dashboard. Update comments to document three variable categories
(secrets, runtime, build-time) and their setup locations.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 20:22:54 +08:00
Zonghao Yuan 1a7d8a21a9 chore(deploy): sanitize wrangler.jsonc for open source, hide story persistence UI (#96)
- Replace hardcoded API URLs and model names with empty-string placeholders
  so self-deployers set their own values via Dashboard
- Comment out D1/R2/KV bindings and placement region to prevent deploy
  failure for users who don't need these optional services
- Add comprehensive comments for secrets, optional tuning vars, and
  build-time NEXT_PUBLIC_* variables
- Hide /stories navigation link until auth integration is ready
  (all story persistence code is retained)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 18:57:17 +08:00
Zonghao Yuan 0e4c2ebef4 feat(engine): merge cloudflare-migration — paradigm D engine, BYOK proxy, story persistence (#95)
Squash-merge the cloudflare-migration branch (7 commits by Kai ki) into
staging with conflict resolution, feature integration, and bug fixes.

Engine:
- Paradigm D: single-stream Writer replacing dual-phase Plan/Beats
- Delete Architect agent; story bible generated via Writer <plan> tag
- Modular prompt architecture (segments/registry/builder)
- StreamRouter for tagged stream splitting (<plan>/<story>/<choices>)

Infrastructure:
- Cloudflare Workers deployment (wrangler.jsonc, OpenNext adapter)
- D1 database schema + Drizzle ORM (scaffolded, not yet active)
- R2 storage helpers (scaffolded, not yet active)
- Story persistence API routes + client-side persistence

BYOK (Bring Your Own Key):
- /api/llm/user-proxy with SSRF-protected LLM proxy (+ requireUser auth)
- CORS-aware fetch in ai-client: auto-detect CORS failure, fallback to
  server proxy transparently via OpenAI SDK custom fetch
- BYO config support added to classify-freeform and vision routes
- SettingsModal CORS privacy notice (keys never logged/stored)

SSE streaming:
- engineClient.ts: fetchSSE helper for progressive scene events
- startSession/requestScene accept optional emit callback
- Fix SSE error event field name (error → message) in scene/start routes

i18n integration:
- Wire buildLanguageDirective into paradigm D's prompt builder
- Update corsNotice i18n keys (zh-CN/en/ja) with CORS proxy privacy text
- Preserve Session.language + LanguageSwitcher from i18n commit

Co-authored-by: Kai ki <155355644+zbf1009@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-18 18:05:38 +08:00
Qi Chen 05bd7e229c Merge pull request #94 from zonghaoyuan/feat/gender-x
feat(i18n): language switcher with en/ja translations
2026-06-18 17:01:20 +08:00
DESKTOP-I1T6TF3\Q 2d35c1d9de feat(i18n): add language switcher with en/ja translations
- New client-side i18n via React Context (useI18n, tArray, I18nProvider)
- Catalog ships 21 locale stubs; only zh-CN/en/ja have reviewed translations
- Header language switcher (globe icon + short label) before settings gear
- All hardcoded Chinese UI text migrated to keys: typewriter, options,
  hints (with embedded gear icon via dangerouslySetInnerHTML), settings
  panel, footer/about, play page hints
- AI output language follows user-selected locale via trailing one-liner
  directive appended to Architect/Writer/CharacterDesigner/InsertBeat
  user messages (preserves system-prompt cacheability)
- Per-locale separator rule: zh uses middot between every glyph; en/ja
  use plain spaces
- Option value → i18n key suffix maps preserve Chinese as the underlying
  identifier so analytics unions and STYLE_MAP keys stay byte-stable

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 16:54:35 +08:00
Zonghao Yuan 4abb57ec1f Merge pull request #89 from zonghaoyuan/feat/cla-mechanism
feat(repo): add CLA mechanism for external contributors
2026-06-18 11:05:26 +08:00
Zonghao Yuan 09844bd5db fix(web): migrate proxy.ts to edge middleware.ts for Cloudflare compat (#88)
Next.js 16 locks proxy.ts to the Node.js runtime, but OpenNext for
Cloudflare rejects Node.js middleware at build time ("Node.js middleware
is not currently supported", build.js exit 1). Rename to middleware.ts
with an explicit experimental-edge runtime so the Supabase SSR cookie
refresh runs on edge and stays deployable to both Vercel and Workers.

Supabase SSR only uses Web APIs (fetch, cookies), so it is
edge-compatible; the core getUser() refresh logic is unchanged. The
matcher excludes static assets by file extension (not by "contains a
dot") so future dotted dynamic routes (e.g. /u/john.doe) still get the
cookie refresh. getUser() is wrapped in try/catch so a transient
network error (rethrown by @supabase/auth-js) doesn't 500 or crash the
page request — the cookie simply isn't refreshed that round.

Note: runtime must be "experimental-edge", not "edge". Next.js 16 routes
the root middleware file through the pages-router static-info path, where
runtime "edge" throws E1015 at build ("Use runtime 'experimental-edge'
instead"). "experimental-edge" only warns; both are treated as edge by
isEdgeRuntime().

Verified: pnpm typecheck, pnpm build (Vercel), pnpm build:cf
(Cloudflare — Bundling middleware function -> OpenNext build complete,
node-middleware guard no longer fires).
2026-06-18 11:00:49 +08:00
Qi Chen 9ec21c46e7 Merge pull request #90 from zonghaoyuan/feat/gender-x
feat(options): add third gender option "X" for universal gender
2026-06-18 09:26:33 +08:00