Skip to content

fix(app): keep session timeline stable across worktree exit#436

Merged
Astro-Han merged 27 commits into
devfrom
codex/fix-i432-timeline-stability
May 5, 2026
Merged

fix(app): keep session timeline stable across worktree exit#436
Astro-Han merged 27 commits into
devfrom
codex/fix-i432-timeline-stability

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 4, 2026

Copy link
Copy Markdown
Owner

Summary

Keep the current session timeline mounted when exit-worktree changes the active execution directory.

  • Stop keying the directory route layout subtree by resolved directory.
  • Make timeline identity session-scoped instead of directory + sessionID.
  • Keep same-session ready state through transient message cache misses.
  • Preserve last-good messages for the same session while the backing store refreshes, including the case where session info is temporarily missing.
  • Split timeline render readiness from session-action readiness and submit readiness: timeline can stay mounted, stop/abort/revert can use current session cache/status readiness, while submit/follow-up additionally wait for usable directory model/provider state.
  • Avoid permanent action locks: seeded provider data is usable before provider refresh completes, provider refresh starts before unrelated slow bootstrap work, and local model-selection persisted failures/timeouts only block auto-restore, not manual operation.
  • Keep provider refresh writes guarded by the latest refresh revision without query-cache in-flight dedupe leaving provider_ready stuck.
  • Keep session follow-up/revert/review helpers reading or snapshotting the correct current directory/client after the subtree stays mounted.
  • Keep revert/restore optimistic store writes and prompt writes pinned to the directory/prompt scope captured at click time.
  • Keep LocalProvider model-selection persistence scoped to the current directory without remounting the timeline subtree.
  • Restore same-session model selection again when the directory or directory-local persisted cache readiness changes, and bound cached LocalProvider directory stores.
  • Keep existing per-directory saved model selection authoritative, wait for persisted model-selection hydration before restore, and cover that priority with tests.
  • Refetch review artifact history when the execution directory changes, using a client created for the resource source directory.
  • Guard VCS diff tasks/results by execution directory so an old directory cannot block or overwrite the current review state.

Why

Fixes #432.

exit-worktree changes where subsequent tools execute. It should not make the current session timeline look like a new timeline. The previous route/provider/timeline keys allowed directory changes and short cache misses to tear down MessageTimeline, resetting staged rendering and scroll state.

Related Issue

Fixes #432

Follow-up architecture work: #441

Human Review Status

Pending. A human should make the final merge decision after reviewing the final diff and verification evidence.

Review Focus

Please focus on the session identity boundary and the remaining directory-scoped surfaces:

  • activeDirectory / route directory changes should not remount the session timeline.
  • messagesReady=false should be treated as timeline refresh/render state, not action readiness.
  • Composer submit/follow-up should wait for current directory model/provider readiness; stop/abort/revert should not be blocked by model/provider readiness.
  • Last-good messages must only be reused for the same session, and must survive same-session cache misses while session info reloads.
  • Local model selection cache, follow-up, revert, review helpers, and review artifact history should use current directory/client values after directory changes.

Risk Notes

Moderate UI lifecycle risk: DirectoryLayout no longer remounts the provider subtree on valid route directory changes. This is intentional for #432, but reviewers should check for assumptions that depended on directory-driven remounts.

Follow-up queue persistence is now session-scoped/global instead of workspace-scoped so a mounted session subtree cannot keep writing queued followups into the initial directory's workspace storage. Legacy workspace-scoped follow-up queues are intentionally not migrated because their saved execution directory can be stale after worktree exit.

Local model selection remains directory-scoped in this Stage 1 patch. If a session has a saved selection in the current directory, restore from the latest user message does not overwrite it; this preserves directory-local user intent. Broader {server, sessionID} identity, session-scoped cache, route canonicalization, and SyncProvider current-directory unpin/replace policy are tracked in #441.

This PR intentionally does not migrate timeline identity, last-good cache, or follow-up persistence to {serverKey, sessionID}. That needs a coherent SessionScope migration and is tracked in #441 rather than mixed into this Stage 1 stopgap.

Manual browser reproduction of the original long-timeline enter-worktree -> exit-worktree -> follow-up path is still not complete. This PR should be merged only if that gap is accepted or after a reviewer runs that UI path and confirms no same-session timeline unmount, no rendered-count reset, and no scrollTop jump near 0.

How To Verify

Latest focused P1 regression tests: 37 passed, 0 failed
Command: cd packages/app && bun test --preload ./happydom.ts ./src/components/prompt-input/readiness.test.ts ./src/pages/session/use-session-timeline-data.test.ts ./src/components/prompt-input/submit.test.ts

Local/sync isolated regression tests: 10 passed, 0 failed
Command: bun --cwd packages/app test ./src/context/local.test.ts ./src/context/sync-current-child.test.ts

App typecheck: ok
Command: bun --cwd packages/app run typecheck

GitHub CI for head ee84e798a: all required checks passed, including ci, codeql, desktop-smoke, e2e-artifacts, dependency-review, commit-lint, CodeRabbit, and CodeQL.

Note: one combined local test invocation that mixed the local/sync context tests with other test files hit existing Bun module-mock isolation pollution. The same test files passed when run in the isolated groups above.

Screenshots or Recordings

Not included. This PR changes lifecycle behavior for the session timeline; targeted unit coverage, diagnostics tests, typecheck, and GitHub CI were run. Manual browser reproduction of the original exit-worktree scroll path was not run in this turn.

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, scope, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for desktop, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • New Features

    • Stronger session readiness gating (action/abort) across composer and followups.
    • Improved timeline caching and "last good" message retention.
    • Persistent per-directory local session stores and safer followup drafts.
  • Bug Fixes

    • More stable session behavior across directory switches and cache misses.
    • Safer revert/restore flows with snapshot-backed rollback and recovery.
  • Performance

    • More resilient provider refresh and session-status hydration with better concurrency handling.

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 40c10114-dc92-4725-b882-5c0bf03bd7b3

📥 Commits

