Repository navigation
fix(simulator): scope replay state by session and owner - #1762
Conversation
|
Review of
Existing verification passed: 46 app tests, 64 package tests, 25 workspace/tooling tests, package builds, full frontend typecheck, and targeted lint. Both added adversarial tests fail on this head. Native UI and CPU/RSS profiling were not run. I am addressing both findings in this PR and will follow up with the fix and verification results. |
|
Addressed both review findings in f4e2817.
Verification: 50 focused app tests passed across seven files; |
6575c19 to
7c0bb11
Compare
f4e2817 to
2cfca99
Compare
2cfca99 to
1231710
Compare
The global replay command atom has no production writer; its only setter lives in .archive/. The cell effect that consumed it, and the StrictMode and revocation handling added for it, only ran in tests. Remove the atom, the effect and the command-only tests, and cover the same end-of-playback, unmount and removal cases through user controls and follow updates. Releasing an owner under the retention cap no longer republishes the replay record: eviction runs through one helper that returns the same object when nothing is evicted.
|
Second review, against the rebased head Unstacked from #1760. This PR never needed #1760: its diff applies cleanly to develop, and typecheck plus the focused suites pass there.
Findings (fixed in
Otherwise the ownership and revocation design holds up. I found no problems with the order of mount-time writes, the Verification
|
…tor-replay-ownership Resolves three collisions with the simulator dead-state refactor (615d691): - src/store/ui/simulatorAtom.ts: keep clearCellReplaySessionAtom from this branch; take develop's deletion of simulatorDataSourceAtom, which is dead and has no consumers. - src/engines/Simulator/hooks/useCellPlayback.ts: docblock only. Both sides stated a true fact, so the merged text keeps both — the timer is independent-mode only and is gated on document visibility. - src/engines/Simulator/ActivitySimulatorGrid.tsx: drop the inert sessionId prop from the SimpleGridCell call site. It type-checked on this branch only because SimpleGridCell spread the shared GridCellProps; develop gave it a narrower own interface. SimpleGridCell renders from the shared cursor and never read sessionId. This collision auto-merged clean and only surfaced under tsgo. globalReplayStateAtom, removed by this branch in 7c4a037, was also removed independently on develop, so that part is now a no-op.
Problem
Replay overrides keyed only by thread name leak between sessions. Cursor and playing writes can overwrite one another, queued updates can outlive a cell, and hidden documents retain playback timers. Removed sessions also leave replay overrides behind.
Solution
Key replay owners by session and thread, remount on identity changes, publish complete user replay transitions and cancel queued owner updates during cleanup. Pause timers while the document is hidden and resume without catch-up. Protect mounted cells from retention eviction, retain up to 256 inactive cell entries, and reapply that limit when owners unmount. Revoke mounted writers at the session-removal boundary before evicting their entries; removed owners cannot publish queued follow updates, timer ticks, or user-control writes, and their playback effects are disabled. The unused global replay command path (no production writer) is removed rather than hardened. Preserve follow, detached and synced replay modes.
Potential risks
Independent of #1760: stack #1783 was dissolved on 2026-09-15 and both commits were rebased onto develop. The replayed changes are identical to the reviewed stacked commits; only surrounding context lines differ. Replay state is memory-only, so no persisted migration is needed. Retention can evict an inactive cell after 256 inactive entries; mounted keys are retained in addition to that cap. Owner registrations are scoped to the Jotai store and released on unmount. Newly mounted owners are separate registrations; this is revocation of existing mounts, not a permanent session tombstone. Additionally, and document visibility does not detect every CSS-hidden panel; a mounted panel in a visible document can still run playback. Native desktop hidden/minimized behavior, screenshots, CPU/RSS measurements and long-session interaction remain unverified. Reverting restores prior ownership behavior without data recovery. An existing expand-button icon presentation issue is recorded in the UI audit and excluded from this state fix.
Verification
pnpm exec vitest run --config config/vitest.config.ts --project app --maxWorkers=2 src/engines/Simulator/hooks/__tests__/useCellReplayState.test.ts src/engines/Simulator/components/SubagentPipCard.lifecycle.test.ts src/engines/Simulator/components/SubagentPipCard.state.test.ts src/engines/Simulator/hooks/__tests__/useCellReplayState.lifecycle.test.ts src/engines/Simulator/__tests__/ActivitySimulatorGrid.lifecycle.test.ts src/store/session/sessionAtom/__tests__/mutations.test.ts: 42 passed across six files.pnpm typecheck:fast: passed on the original stacked head, using the frozen-lockfile installation from refactor(workspace): extract private frontend core packages #1760.Review follow-up verification
pnpm exec vitest run --config config/vitest.config.ts --project app --maxWorkers=2 src/engines/Simulator/hooks/__tests__/useCellReplayState.test.ts src/engines/Simulator/hooks/__tests__/useCellReplayState.lifecycle.test.ts src/engines/Simulator/__tests__/ActivitySimulatorGrid.lifecycle.test.ts src/engines/Simulator/components/SubagentPipCard.lifecycle.test.ts src/engines/Simulator/components/SubagentPipCard.state.test.ts src/store/session/sessionAtom/__tests__/mutations.test.ts src/engines/SessionCore/replay/components/ShellReplayOutput/ShellReplayOutput.test.ts: 50 passed across seven files, including active-cell eviction, post-unmount retention, paused deletion, queued deletion, removal throughremoveSession, timer shutdown, rejected post-removal user/global writes, and store isolation.pnpm typecheck:fast: passed, including both package typechecks.pnpm exec eslint src/store/ui/simulatorAtom.ts src/engines/Simulator/hooks/useCellPersistence.ts src/engines/Simulator/hooks/useCellPlayback.ts src/engines/Simulator/hooks/useCellReplayState.ts src/engines/Simulator/hooks/__tests__/useCellReplayState.lifecycle.test.ts --max-warnings 0: passed.git diff --check: passed.Rebase onto develop verification
developat4f7a624056.git range-diff 7c0bb11942..2cfca994e1 origin/develop..12317107bereports the fix commit as identical; the first commit differs only in one context line (thefindIndexAtTimeimport it sits next to). The diff is still 16 files, +731/-51, and commit messages and authors are unchanged. The commits were replayed withgit merge-tree/git commit-tree, so commit hooks did not run again; the checks below ran on the rebased head instead.pnpm install --frozen-lockfile --ignore-scripts: passed.pnpm typecheck:fast(tsgo): passed.pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=2 src/engines/Simulator/hooks/__tests__/useCellReplayState.test.ts src/engines/Simulator/hooks/__tests__/useCellReplayState.lifecycle.test.ts src/engines/Simulator/__tests__/ActivitySimulatorGrid.lifecycle.test.ts src/engines/Simulator/components/SubagentPipCard.lifecycle.test.ts src/engines/Simulator/components/SubagentPipCard.state.test.ts src/store/session/sessionAtom/__tests__/mutations.test.ts: 46 passed across six files. TheShellReplayOutputsuite listed above belongs to refactor(workspace): extract private frontend core packages #1760 and is no longer part of this diff.pnpm exec eslint --max-warnings 0 --report-unused-disable-directiveson the 14 changed TypeScript files: passed.node scripts/quality/check-test-placement.mjs,scripts/ci/check-changed-file-length.cjson the diff, andgit diff --check origin/develop...HEAD: passed.Second review follow-up verification
Commit
7c4a0372c2removesglobalReplayStateAtomand its cell effect. The only setter lived in.archive/, so the path ran only in tests. The command-only tests now cover the same behaviour through user controls and follow updates: end-of-playback stop, no publish after unmount, and revocation of a queued update. Releasing an owner under the retention cap no longer republishes the replay record. Earlier evidence above that mentions global commands predates this commit.pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=2on the six files listed in the rebase section: 47 passed. That includes a new test that unmount below the cap keeps the record identity.useCellReplayState.lifecycle.test.ts. Restoring the unconditional unmount republish fails the new identity test. Removing the owner write guard fails three removal/revocation tests, including the queued follow-update case. Both files were restored byte-for-byte.pnpm typecheck:fast, ESLint and oxlint on the four changed TypeScript files,node scripts/quality/check-test-placement.mjs, the file-length gate, andgit diff --check: passed. Commit hooks (lint-staged, tsgo, commitlint) passed.Develop integration verification (2026-09-22)
Merged
origin/developinto the branch atef782f36f2rather than rebasing, so thebranch is not force-pushed under the author. Three files collided with the simulator
dead-state refactor
615d6915d8:src/store/ui/simulatorAtom.ts: keptclearCellReplaySessionAtomfrom this branch andtook develop's deletion of
simulatorDataSourceAtom, verified dead with no consumers insrc/or.archive/.src/engines/Simulator/hooks/useCellPlayback.ts: docblock only. Both sides stated a truefact, so the merged text keeps both — independent-mode only, and gated on document
visibility.
src/engines/Simulator/ActivitySimulatorGrid.tsx: dropped thesessionIdprop from theSimpleGridCellcall site. This one auto-merged without a conflict and only surfacedunder
tsgo. It type-checked on this branch becauseSimpleGridCellspread the sharedGridCellProps, which this branch extended withsessionId; develop replaced that with anarrower own interface.
SimpleGridCellrenders from the shared cursor and never read theprop, so nothing is lost.
IndependentGridCell, which does use it forcellReplayKey, isunchanged.
globalReplayStateAtom, removed here in7c4a0372c2, was also removed independently ondevelop, so that commit is now a no-op against the current base.
Net diff against develop is 16 files, +719/-49 (was +731/-51 before integration; the
difference is the dropped prop and the merged docblock).
npx tsgo --noEmit --pretty falseon the merged tree: passed, 0 errors. It failed withTS2322before theSimpleGridCellresolution, which is how that silent collision was found.vitest run --config config/vitest.config.ts src/engines/Simulator src/store/session/sessionAtom src/store/ui: 445 passed across 61 files.prettier --check,oxlint -c src/.oxlintrc.json --max-warnings 0, andeslint --max-warnings 0 --report-unused-disable-directiveson the 14 changed TypeScriptfiles: passed.
node scripts/quality/check-test-placement.mjs: passed. All changed files are under the700-line gate (largest:
SubagentPipCard.tsxat 660).instead. Reason: on a merge commit
lint-stagedtreats the whole incoming develop delta asstaged, and its
*.{css,scss,md,json}→prettier --writerule rewrote 11 unrelated docs(for example
*agent*→_agent_inevidence/resend-diagnosis.md). Those files alreadyviolate prettier on develop and CI does not prettier-check docs, so the churn was
pre-existing and out of scope. They were restored to develop's content and the final diff
contains only this PR's 16 files.
three), and native desktop behaviour.
Audit
Architecture L1 tests/types; L2-L4 cell/session ownership and cache lifetime; L5 explicit replay transitions; L6-L7 complete state updates and identity keys; L8 memory-only state contract; L9 all cell entry points; L10 session removal at the owning mutation boundary. React review covers timer cleanup, canceled microtasks, keyed remounts and narrowly selected cell state. Performance verdict: source-level lifecycle regressions are fixed; mounted-owner protection, a 256-inactive-entry limit, removal revocation, and timer cleanup are verified in tests. Native resource-measurement verdict remains blocked because CPU/RSS and real Tauri behavior were not measured. See
docs/org2-performance-guard-2026-09-14/SimulatorReplay.md. UI report: one existing fix candidate, four keep-with-reason, zero abstractions.