Repository navigation
Conversation
Adds everwith-claude-frontend/: an independent, runnable desktop frontend rebuilt from the 2026-10-13 private Everwith handoff package. Nothing outside this directory changes; frontend/, frontend2/ and rpg_backend/ are untouched. - React 19 + Vite 7 + TypeScript strict. Every service is an in-browser local mock (no real Luna / JEV / TTS / account / storage connection). - Private chat, five Live2D characters, three board games (Royale, Splendor, CABO), stories, recent, creation studio, account. - Handoff assets (portraits, Live2D models, Cubism SDK, game art) are not in git: `npm run assets:sync -- --from <handoff>` copies and hash-checks them. - public/generated/batch-01: 46 resized copies of the user-provided GPT image batch v2-batch-01 (user-provided GPT output; commercial / redistribution rights unverified). Provenance: docs/GENERATED_ASSETS.json. - Verified on this exact app code (sha256 fde68002…): 54/54 unit, 29/29 E2E (Playwright, headless Chromium + SwiftShader), clean-room reproduction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2RywNAaCUZbaHNhH3So7E
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f492ce8257
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| let cache: DB | null = null | ||
| export function db(): DB { | ||
| if (cache) return cache |
There was a problem hiding this comment.
Reload shared state before writing from another tab
When two tabs have each called db(), both retain independent cached copies indefinitely; reloadDB() is never used and save() serializes the entire stale object. A later mutation in either tab therefore overwrites unrelated chats, games, stories, or works saved by the other tab, causing real cross-tab progress loss rather than the intended stale-revision conflict handling.
Useful? React with 👍 / 👎.
| useEffect(() => { | ||
| if (sync !== 'pending') return | ||
| const timer = setTimeout(() => { void flush() }, 700) | ||
| return () => clearTimeout(timer) |
There was a problem hiding this comment.
Flush pending edits before cancelling the debounce
If a user edits a work and follows the Back link or otherwise leaves within the 700 ms debounce window, this cleanup cancels the only scheduled flush() and there is no unmount or navigation flush. The draft exists only in component memory at that point, so returning to the work silently loses the latest edits.
Useful? React with 👍 / 👎.
| const flush = useCallback(async () => { | ||
| if (flight.current || !dirty.current || !draftRef.current) return |
There was a problem hiding this comment.
Wait for an active draft upload before saving
When an autosave request is already in flight, flush() returns immediately, so save() and startPreview() proceed using the old base.current. If the user edits during that request and immediately saves or previews, the immutable version can capture the previous draft or the command can fail with a revision conflict even though the UI just requested a flush; the active upload needs to be awaited and any newly dirty draft flushed afterward.
Useful? React with 👍 / 👎.
| if (!want) { missing.push({rel, reason: 'not listed in SHA256SUMS'}); continue; } | ||
| if (!existsSync(source)) { missing.push({rel, reason: 'absent in handoff copy'}); continue; } |
There was a problem hiding this comment.
Remove stale assets when the new handoff omits them
When syncing over an existing installation with an incomplete handoff, missing entries are merely recorded and the command still succeeds, while files copied by an earlier run remain at their destinations. Because the app loads those destination URLs directly, it uses stale assets instead of the documented missing-asset fallback, and the new lock file no longer checks those leftover files.
Useful? React with 👍 / 👎.
| f.epoch++; f.revision++ | ||
| for (const c of Object.values(d.conversations)) if (c.friend_id === f.id && c.friend_epoch < f.epoch) c.status = 'read_only' | ||
| for (const s of Object.values(d.stories)) if (s.cast.some(r => r.friend_id === f.id)) s.status = 'read_only' |
There was a problem hiding this comment.
Retire pending operations when resetting a friend
If Reset is confirmed while that friend's chat reply or story turn is still pending, these assignments make the old session read-only but do not cancel or stale its pending operation. The background tickers subsequently commit the reply or story turn anyway, so a supposedly frozen old conversation/run continues mutating after the epoch change; Remove has the same problem for chat operations.
Useful? React with 👍 / 👎.
| // The account may finish loading after the first render (reload): pick up its saved draft then. | ||
| useEffect(() => { | ||
| const saved = readJSON<string>(draftKey(accountId, sessionId), '') | ||
| if (saved) setDraftState(current => current || saved) | ||
| }, [accountId, sessionId]) |
There was a problem hiding this comment.
Migrate story drafts when guest ownership changes
When a guest signs in, the story session is reassigned to the account but its composer draft remains stored under the guest-scoped key. This effect only reads the new account key and never copies the current or guest draft into it, so signing in and then refreshing or revisiting the merged run makes the unsent draft disappear.
Useful? React with 👍 / 👎.
| /** A frame threw (director keeps running). Many in a row → give up to the static fallback. */ | ||
| private frameErrorRun = 0 | ||
| noteFrameError(error: unknown) { | ||
| this.stats.lastError = error instanceof Error ? error.message : String(error) | ||
| if (++this.frameErrorRun > 90 && this.status === 'ready') { this.status = 'failed'; this.error = 'frame_failed' } |
There was a problem hiding this comment.
Propagate runtime failure to the static fallback
After repeated draw failures this changes only ModelRuntime.status; LiveStage copies the status into React state only when the initial load finishes and never observes later changes. Consequently the canvas remains selected and the same-character static image stays hidden after frame_failed (and similarly after a failed context restore), producing a permanently blank or frozen stage instead of the fallback promised by this comment.
Useful? React with 👍 / 👎.
| setSeats(prev => Array.from({length: players - 1}, (_, i) => { | ||
| if (prev[i]) return prev[i] | ||
| if (i === 0 && withFriend) { | ||
| const f = friends.find(x => x.id === withFriend) | ||
| if (f) return {kind: 'friend', friend_id: f.id, friend_epoch: f.epoch} |
There was a problem hiding this comment.
Seed the requested friend after the friend list loads
On a refreshed or directly opened gamePrepare link with ?with=<friend>, the game catalog normally resolves before the authenticated friend list, so this effect first fills seat 1 with a temporary character. When friends later arrive, prev[i] causes that temporary choice to be retained and the requested friend is never seated, silently breaking the Play Together/deep-link intent.
Useful? React with 👍 / 👎.
| // Same-source friends are separate people: listed with their own names and dates. | ||
| const same = (friends ?? []).filter(f => f.source_id === character.id) |
There was a problem hiding this comment.
Match existing friends by source version
For a user-created character with multiple saved versions, this filter treats friends created from every version as matches for the currently displayed version. Visiting a v2 character page can therefore offer a chat with a v1-derived friend and list that older identity as if it came from v2, despite friend identity elsewhere being defined by both source_id and source_version.
Useful? React with 👍 / 👎.
| const load = useCallback(() => { setError(false); api.records.list(archived).then(r => setItems(r.items)).catch(() => setError(true)) }, [archived]) | ||
| useEffect(() => { setItems(null); load() }, [load]) |
There was a problem hiding this comment.
Ignore record-list responses from the previous tab
If the user switches between Active and Archived while the prior /records request is still pending, both promises can call setItems because there is no cancellation or generation check. A slower response for the previous tab can then replace the new tab's rows, showing active records under Archived or vice versa until another reload.
Useful? React with 👍 / 👎.
这是什么
新增独立目录
everwith-claude-frontend/:依据 2026-10-13 的 Everwith 私人交接包重建的桌面网页前端,可以独立运行。仓库里其他目录一个字节都没改(frontend/、frontend2/、rpg_backend/等都不受影响)。npm run assets:sync -- --from <交接包目录>会按字节复制并核对 sha256,缺图时降级为同一角色静态图 / 中性占位。public/generated/batch-01/:46 个 GPT 生成图的缩放副本(故事封面与场景、角色房间背景、游戏封面、状态插画、作品封面、Persona 头像、图标)。权利标签:用户经 GPT 生成并提供;商用/再分发权未核实。来源、哈希、prompt 见docs/GENERATED_ASSETS.json/.md。怎么跑
验证(同一份应用代码,sha256
fde68002…,见SOURCE_MANIFEST.txt)ACCEPTANCE.md:12 PASS(mock) / 5 PASS(local) / 7 PARTIAL / 0 FAIL,没有 PASS-real。Everwith_Claude_Frontend_Rebuild_2026-10-13/,没有放进 git。Review 建议从这里看
src/api/adapter.ts、src/api/client.ts:mock / real 适配层与响应校验src/mock/server/*:本地模拟服务端(账号、好友、私聊流、故事、桌游权威状态、创作)src/chat/stream-controller.ts:私聊流控制(generation / receipt / retry / lookup)src/live2d/*:运行时、单 RAF 导演、舞台组件src/features/games/*、src/games/engines/*:三款牌桌;引擎与交接包逐字节一致(PROVENANCE.json)docs/GENERATED_ASSETS.md:生成素材用在哪里已知缺口
真实中文输入法人工走查、口型与真实音频同步、故事内纠错、全部真实服务接入、真实 GPU 下 Live2D 观感、移动端、独立验收。
对 CI 的影响
现有 CI 只跑 backend(pytest)和 frontend2(tsc + build);新目录没有接入 CI,也不影响这两个任务。
🤖 Generated with Claude Code
https://claude.ai/code/session_01P2RywNAaCUZbaHNhH3So7E
Generated by Claude Code