Skip to content

fix(app): isolate home composer from session-only docks (#753) - #759

Merged
Astro-Han merged 3 commits into
devfrom
claude/home-composer-region
May 19, 2026
Merged

fix(app): isolate home composer from session-only docks (#753)#759
Astro-Han merged 3 commits into
devfrom
claude/home-composer-region

Conversation

@Astro-Han

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

Copy link
Copy Markdown
Owner

Summary

Split a dedicated HomeComposerRegion off from SessionComposerRegion so the home route can no longer mount session-only docks with stale composer state. Removes the visible 1-frame full-height todo-dock flash that appeared when navigating session → home.

Why

session.tsx is a single route component serving both /:dir/session and /:dir/session/:id. It constructed one composer = createSessionComposerState({...}) and passed it into the same SessionComposerRegion for both variants. On a session→home transition, the home variant re-mounted while composer.dock() was still stale-true from the session view; useSpring initialises from the current target on mount (no animate-from-0), so SessionTodoDock rendered immediately at full height before the todos effect re-ran and animated it out — a phantom dock collapse on a page that should never show the dock.

The fix moves Home onto a region that never receives SessionComposerState and never imports SessionTodoDock / SessionQuestionDock / SessionPermissionContent / SessionRevertDock / SessionFollowupDock. The boundary is structural, so the next field added to SessionComposerState cannot leak the same way.

Plan iteration: 10 review rounds (3× Claude+Codex crosscheck + 5× GPT Pro external) before code was written; final plan at #753 (comment). Post-implementation crosscheck (Claude+Codex) returned 0 P0/P1; P2 findings folded into the cleanup commit.

Related Issue

Fixes #753

Human Review Status

Pending

Review Focus

  • The boundary itself: home-composer-region.tsx has zero imports from session-only docks and no SessionComposerState prop (locked by an import-grep contract test).
  • composerHome rewire in session.tsx: now points at renderHomeComposerRegion instead of branching renderComposerRegion("home", ...).
  • The narrowed session-only SessionComposerRegion still serves child-session "back to parent" + disabled-prompt path (uses useNavigate, useSync, useSessionRouteKey for route.params.dir — preserved intentionally; only the home() memo and home-only classList branches are removed).
  • E2E shape: packages/app/e2e/session/session-composer-dock.spec.ts:1691 installs a MutationObserver on document.body BEFORE clicking the sidebar new-session button and asserts zero transient [data-component="session-todo-dock"] additions until [data-component="session-new-home"] is visible. This catches the one-frame flash that a steady-state toHaveCount(0) assertion would miss.

Risk Notes

  • Known residual, out of scope for this PR: a fast session_A → home → session_B sequence may still mount session_B's region while composer.dock() is stale-true from session_A's snapshot. Different navigation pattern, separate flash surface. Will be tracked as a follow-up issue post-merge. The cheapest remedy when it surfaces is a per-call { from: 0 } option on useSpring applied at session-composer-region.tsx.
  • Skipped conditional — visible UI manual check with screenshots/recordings: the bug is a 1-frame DOM mutation during a route transition; a static screenshot cannot capture it, and a 25fps video (40ms per frame) would straddle the ~30ms flash window unreliably (a missing capture would not prove the fix; a captured frame would not prove regression). The MutationObserver assertion at session-composer-dock.spec.ts:1691 is the precise dynamic contract — TDD-verified red against pre-fix code (the dock element was recorded in addedNodes during the transition) and green after the split.
  • Skipped conditional — macOS/Windows platform impact: no platform/packaging/updater/signing/paths/shell/permissions surface was touched. Pure SolidJS route component split.
  • Skipped conditional — docs/release notes/dependencies/permissions/credentials/deletion/generated/local files: none of those surfaces were touched.

How To Verify

app typecheck (tsgo -b):           passed
eslint (root lint):                passed
shell-frame-contract test:         9/9 passed (covers the new boundary)
session-composer-dock e2e:         41/41 passed incl. new "todo dock does not flash on home" case
home.spec.ts / theme-helper / a11y-chip e2e: clean (1 pre-existing failure on `active workspace has a check icon` exists on origin/dev too, unrelated)
TDD verification:                  new e2e runs RED on pre-fix code, GREEN after the split (manually stashed impl to confirm)

Screenshots or Recordings

Not applicable — the bug is a single-frame DOM mutation during route transition; static screenshots cannot capture it. The new MutationObserver-based e2e at packages/app/e2e/session/session-composer-dock.spec.ts:1691 records the dock element being added to the DOM during the transition and asserts it is empty post-fix. Verified red→green.

Checklist

How to use this checklist:

  • Tick a box by replacing [ ] with [x]. Do not edit, add, or remove items.
  • The bot-applied label items can only be honestly ticked AFTER the PR is opened and the labeler / priority-triage bots have run — return to the PR description and tick them then.
  • Most items are required. The few that are conditional are explicitly marked (conditional); for those, leave unticked if they truly do not apply and explain why in Risk Notes. All other items must be ticked before requesting human review.
  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • 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

  • Bug Fixes

    • Fixed an issue where the session todo dock would briefly appear during navigation to the home view.
  • Tests

    • Added end-to-end tests to verify the dock no longer flashes on navigation and prevent future regressions.

Review Change Stack

Astro-Han added 2 commits May 19, 2026 16:30
The session route component serves both /:dir/session and /:dir/session/:id
and previously rendered the same SessionComposerRegion (with the shared
composer state) for both. On a session→home navigation the home variant
re-mounted while composer.dock() was still stale-true from the session
view; useSpring initialised at 1, so SessionTodoDock rendered immediately
at full height and then animated out — a phantom collapse on a page that
should never show the dock.

Split a dedicated HomeComposerRegion that has no SessionComposerState
prop and does not import any session-only dock components, so the home
path cannot mount a session dock by construction. The session variant of
SessionComposerRegion + the SessionPageComposerRegion wrapper narrow
their variant union to "session" only; renderHomeComposerRegion takes
just the props PromptInput actually needs in homeMode. Page-level
composer / timeline / followups / revert hook still run on the home
variant — they are needed for the moment a session id appears — but
nothing in the home composer subtree can read SessionComposerState.

Coverage:
- new e2e (session-composer-dock.spec.ts): seeds a todo dock, installs a
  MutationObserver rooted at document.body before clicking the sidebar
  "new session" button, asserts no addedNodes contain
  [data-component="session-todo-dock"] during the transition. Verified
  red against pre-fix code and green after.
- shell-frame-contract.test.ts: locks the new `renderHomeComposerRegion`
  literal + JSX `variant="session"` attribute + `not.toContain('| "home"')`
  on session.tsx; adds an import-only grep that asserts
  home-composer-region.tsx does not import any of SessionTodoDock,
  SessionQuestionDock, SessionPermissionContent, SessionRevertDock,
  SessionFollowupDock, SessionComposerState, or their module paths.

Known residual (out of scope for this issue, follow-up will be filed):
a fast session_A → home → session_B sequence may still mount session_B's
region while composer.dock() is stale-true from session_A. Different
navigation pattern, different fix surface.

Local verify: app typecheck (tsgo -b), eslint, contract test (1179/1179),
session-composer-dock e2e (41/41 incl. new case), home/theme/a11y/
workspace-chip e2e suites (no regressions; one pre-existing failure on
`active workspace has a check icon` exists on origin/dev too, unrelated
to this change).

Fixes #753
Post-split, SessionComposerRegion + SessionPageComposerRegion + the
session.tsx call site all carried a `variant: "session"` literal that no
longer branches anything (the home path renders HomeComposerRegion).
Removing the dead prop makes the API self-explanatory and the contract
test more precise.

Contract test now asserts the actual boundary the split establishes:
- `renderHomeComposerRegion` is declared in session.tsx
- `composerHome` is wired to that function (catches a future refactor
  that drops the wiring while keeping the function around)

Replaces the prior `toContain('variant="session"')` + broad
`not.toContain('| "home"')` pair, which would have tripped on any
unrelated `| "home"` union appearing in session.tsx later.

Also inlines the `composerHome={(ctx) => renderHomeComposerRegion(ctx)}`
identity wrapper.
@github-actions github-actions Bot added app Application behavior and product flows ui Design system and user interface P2 Medium priority labels May 19, 2026

@github-actions github-actions 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.

Suggested priority: P2 (includes user-path files (packages/app/src/pages/session.tsx, packages/app/src/pages/session/composer/home-composer-region.tsx, packages/app/src/pages/session/composer/index.ts, packages/app/src/pages/session/composer/session-composer-region.tsx, packages/app/src/pages/session/session-composer-region.tsx, packages/app/src/shell-frame-contract.test.ts)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@coderabbitai

coderabbitai Bot commented May 19, 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: 1dd8945d-8cf7-4a21-a320-a6535576bcc9

📥 Commits

Reviewing files that changed from the base of the PR and between 54856f0 and 3f6d7db.

📒 Files selected for processing (7)
  • packages/app/e2e/session/session-composer-dock.spec.ts
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/composer/home-composer-region.tsx
  • packages/app/src/pages/session/composer/index.ts
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/pages/session/session-composer-region.tsx
  • packages/app/src/shell-frame-contract.test.ts
💤 Files with no reviewable changes (1)
  • packages/app/src/pages/session/session-composer-region.tsx

📝 Walkthrough

Walkthrough

This PR fixes a visual bug where the session todo dock briefly flashes at full height when navigating from an active session back to the home view. The fix separates the shared variant-driven composer renderer into dedicated HomeComposerRegion and simplified SessionComposerRegion components, removing the variant prop plumbing, and adds E2E and contract validation.

Changes

Composer Region Separation

Layer / File(s) Summary
HomeComposerRegion component
packages/app/src/pages/session/composer/home-composer-region.tsx, packages/app/src/pages/session/composer/index.ts
New HomeComposerRegion component renders the home view prompt dock, reading handoff prompt state, deriving a preview from prompt parts, persisting the preview to session handoff, and displaying PromptInput when ready or a loading fallback.
SessionComposerRegion: remove variant prop
packages/app/src/pages/session/composer/session-composer-region.tsx
Removes variant?: "session" | "home" prop and derived home-mode logic; converts memoized display fields to direct prop accessors; fixes layout/container markup to use fixed "session" variant styling and sets sessionIDControlled unconditionally.
SessionPageComposerRegion: props cleanup
packages/app/src/pages/session/session-composer-region.tsx
Removes variant field from SessionPageComposerRegion props type, simplifying the component signature.
Session page: wire separate composers
packages/app/src/pages/session.tsx
Imports HomeComposerRegion; refactors renderComposerRegion to be session-only with fixed SessionPageComposerRegion props; adds renderHomeComposerRegion helper; updates SessionMainView wiring to pass composerSession={renderComposerRegion()} and composerHome={renderHomeComposerRegion}.
E2E and contract tests
packages/app/e2e/session/session-composer-dock.spec.ts, packages/app/src/shell-frame-contract.test.ts
New E2E test todo dock does not flash on home after navigating from a session validates DOM mutation observer captures no dock insertions during transition; updates contract tests to assert renderHomeComposerRegion rendering and composerHome wiring; adds safety test ensuring home-composer-region does not import session-only symbols.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Astro-Han/pawwork#753: This PR directly fixes the bug of the todo dock flashing at full height during session-to-home navigation by separating composer region rendering paths.

Possibly related PRs

  • Astro-Han/pawwork#525: Both modify packages/app/src/pages/session/composer/session-composer-region.tsx props and wiring for dock/composer behavior.
  • Astro-Han/pawwork#539: Both update packages/app/e2e/session/session-composer-dock.spec.ts with test logic and helpers for session composer dock rendering behavior.
  • Astro-Han/pawwork#329: Both refactor session page composer rendering to stabilize session identity and visibility during transitions, touching shared files around variant/composer logic.

Poem

🐰 A dock once flashed at home, a sight so wrong,
So here we split the regions, two not one,
Session stays below, and home above,
Each rendered true—now navigation's smooth!

🚥 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 title clearly and concisely describes the main change: isolating the home composer from session-only docks to fix a visual glitch.
Description check ✅ Passed The description is comprehensive and well-structured, covering all required sections: summary, why, related issue, human review status, review focus, risks, verification, and complete checklist.
Linked Issues check ✅ Passed The code changes directly address #753: a dedicated HomeComposerRegion prevents session-only docks and stale composer state from appearing on home, eliminating the one-frame flash via structural isolation and e2e verification.
Out of Scope Changes check ✅ Passed All changes are scoped to the home/session composer refactoring: new HomeComposerRegion component, narrowed SessionComposerRegion, updated session.tsx wiring, contract tests, and e2e test—no unrelated refactors or dependencies added.

✏️ 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 claude/home-composer-region

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Astro-Han Astro-Han added the bug Something isn't working label May 19, 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 refactors the session composer by introducing a dedicated HomeComposerRegion component and simplifying SessionComposerRegion by removing the 'home' variant logic. This separation decouples the home view from session-specific dependencies, which is now verified by a new contract test. Additionally, an E2E test was added to ensure the todo dock does not flash when navigating from a session to the home screen. Feedback suggests extracting duplicated logic for onSubmit and previewPrompt into shared utilities to improve maintainability and removing a redundant createMemo wrapper in the new component to better align with SolidJS best practices.

Comment thread packages/app/src/pages/session.tsx
Comment thread packages/app/src/pages/session/composer/home-composer-region.tsx Outdated
Comment thread packages/app/src/pages/session/composer/home-composer-region.tsx
@github-actions

github-actions Bot commented May 19, 2026

Copy link
Copy Markdown

Perf delta summary

Comparator: pass

Profile / Scenario interaction median interaction worst long task max tbt frame gap p95 frame gap max jank count cls status
default / homepage-cold 32 -> 24 (-8) 40 -> 40 (0) 69 -> 73 (+4) 19 -> 23 (+4) 116.6 -> 16.8 (-99.8) 316.6 -> 116.7 (-199.9) 9 -> 3 (-6) 0 -> 0 (0) pass
default / long-session-input-lag 48 -> 48 (0) 48 -> 48 (0) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.7 (0) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-streaming-long 48 -> 48 (0) 64 -> 64 (0) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.8 (0) 16.8 -> 33.3 (+16.5) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-call-expand 16 -> 16 (0) 16 -> 24 (+8) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.7 (0) 16.7 -> 16.7 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-default-open-heavy-bash 24 -> 24 (0) 32 -> 24 (-8) 65 -> 63 (-2) 15 -> 13 (-2) 100 -> 50 (-50) 366.7 -> 133.4 (-233.3) 10 -> 3 (-7) 0 -> 0 (0) pass
default / terminal-side-panel-open 48 -> 48 (0) 56 -> 56 (0) 0 -> 0 (0) 0 -> 0 (0) 66.6 -> 33.4 (-33.2) 66.6 -> 33.4 (-33.2) 1 -> 0 (-1) 0 -> 0 (0) pass
default / session-scroll-reading 32 -> 24 (-8) 40 -> 24 (-16) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.8 (+0.1) 16.7 -> 16.8 (+0.1) 0 -> 0 (0) 0.505 -> 0.505 (0) warn: cls
low-end / session-scroll-reading-long 72 -> 64 (-8) 80 -> 64 (-16) 101 -> 90 (-11) 74 -> 57 (-17) 16.8 -> 16.8 (0) 150 -> 150 (0) 3 -> 3 (0) 0.011 -> 0.011 (0) pass
low-end / session-timeline-recompute 120 -> 120 (0) 128 -> 144 (+16) 110 -> 110 (0) 187 -> 175 (-12) 100 -> 100 (0) 166.7 -> 183.3 (+16.6) 3 -> 3 (0) 0.081 -> 0.081 (0) pass
low-end / concurrent-shimmer-extreme 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.7 (-0.1) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass

route.layoutRouteKey is already a reactive accessor from useSessionRouteKey;
wrapping it in createMemo just calls through with no extra value. Aliasing
the accessor directly is equivalent and avoids modeling a cargo-cult memo
pattern that other composer-region files might copy.
@Astro-Han
Astro-Han merged commit 31236e7 into dev May 19, 2026
28 checks passed
@Astro-Han
Astro-Han deleted the claude/home-composer-region branch May 19, 2026 09:47
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 P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Todo dock briefly flashes at full height when navigating from session to home

1 participant