Reviewing files that changed from the base of the PR and between c8dcadc and 3531bfa.

📒 Files selected for processing (5)
  • packages/app/src/components/prompt-input/readiness.test.ts
  • packages/app/src/components/prompt-input/readiness.ts
  • packages/app/src/context/local.tsx
  • packages/app/src/context/sync.tsx
  • packages/app/src/pages/session.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/app/src/components/prompt-input/readiness.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/app/src/components/prompt-input/readiness.ts
  • packages/app/src/context/local.tsx
  • packages/app/src/context/sync.tsx

📝 Walkthrough

Walkthrough

Separates timeline/session identity from directory, makes many directory inputs reactive (Accessors) to avoid remounts, preserves timeline messages across transient cache misses, introduces action-ready gating across prompt/followup/revert flows, and adds per-directory persisted stores, session-status readiness, and retainDirectory APIs.

Changes

Session & Timeline / Reactive Directory / Action-Ready / Sync

Layer / File(s) Summary
Data Shape / Types
packages/app/src/pages/session/session-view-controller.ts, packages/app/src/context/global-sync/types.ts, packages/app/src/context/local.tsx
Introduces TimelineIdentity; removes directory from session view state; adds SessionStatusState + session_status_ready; adds per-directory saved-store types/constants.
Cache / Read Model
packages/app/src/pages/session/use-session-timeline-data.ts
Adds readTimelineMessages, readTimelineMessagesFromCache, timelineDataIdentity, currentSessionCacheReady, currentSessionActionReady; tracks lastGood messages for transient cache misses; includes localReady in model sync key.
Core Logic (controllers / mutations)
packages/app/src/pages/session/session-view-controller.ts, packages/app/src/pages/session/use-session-revert.ts, packages/app/src/pages/session/use-session-followups.ts
Session identity now derived from sessionID only; nextSessionViewState preserves readiness across same-session cache misses; revert/restore refactored to use per-call snapshot() (client/store/prompt) and gate on actionReady; followups use directory() accessor and followupDraftForDirectory.
Provider / Sync / Retention
packages/app/src/pages/directory-layout.tsx, packages/app/src/context/sync.tsx, packages/app/src/context/global-sync.tsx
DirectoryDataProvider and SDK wiring switch to Accessor<string> inputs; Show when={resolved()} no longer keyed to avoid subtree destroy; adds retainDirectory and syncChildOptionsForTarget to pin/peek directory-scoped stores without bootstrapping.
UI / Prompt gating
packages/app/src/components/prompt-input.tsx, packages/app/src/components/prompt-input/submit.ts, packages/app/src/components/prompt-input/readiness.ts
Adds actionReady/abortReady accessors and helpers; prompt input, paste, commands, variant/model controls, submit and abort are gated by readiness; new readiness helpers and tests.
VCS / Review state
packages/app/src/pages/session/use-session-review-state.ts
createSessionReviewState takes directory: () => string; VCS diffs keyed by vcsTaskKey(directory, mode); adds shouldApplyVcsDiffResult to validate directory+run before applying results.
Local persistence / pruning
packages/app/src/context/local.tsx
Replaces single persisted store with per-directory cached persisted stores (LRU pruning), readiness fallback timer, and helpers pruneLocalSavedStores, shouldRestoreLocalSessionModel.
Bootstrap / Provider refresh
packages/app/src/context/global-sync/bootstrap.ts
Bootstrap now initializes session_status_state/session_status_ready and runs a retry-wrapped session.status merge; provider refresh is revisioned and cancels stale results.
Wiring / Rendering
packages/app/src/pages/session/session-main-view.tsx, packages/app/src/pages/session.tsx, packages/app/src/pages/session/composer/*, packages/app/src/pages/directory-layout.tsx
MessageTimeline render gate relaxed (no timelineMessagesReady requirement); session wiring uses new readiness signals (sessionActionReady, submitReady, etc.); Directory/SDK wiring now passes accessors to avoid keyed remounts.
Tests / Verification
packages/app/src/pages/session/*.test.ts, packages/app/src/context/*, packages/app/src/components/prompt-input/*
Adds/updates unit tests for timeline message reuse, same-session cache miss preservation, readiness gating, revert payloads, followup rebasing, local-store pruning, provider bootstrap/status, and renderer-incident detection for exit-worktree-like scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant DirLayout as DirectoryLayout
    participant SDKProv as SDKProvider
    participant GlobalSync as GlobalSync
    participant Timeline as TimelineData
    participant Snapshot as RevertSnapshot
    participant Client as SessionClient

    Browser->>DirLayout: route /base64(dir) change
    DirLayout->>SDKProv: resolved() accessor (no keyed remount)
    SDKProv->>GlobalSync: retainDirectory(directory) / child({ pin: false })
    GlobalSync-->>SDKProv: { store, setStore, release }
    SDKProv->>Timeline: readTimelineMessagesFromCache(raw?, lastGood)
    Timeline-->>SDKProv: messages + lastGood (reused if raw missing)
    SDKProv->>Snapshot: snapshot() => { client, store, setStore, promptScope }
    Snapshot->>Client: client.session.revert/unrevert(payload)
    Client-->>Snapshot: success / error
    Snapshot->>GlobalSync: release()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

🐇
I nibble traces, stitch the thread,
When folders hop, the timeline's fed.
Cached crumbs beneath the log I keep,
No sudden tear, no midnight leap.
Quiet scroll — the rabbit guards your sleep.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and accurately summarizes the main objective: keeping the session timeline stable when exiting a worktree by changing directories.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, motivation, related issues, review focus, risks, verification steps, and checklist completion.
Linked Issues check ✅ Passed The PR directly addresses all primary objectives from issue #432: preventing session timeline teardown on directory changes, preserving messages and scroll state, treating transient cache misses as render state, ensuring current directory binding for actions, and adding diagnostics/tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the worktree exit regression. No unrelated refactors, dependencies, or out-of-scope file changes detected; all modifications support the core objective of timeline stability.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-i432-timeline-stability

@Astro-Han Astro-Han added bug Something isn't working P2 Medium priority app Application behavior and product flows ui Design system and user interface labels May 4, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves session state management by allowing the application to retain 'last good' messages and 'ready' states during transient cache misses, provided the session identity remains the same. It also simplifies session keys by removing directory prefixes and updates the DirectoryDataProvider to accept a directory accessor. A critical issue was identified in the directory layout where removing the 'keyed' property from a Show component while using its render argument causes stale data and a type mismatch, as the child components now expect an accessor rather than a raw string.

Comment thread packages/app/src/pages/directory-layout.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/app/src/pages/session/session-main-view.test.ts (1)

35-44: 💤 Low value

The test duplicates an existing assertion.

This test case (lines 35-44) uses identical inputs to the second assertion in "does not replace the new-session home or ready timeline" (lines 25-32), both checking routeReady: true with matching session IDs and expecting false. While the descriptive name clarifies the transient cache miss intent, consider consolidating or adding a distinguishing comment to explain why both tests exist.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/session/session-main-view.test.ts` around lines 35 -
44, The test "does not replace an already-ready same-session timeline during a
transient cache miss" duplicates the assertion already covered by "does not
replace the new-session home or ready timeline" because both call
shouldShowSessionOpeningState with identical inputs
(activeSessionID/routeSessionID/routeReady/timelineSessionID all "ses_target"
and routeReady: true); either remove this redundant test or modify its inputs to
reflect the intended transient cache-miss scenario (e.g., change one of the IDs
or routeReady flag) and/or add a brief clarifying comment above the test
explaining why a separate case is necessary; update the test name or inputs
accordingly and keep references to shouldShowSessionOpeningState and the two
test names when making the change.
packages/app/src/pages/session/use-session-timeline-data.test.ts (1)

5-11: 💤 Low value

Minor: The hardcoded sessionID in the helper could be parameterized for clarity.

The userMessage helper hardcodes sessionID: "ses_target", but test 2 uses it for "ses_source". While this doesn't affect test correctness (the function doesn't validate message sessionID), parameterizing it would improve readability.

♻️ Optionally parameterize the helper
-const userMessage = (id: string): Message =>
+const userMessage = (id: string, sessionID = "ses_target"): Message =>
   ({
     id,
     role: "user",
-    sessionID: "ses_target",
+    sessionID,
     time: { created: 1 },
   }) as Message
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/session/use-session-timeline-data.test.ts` around
lines 5 - 11, The helper userMessage currently hardcodes sessionID
("ses_target"); change it to accept an optional sessionID parameter (e.g.,
userMessage(id: string, sessionID = "ses_target"): Message) and use that value
for the sessionID field, then update any test calls that expect "ses_source" to
pass that value explicitly so intent is clear (leave callers that rely on the
default unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/app/src/pages/session/session-main-view.test.ts`:
- Around line 35-44: The test "does not replace an already-ready same-session
timeline during a transient cache miss" duplicates the assertion already covered
by "does not replace the new-session home or ready timeline" because both call
shouldShowSessionOpeningState with identical inputs
(activeSessionID/routeSessionID/routeReady/timelineSessionID all "ses_target"
and routeReady: true); either remove this redundant test or modify its inputs to
reflect the intended transient cache-miss scenario (e.g., change one of the IDs
or routeReady flag) and/or add a brief clarifying comment above the test
explaining why a separate case is necessary; update the test name or inputs
accordingly and keep references to shouldShowSessionOpeningState and the two
test names when making the change.

In `@packages/app/src/pages/session/use-session-timeline-data.test.ts`:
- Around line 5-11: The helper userMessage currently hardcodes sessionID
("ses_target"); change it to accept an optional sessionID parameter (e.g.,
userMessage(id: string, sessionID = "ses_target"): Message) and use that value
for the sessionID field, then update any test calls that expect "ses_source" to
pass that value explicitly so intent is clear (leave callers that rely on the
default unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 71ab5c67-a2ae-4500-8917-2fdbf868ca2e

📥 Commits

Reviewing files that changed from the base of the PR and between 3b61dca and af87f30.

📒 Files selected for processing (8)
  • packages/app/src/pages/directory-layout.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/session-main-view.test.ts
  • packages/app/src/pages/session/session-main-view.tsx
  • packages/app/src/pages/session/session-view-controller.test.ts
  • packages/app/src/pages/session/session-view-controller.ts
  • packages/app/src/pages/session/use-session-timeline-data.test.ts
  • packages/app/src/pages/session/use-session-timeline-data.ts

@Astro-Han

Copy link
Copy Markdown
Owner Author

Follow-up on the broader review checklist:

  • P1 exit-worktree regression coverage: the PR already emits and detects the relevant renderer diagnostics (session.timeline.unmount, visible rendered_count, and scroll-to-top incidents). I added a long-session cache-miss test so a transient same-session cache miss keeps the full last-good message window instead of dropping back to the initial batch.
  • P1 directory provider audit: SyncProvider is safe for this Stage 1 change because it reads sdk.directory through an accessor and createCurrentSyncChild tracks later directory values while the provider owner stays mounted. I added a regression test for that. LocalProvider was audited separately: its runtime reads use sdk.directory, but its Persist.workspace(sdk.directory, "model-selection", ...) target is still bound at initialization. That does not participate in timeline/messages/scroll and is not on the [Bug] exit-worktree triggers full session view teardown/rebuild, causing timeline flash, layout shift, and scroll-to-top #432 Stage 1 teardown path, so I am leaving the persistence migration to Stage 2 rather than changing model-selection storage semantics in this fix.
  • P2/P3 cleanup: fixed the non-keyed Solid <Show> accessor issue from Gemini, removed directory from the timeline identity controller input, and added comments around route readiness and last-good messages.
  • Existing diagnostics already cover incident.session_timeline_remount, incident.session_visible_messages_cleared, and incident.session_scroll_jump_to_top; I included src/context/renderer-diagnostics.test.ts in local verification.

Local verification after the follow-up:

bun --cwd packages/app test src/pages/session/session-view-controller.test.ts src/pages/session/use-session-timeline-data.test.ts src/pages/session/session-main-view.test.ts src/context/sync-current-child.test.ts src/context/renderer-diagnostics.test.ts
bun --cwd packages/app typecheck
git diff --check

All passed locally. The remaining Stage 2 work should split SessionTimelineStore (session-scoped) from ExecutionDirectoryContext (directory-scoped), including the LocalProvider model-selection persistence target.

@Astro-Han Astro-Han added P1 High priority and removed P2 Medium priority labels May 4, 2026
@Astro-Han

Copy link
Copy Markdown
Owner Author

Follow-up for the latest review:

  • Fixed the P1 stale directory/client path for the named helpers. createSessionFollowups, createSessionRevert, and createSessionReviewState now receive accessors/current getters instead of initialization snapshots.
  • Follow-up send now rebases the queued draft to the current execution directory before calling sendFollowupDraft, and reads the current SDK client at send time.
  • Follow-up queue persistence is no longer workspace-directory scoped. It now uses session-scoped/global persistence, so a mounted session subtree cannot keep reading or writing the initial directory's follow-up store after sdk.directory changes.
  • Added TimelineIdentity as a small alias around session identity, keeping directory out of the timeline key while leaving room for a later {server, sessionID} upgrade.
  • Updated the PR description to reflect the actual verification status, including that no manual browser reproduction was run.

Local verification:

bun --cwd packages/app test src/pages/session/use-session-followups.test.ts src/pages/session/session-view-controller.test.ts src/pages/session/use-session-timeline-data.test.ts src/pages/session/session-main-view.test.ts src/context/sync-current-child.test.ts src/context/renderer-diagnostics.test.ts
bun --cwd packages/app typecheck
git diff --check

All passed locally.

@Astro-Han

Copy link
Copy Markdown
Owner Author

Final follow-up for the repeated review checklist:

  • Closed the remaining review subagent.
  • Added an identity scope to lastGoodMessages, so a same sessionID under a different timeline/message identity scope will not reuse old messages.
  • Added a diagnostics fixture for an exit-worktree-like same-session refresh: no timeline unmount, rendered count remains high, and scroll stays near bottom without a near-zero jump.
  • Re-ran local verification after these changes:
bun --cwd packages/app test src/pages/session/use-session-followups.test.ts src/pages/session/session-view-controller.test.ts src/pages/session/use-session-timeline-data.test.ts src/pages/session/session-main-view.test.ts src/context/sync-current-child.test.ts src/context/renderer-diagnostics.test.ts
bun --cwd packages/app typecheck
git diff --check

Result: 852 app tests passed locally, typecheck passed, diff check passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/pages/session/use-session-followups.ts (1)

77-90: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Existing queued follow-ups are silently dropped on upgrade — confirm this is intentional.

The old persistence key was constructed by Persist.workspace(input.directory, …), which presumably produced a directory-namespaced key (e.g. "<dir>:followup.v1"). The migration array ["followup.v1"] is flat and non-namespaced, so it cannot locate those old keys; users upgrading with queued-but-unsent items will silently lose them.

If this data loss is an accepted trade-off for this fix, a short inline comment (// legacy workspace-scoped keys are intentionally not migrated) would prevent future confusion when the Stage 2 migration work resumes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/pages/session/use-session-followups.ts` around lines 77 -
90, The current persisted store uses Persist.global("session-followup.v1",
["followup.v1"]) which will not find legacy workspace-scoped keys previously
created via Persist.workspace(...), causing queued follow-ups to be dropped;
either migrate those legacy keys by adding the workspace-scoped legacy key(s) to
the migration list (e.g. include the workspace-prefixed `"followup.v1"` variants
or derive the workspace namespace used previously) so persisted(...) can pick
them up, or if data loss is intentional add a clear inline comment next to the
persisted/Persist.global call (referencing persisted, Persist.global,
Persist.workspace and the "session-followup.v1"/"followup.v1" keys) stating that
legacy workspace-scoped keys are intentionally not migrated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/app/src/pages/session/use-session-followups.ts`:
- Around line 77-90: The current persisted store uses
Persist.global("session-followup.v1", ["followup.v1"]) which will not find
legacy workspace-scoped keys previously created via Persist.workspace(...),
causing queued follow-ups to be dropped; either migrate those legacy keys by
adding the workspace-scoped legacy key(s) to the migration list (e.g. include
the workspace-prefixed `"followup.v1"` variants or derive the workspace
namespace used previously) so persisted(...) can pick them up, or if data loss
is intentional add a clear inline comment next to the persisted/Persist.global
call (referencing persisted, Persist.global, Persist.workspace and the
"session-followup.v1"/"followup.v1" keys) stating that legacy workspace-scoped
keys are intentionally not migrated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 025561ad-ef3f-4c33-97ff-de16aeb8e42a

📥 Commits

Reviewing files that changed from the base of the PR and between af87f30 and 9139287.

📒 Files selected for processing (11)
  • packages/app/src/context/renderer-diagnostics.test.ts
  • packages/app/src/context/sync-current-child.test.ts
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/session-view-controller.test.ts
  • packages/app/src/pages/session/session-view-controller.ts
  • packages/app/src/pages/session/use-session-followups.test.ts
  • packages/app/src/pages/session/use-session-followups.ts
  • packages/app/src/pages/session/use-session-revert.ts
  • packages/app/src/pages/session/use-session-review-state.ts
  • packages/app/src/pages/session/use-session-timeline-data.test.ts
  • packages/app/src/pages/session/use-session-timeline-data.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/app/src/pages/session/session-view-controller.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/app/src/pages/session/use-session-timeline-data.ts
  • packages/app/src/pages/session.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/app/src/context/local.tsx (1)

123-147: ⚡ Quick win

The "__unknown__" bucket pattern is architecturally guarded but represents defensive design weakness.

LocalProvider is instantiated only within <Show when={resolved()}> in directory-layout.tsx (line 73), where resolved() returns "" if the directory param is missing or invalid. This guard ensures sdk.directory will be truthy whenever LocalProvider initializes. However, the fallback pattern at line 124 (const key = directory || "__unknown__") creates unnecessary risk and violates defensive design principles.

Even with the current guard in place, if the assumption about route stability changes, or if the context is used in an unexpected way, unresolved state could persist and leak between workspaces. Better approach: assert that sdk.directory is always defined when needed, or explicitly reject unresolved directory state rather than silently persisting it to a shared bucket.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/context/local.tsx` around lines 123 - 147, The code uses a
fallback bucket "__unknown__" in savedFor which can cause cross-workspace
leakage; remove this fallback and enforce that a valid directory is provided by
changing savedFor to assert/throw when directory is falsy instead of using
"__unknown__", update any callers (the createMemo saved = createMemo(() =>
savedFor(sdk.directory).store) and setSavedSession which calls
savedFor(sdk.directory).setStore) to ensure they only call savedFor with a
defined sdk.directory, and keep existing cleanup/pruneLocalSavedStores logic
intact (references: savedFor, createSavedEntry, savedStores, setSavedSession,
pruneLocalSavedStores).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/app/src/context/local.tsx`:
- Around line 123-147: The code uses a fallback bucket "__unknown__" in savedFor
which can cause cross-workspace leakage; remove this fallback and enforce that a
valid directory is provided by changing savedFor to assert/throw when directory
is falsy instead of using "__unknown__", update any callers (the createMemo
saved = createMemo(() => savedFor(sdk.directory).store) and setSavedSession
which calls savedFor(sdk.directory).setStore) to ensure they only call savedFor
with a defined sdk.directory, and keep existing cleanup/pruneLocalSavedStores
logic intact (references: savedFor, createSavedEntry, savedStores,
setSavedSession, pruneLocalSavedStores).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3293ad3b-e530-41e6-b14e-74a1c99e93cf

📥 Commits

Reviewing files that changed from the base of the PR and between 9139287 and f5608a5.

📒 Files selected for processing (21)
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/submit.test.ts
  • packages/app/src/components/prompt-input/submit.ts
  • packages/app/src/context/global-sync/bootstrap.test.ts
  • packages/app/src/context/global-sync/bootstrap.ts
  • packages/app/src/context/global-sync/child-store.ts
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/types.ts
  • packages/app/src/context/local.test.ts
  • packages/app/src/context/local.tsx
  • packages/app/src/context/sync.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/pages/session/session-composer-region.tsx
  • packages/app/src/pages/session/use-session-followups.test.ts
  • packages/app/src/pages/session/use-session-followups.ts
  • packages/app/src/pages/session/use-session-revert.ts
  • packages/app/src/pages/session/use-session-review-state.test.ts
  • packages/app/src/pages/session/use-session-review-state.ts
  • packages/app/src/pages/session/use-session-timeline-data.test.ts
  • packages/app/src/pages/session/use-session-timeline-data.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/local.test.ts
  • packages/app/src/context/global-sync/child-store.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/pages/session/use-session-followups.ts

@Astro-Han

Copy link
Copy Markdown
Owner Author

CodeRabbit follow-up status:

  • packages/app/src/pages/session/use-session-followups.ts outside-diff comment about legacy workspace-scoped follow-up queue migration: intentionally not migrated. The PR now calls this out in Risk Notes. The old queue key was directory-scoped, and carrying it forward after worktree exit can send stale execution-directory context. Stage 1 prefers dropping stale queued drafts over replaying them in the wrong directory; broader {serverKey, sessionID} queue migration is tracked in [Task] Stage 2: separate session identity from execution directory #441.
  • packages/app/src/context/local.tsx "__unknown__" bucket comment: fixed in 5b0b9ec. localSavedStoreKey() now returns undefined for missing directories, savedFor() no-ops instead of persisting to a fallback bucket, and tests cover the no-fallback behavior.
  • Local persisted model-selection hydration risk: fixed in 5b0b9ec. SavedEntry keeps the persisted(...) ready accessor, and session.restore() refuses to restore from the latest user message until the directory-local store is hydrated. Added local.test.ts coverage for savedReady=false.
  • Queued follow-up rebase path/context concern: covered in 5b0b9ec. The rebasing test now includes an @file prompt entry plus file/comment context and asserts only sessionDirectory changes while prompt/context path metadata is preserved.
  • Earlier nitpicks in session-main-view.test.ts and use-session-timeline-data.test.ts: treated as low-value cleanup, not changed in this Stage 1 patch because they do not affect [Bug] exit-worktree triggers full session view teardown/rebuild, causing timeline flash, layout shift, and scroll-to-top #432 behavior or correctness.

Verification after the latest fixes:

bun --cwd packages/app run test:unit
874 passed, 0 failed

bun --cwd packages/app test --preload ./happydom.ts ./src/context/local.test.ts ./src/pages/session/use-session-followups.test.ts ./src/pages/session/use-session-revert.test.ts ./src/pages/session/use-session-timeline-data.test.ts ./src/context/global-sync/bootstrap.test.ts ./src/pages/session/session-running-state.test.ts ./src/components/prompt-input/submit.test.ts ./src/pages/session/use-session-review-state.test.ts
61 passed, 0 failed

bun --cwd packages/app run typecheck
ok

git diff --check
ok

GitHub checks are green on head 5b0b9ecebce6fa0ed1cb0ff1a88a9a0a3faf11c8. Manual browser reproduction of the original long-timeline enter-worktree -> exit-worktree -> follow-up path is still not claimed as complete and remains called out in the PR description as an accepted-risk/manual-verification item.

@Astro-Han

Copy link
Copy Markdown
Owner Author

Review follow-up for the latest P1/P2/P3 batch:

Fixed in 9d2245f:

  • LocalProvider async persisted ready race: LocalProvider.session.ready() now exposes the directory-local persisted ready accessor, and timelineModelSyncKey(...) includes that ready state. If restore is skipped while the directory store is still hydrating, the same last-user-message restore path reruns when the store becomes ready. Added timelineModelSyncKey coverage for the ready transition.
  • Revert/restore prompt pollution after navigation: the revert snapshot now captures both the prompt value and prompt scope at click time. Optimistic prompt writes, restore resets, and failure rollbacks write to that captured prompt scope instead of whichever session/directory is current when the async request settles.

Still not fixed in this PR, by scope:

Verification:

bun --cwd packages/app run typecheck
ok

bun --cwd packages/app test --preload ./happydom.ts ./src/context/local.test.ts ./src/pages/session/session-model-helpers.test.ts ./src/pages/session/use-session-timeline-data.test.ts ./src/pages/session/use-session-revert.test.ts ./src/pages/session/use-session-followups.test.ts ./src/components/prompt-input/submit.test.ts
43 passed, 0 failed

bun --cwd packages/app run test:unit
875 passed, 0 failed

git diff --check
ok

@Astro-Han

Copy link
Copy Markdown
Owner Author

Latest review follow-up:

Fixed in 471d3d9:

  • Review VCS pending task race: vcsTask / vcsRun are now keyed by {directory, mode} via vcsTaskKey(...). A pending old-directory diff no longer blocks a new-directory diff from starting after directory changes, and stale result application still checks requested directory plus run id.
  • Status hydration snapshot race: session.status() snapshot application now merges through mergeSessionStatusSnapshot(...), preserving active busy / retry statuses already present in the store when the snapshot resolves. This prevents an older idle/full snapshot from clearing a busy event that arrived during hydration.

Not fixed in this PR:

Why P1s keep appearing after many commits:

This PR deliberately removed directory-driven remounts. That fixed the timeline flash root path, but it also exposed places that had been relying on remount as an implicit cleanup boundary: pending VCS promises, status hydration snapshots, LocalProvider persisted ready, revert prompt rollback, follow-up queue state, etc. The commits are not repeatedly fixing the same defect; each review is uncovering another hidden lifecycle assumption that became visible once the provider subtree stays mounted.

Verification:

bun --cwd packages/app test --preload ./happydom.ts ./src/pages/session/use-session-review-state.test.ts ./src/context/global-sync/bootstrap.test.ts ./src/pages/session/use-session-timeline-data.test.ts ./src/components/prompt-input/submit.test.ts ./src/pages/session/use-session-followups.test.ts ./src/pages/session/use-session-revert.test.ts
47 passed, 0 failed

bun --cwd packages/app run typecheck
ok

bun --cwd packages/app run test:unit
877 passed, 0 failed

git diff --check
ok

@Astro-Han

Copy link
Copy Markdown
Owner Author

Latest review follow-up:

Fixed in b8949b52d:

  • bootstrapDirectory now snapshots active session statuses at the start of the status refresh. Stale pre-request busy / retry entries no longer override the fresh session.status() snapshot, while active events that arrive during the request are preserved. Added regression coverage for both cases.
  • Current-directory store retention is now tied to the live directory accessor instead of the long-lived provider owner. The current child lookup skips owner pinning and uses explicit retain/release on directory changes, so old unpinned directory stores can be evicted by MAX_DIR_STORES / TTL. Added regression coverage for eviction beyond the store limit.

Not changed in this commit:

  • P2 readiness gates for commands/provider/local model, follow-up {serverKey, sessionID} scoping, revert async integration coverage, and VCS diff keying are broader Stage 2 hardening items. They do not block the two concrete P1 regressions above and should be handled as focused follow-ups rather than mixed into this patch.
  • P3 naming/architecture cleanup is intentionally deferred for the same reason.

@Astro-Han

Copy link
Copy Markdown
Owner Author

Addressed the latest P1 review in 7b106b744.

What changed:

  • actionReady now also waits for the current directory's local model-selection store (local.session.ready()) and provider hydration (provider_ready) before enabling submit/manual follow-up/revert paths. This closes the directory-switch window where a prompt could send with fallback/global/old model selection before restore completed.
  • Non-current directory sync target access now uses { bootstrap: false, pin: false }, so optimistic add/remove and other write-target access do not owner-pin old directory child stores after SyncProvider stays mounted.

Verification:

  • bun test ./src/pages/session/use-session-timeline-data.test.ts ./src/context/sync-current-child.test.ts from packages/app: 21 pass, 0 fail.
  • bun run typecheck from packages/app: pass.

No P1 was rejected. The P2/P3 items around non-creating peekExisting, provider scope refactor, follow-up draft validation, and handoff key scope remain valid follow-up hardening, but they are broader than the current P1 regression fix.

@Astro-Han

Copy link
Copy Markdown
Owner Author

Addressed the latest review in 13f8b52cf.

What changed:

  • Provider readiness is no longer a hard dependency on the final provider refetch. actionReady now accepts seeded current-directory provider data as usable, and provider refresh starts before unrelated slow bootstrap work such as permission/question/MCP hydration.
  • Local model-selection persisted readiness is no longer a permanent manual-action lock. If persisted init fails or times out, manual actions can proceed; auto-restore still requires the persisted store to actually be ready, so a failed/hung restore does not overwrite directory-local choices.
  • Updated PR verification text so the evidence reflects the latest commits.

Verification:

  • bun test ./src/pages/session/use-session-timeline-data.test.ts ./src/context/sync-current-child.test.ts ./src/context/local.test.ts ./src/context/global-sync/bootstrap.test.ts from packages/app: 35 pass, 0 fail.
  • bun run typecheck from packages/app: pass.

No P1 was rejected. The P2 items around home composer readiness, command-list scoping for queued followups, non-creating peek, and shared provider scope remain valid follow-up hardening unless you want this PR to expand beyond the Stage 1 timeline/action-readiness fix.

@Astro-Han

Copy link
Copy Markdown
Owner Author

Addressed the latest P1 review in 94e799d2e.

What changed:

  • Split readiness into two gates:
    • sessionActionReady: current session cache/status readiness only, used for stop/abort/revert-style session actions.
    • submitReady: sessionActionReady plus directory-local model-selection/provider usability, used for submit and follow-up sending.
  • PromptInput now has a separate abortReady, so a running session can still be stopped when submit is blocked by model/provider readiness.
  • Provider refresh no longer uses queryClient.fetchQuery with the same provider key for command-style refresh. Each bootstrap starts its own provider list request with an external revision guard, so an older in-flight request cannot cause a later refresh to skip writing provider_ready.
  • Stale provider refresh failures no longer log/toast after their revision is obsolete.

Verification:

  • bun test ./src/pages/session/use-session-timeline-data.test.ts ./src/context/global-sync/bootstrap.test.ts ./src/components/prompt-input/submit.test.ts from packages/app: 39 pass, 0 fail.
  • bun test ./src/context/local.test.ts ./src/context/sync-current-child.test.ts from packages/app: 10 pass, 0 fail.
  • bun run typecheck from packages/app: pass.

No P1 was rejected. P2 items around new-session/workspace readiness, unified provider scope, delayed persisted load dirty tracking, and follow-up command scoping remain valid follow-up hardening beyond this Stage 1 PR.

@Astro-Han

Copy link
Copy Markdown
Owner Author

Handled the latest review in c8dcadc.

Reclassification:

  • P0: none.
  • P1 accepted and fixed: keyboard stop now uses abort readiness when stopping, while submit still uses submit readiness. Home/new-session composer now uses workspace submit readiness instead of hard-coded true, so current-directory local/model/provider readiness gates new-session submit.
  • P2 partially fixed where it shared the same layer: Send button disabled now keys off stopping() instead of broad working(). The slash-command target-directory command table, provider source unification, delayed persisted overwrite guard, and CI mock-isolation cleanup remain follow-up scope because they are broader than this Stage 1 readiness regression.
  • P3 deferred: diagnostics split and empty-provider UI are useful cleanup, but not required to close the current P1 regressions.

Verification:

  • cd packages/app && bun test --preload ./happydom.ts ./src/components/prompt-input/readiness.test.ts ./src/pages/session/use-session-timeline-data.test.ts ./src/components/prompt-input/submit.test.ts -> 36 passed, 0 failed.
  • cd packages/app && bun run typecheck -> passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/context/local.tsx (1)

234-247: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wait for the target local store before replaying the handoff.

Lines 241-247 treat saved().session[session] === undefined as authoritative even while the directory-local persisted(...) store is still hydrating. On a cold directory switch, that can overwrite an existing saved session model with the handoff snapshot before the persisted value has been read.

Suggested fix
     createEffect(() => {
       const session = id()
       if (!session) return

       const key = handoffKey(sdk.directory, session)
       const next = handoff.get(key)
       if (!next) return
-      if (saved().session[session] !== undefined) {
+      const savedEntry = savedFor(sdk.directory)
+      if (!savedEntry?.readyForAction()) return
+      if (savedEntry.store.session[session] !== undefined) {
         handoff.delete(key)
         return
       }

-      setSavedSession(session, clone(next))
+      savedEntry.setStore("session", session, clone(next))
       handoff.delete(key)
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/context/local.tsx` around lines 234 - 247, The replay logic
in createEffect currently assumes saved().session[session] is authoritative and
may overwrite a persisted session before the directory-local persisted store has
finished hydrating; modify the handler so it waits for the directory-local
persisted store to be ready before treating saved().session[session] as
definitive — for example, query or check the persisted(...) store/readiness for
sdk.directory (or a persisted.hasHydrated flag) and only run the
saved().session[session] !== undefined guard after the persisted value is
available; update the block around id(), handoffKey(...), handoff.get(...),
setSavedSession(...) and handoff.delete(...) to first await or verify
persisted(sdk.directory) hydration or existing persisted(session) value.
🧹 Nitpick comments (1)
packages/app/src/context/sync-current-child.test.ts (1)

90-99: 💤 Low value

Optional: cover currentDirectory: undefined in syncChildOptionsForTarget cases.

You already exercise same-dir, undefined-target, and different-target. The "current is undefined, target is set" branch (early bootstrap before sdk.directory resolves) flips to non-current behavior and is worth pinning down given how readiness/hydration races feature in this PR.

🧪 Proposed addition
     expect(syncChildOptionsForTarget({ currentDirectory: "/repo", targetDirectory: "/repo-worktree" })).toEqual({
       bootstrap: false,
       pin: false,
     })
+    expect(syncChildOptionsForTarget({ currentDirectory: undefined, targetDirectory: "/repo" })).toEqual({
+      bootstrap: false,
+      pin: false,
+    })
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/context/sync-current-child.test.ts` around lines 90 - 99,
Add a test case in the syncChildOptionsForTarget suite to cover the branch where
currentDirectory is undefined and targetDirectory is set (to simulate early
bootstrap before sdk.directory resolves); specifically, call
syncChildOptionsForTarget({ currentDirectory: undefined, targetDirectory:
"/repo-worktree" }) and assert it returns { bootstrap: false, pin: false }
(matching the non-current-directory behavior already asserted for
"/repo-worktree"); update the test block that contains syncChildOptionsForTarget
to include this expect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/context/local.tsx`:
- Around line 466-468: session.ready() currently returns
savedFor(sdk.directory)?.readyForAction() which can become true after a
timeout/rejected hydration while restore() still requires savedEntry?.ready(),
causing a mismatch; change ready() to mirror restore()’s check (use
savedEntry?.ready() or the same readiness predicate restore uses) so readiness
only becomes true when the session has actually been seeded, and apply the same
fix to the other readiness-related logic around the restore flow (the block
referencing savedEntry and restore at lines ~485-499) to keep both paths
consistent.

In `@packages/app/src/context/sync.tsx`:
- Around line 215-218: retainTarget may pass undefined into
globalSync.retainDirectory causing children.pin(undefined) errors; add a guard
in retainTarget (the function defined as const retainTarget) that checks whether
directory || sdk.directory resolves to a truthy string and if not returns a
no-op release handle (i.e., an object with a no-op release/unpin function)
instead of calling globalSync.retainDirectory; this prevents calling
globalSync.retainDirectory or children.pin with undefined during
bootstrap/teardown.

---

Outside diff comments:
In `@packages/app/src/context/local.tsx`:
- Around line 234-247: The replay logic in createEffect currently assumes
saved().session[session] is authoritative and may overwrite a persisted session
before the directory-local persisted store has finished hydrating; modify the
handler so it waits for the directory-local persisted store to be ready before
treating saved().session[session] as definitive — for example, query or check
the persisted(...) store/readiness for sdk.directory (or a persisted.hasHydrated
flag) and only run the saved().session[session] !== undefined guard after the
persisted value is available; update the block around id(), handoffKey(...),
handoff.get(...), setSavedSession(...) and handoff.delete(...) to first await or
verify persisted(sdk.directory) hydration or existing persisted(session) value.

---

Nitpick comments:
In `@packages/app/src/context/sync-current-child.test.ts`:
- Around line 90-99: Add a test case in the syncChildOptionsForTarget suite to
cover the branch where currentDirectory is undefined and targetDirectory is set
(to simulate early bootstrap before sdk.directory resolves); specifically, call
syncChildOptionsForTarget({ currentDirectory: undefined, targetDirectory:
"/repo-worktree" }) and assert it returns { bootstrap: false, pin: false }
(matching the non-current-directory behavior already asserted for
"/repo-worktree"); update the test block that contains syncChildOptionsForTarget
to include this expect.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a5c3336-6738-4379-9a82-a61b85f9e338

📥 Commits

Reviewing files that changed from the base of the PR and between f5608a5 and c8dcadc.

📒 Files selected for processing (28)
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/readiness.test.ts
  • packages/app/src/components/prompt-input/readiness.ts
  • packages/app/src/components/prompt-input/submit.test.ts
  • packages/app/src/components/prompt-input/submit.ts
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/context/global-sync/bootstrap.test.ts
  • packages/app/src/context/global-sync/bootstrap.ts
  • packages/app/src/context/global-sync/child-store.test.ts
  • packages/app/src/context/global-sync/child-store.ts
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/types.ts
  • packages/app/src/context/local.test.ts
  • packages/app/src/context/local.tsx
  • packages/app/src/context/sync-current-child.test.ts
  • packages/app/src/context/sync.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/pages/session/session-composer-region.tsx
  • packages/app/src/pages/session/session-model-helpers.test.ts
  • packages/app/src/pages/session/session-model-helpers.ts
  • packages/app/src/pages/session/use-session-followups.test.ts
  • packages/app/src/pages/session/use-session-revert.test.ts
  • packages/app/src/pages/session/use-session-revert.ts
  • packages/app/src/pages/session/use-session-review-state.test.ts
  • packages/app/src/pages/session/use-session-review-state.ts
  • packages/app/src/pages/session/use-session-timeline-data.test.ts
  • packages/app/src/pages/session/use-session-timeline-data.ts
✅ Files skipped from review due to trivial changes (6)
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/pages/session/session-model-helpers.ts
  • packages/app/src/components/prompt-input/readiness.test.ts
  • packages/app/src/pages/session/session-model-helpers.test.ts
  • packages/app/src/pages/session/use-session-followups.test.ts
  • packages/app/src/pages/session/use-session-timeline-data.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/app/src/context/global-sync/child-store.ts
  • packages/app/src/pages/session/use-session-review-state.test.ts
  • packages/app/src/context/local.test.ts
  • packages/app/src/pages/session/session-composer-region.tsx
  • packages/app/src/components/prompt-input/submit.ts
  • packages/app/src/components/prompt-input/submit.test.ts
  • packages/app/src/pages/session/use-session-review-state.ts
  • packages/app/src/pages/session/use-session-timeline-data.ts

Comment thread packages/app/src/context/local.tsx
Comment thread packages/app/src/context/sync.tsx
@Astro-Han

Copy link
Copy Markdown
Owner Author

Handled latest review in ee84e79.

Reclassification:

  • P0: none.
  • P1 accepted and fixed: non-stop keys now remain blocked while submit/action readiness is false. Only stop-related Enter/Escape can pass via abortReady.
  • P2 accepted where it shared the same readiness layer: home composer now stays rendered (ready remains render/lifecycle state) while actionReady gates new-session submit with workspaceSubmitReady. The deeper component-level keyboard integration test request is useful but not required for the P1 fix because the readiness branch is now covered by the extracted helper and the submit hook tests. Escape semantics remain existing behavior: Escape aborts a running session when abort is ready; otherwise it follows the existing popover/shell/blur path.
  • P3 handled for stale PR evidence: PR verification head was updated to ee84e79. Tooltip helper extraction is deferred cleanup.

Verification:

  • cd packages/app && bun test --preload ./happydom.ts ./src/components/prompt-input/readiness.test.ts ./src/pages/session/use-session-timeline-data.test.ts ./src/components/prompt-input/submit.test.ts -> 37 passed, 0 failed.
  • cd packages/app && bun run typecheck -> passed.

@Astro-Han

Copy link
Copy Markdown
Owner Author

Resolved the two remaining CodeRabbit conversations after fixing them in 3531bfa.

  • local.tsx: handoff replay now waits for the target directory-local saved store to be action-ready before treating saved session state as authoritative; restore now uses the same ready-for-action predicate as the submit gate.
  • sync.tsx: retainTarget() now guards an empty resolved directory and returns a no-op retained handle instead of pinning undefined.

Verification:

  • cd packages/app && bun test --preload ./happydom.ts ./src/context/local.test.ts ./src/context/sync-current-child.test.ts -> 10 passed, 0 failed.
  • cd packages/app && bun run typecheck -> passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working P1 High priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] exit-worktree triggers full session view teardown/rebuild, causing timeline flash, layout shift, and scroll-to-top

1 participant