🤖 fix: chat layout tears — sticky in-flow composer dock + first-paint readiness barrier#3501
🤖 fix: chat layout tears — sticky in-flow composer dock + first-paint readiness barrier#3501ammar-agent wants to merge 7 commits into
Conversation
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…pop in after reveal)
|
@codex review Pushed a second architectural layer on top of the sticky dock: a first-paint readiness barrier ( |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6e83ed982
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed: the session-usage "known" flip now runs after the stale-response version guard (both success and failure paths), so only the latest request can mark usage known and the reveal can't race a pending refresh. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb357392ee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Re: thread PRRT_kwDOPxxmWM6IT5Ol (reset usage readiness per refresh) — keeping the latch is intentional, now documented at the
Resolving the thread on that basis. |
|
@codex review Please take another look. The session-usage latch rationale is now documented inline; no behavior change in this round. |
|
@codex review Two commits since the last review: (1) documented the deliberate per-session latch on session-usage readiness (no behavior change), (2) wired ProvidersConfigStore in the useRouting hook test which bypasses AppLoader. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3bfe06b9a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed: the live onConfigChanged iterator is now tracked on the store instance and force-closed ( |
|
Codex Review: Didn't find any major issues. Hooray! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Eliminates the chat view's post-paint layout shifts ("tears"/"flashes") with two architectural changes: (1) the composer dock becomes in-flow sticky scroll content, so transcript clearance is reserved by flow layout in the same pass as any dock height change; and (2) a first-paint readiness barrier makes the chat view reveal once, fully formed — decorations (background bashes, compaction warning, concurrent-local warning, chat instructions, …) can no longer "load in" a few frames after the transcript is visible and shift it.
Background
Two distinct flash classes were observed after hydration:
--composer-hpadding. The measurement channel lags layout by ≥1 frame, so any decoration mount painted the last message under the composer for a frame, then snapped.BackgroundProcessesBannerpopped in on every workspace visit becauseBackgroundBashStoredeleted its cache on unsubscribe.Implementation
Sticky in-flow composer dock (kills class 1)
The dock is now the last child of the transcript scrollport (
position: sticky; bottom: 0), after the bottom-anchoring sentinel. Clearance is the dock itself — exact, and reserved in the same layout pass as any height change. The measured--composer-hchannel,reanchorBottom(), and allLayoutStackLaneheight-reservation machinery are deleted (~250 LoC of correction logic). Dock growth happens below the sentinel where native anchoring can't compensate, souseAutoScroll's safety-net ResizeObserver now observes the scrollport and each direct child, re-pinning pre-paint while bottom-locked.First-paint readiness barrier (kills class 2)
New contract in
useChatViewDataReady— "unknown is not empty": every async source that can change the chat view's initial layout must expose a latched, synchronous "state known" signal and register it in the barrier.computeChatViewRevealthen derives a single reveal decision: the hydration skeleton holds (and the decoration lane stays empty) until history is caught up and all sources are known, so transcript + decorations mount in one commit. Readiness is monotonic per mounted workspace — the barrier only ever delays the initial mount, never unmounts visible decorations. A 2s resilience deadline force-reveals if a source hangs (every source also self-heals to "known" on error), so a broken backend degrades to the old pop-in behavior instead of a stuck skeleton.Sources registered:
BackgroundBashStoregains a state-known signal; caches are now retained across unsubscribe so revisits render last-known state synchronously.activity.list()snapshot applies.useProvidersConfigis rebacked by a new app-wideProvidersConfigStore(one fetch + oneonConfigChangedsubscription per session instead of one per mount across ~12 consumers). FixesCompactionWarning/CodexOauthWarningBannerpopping in on every mount, and makes optimistic updates visible to all consumers.data-loaded(perf tests + story play helpers) now includes readiness, so anything waiting on it observes the final post-reveal layout.Validation
BackgroundBashStoreknown-signal/retention/self-heal,ProvidersConfigStoreshared-fetch/self-heal/optimistic-vs-stale,computeChatViewRevealbranch matrix, safety-net observer coverage inuseAutoScroll.tests/ui: chat (15 suites), config, layout, workspaces, compaction, review — green.tests/e2e:composerLayoutStability(clientHeight invariant across send + pinned bottom with grown dock) and all 6perf.workspaceOpenprofiles (provesdata-loadedreaches true with the barrier in a real Electron app).Risks
loadinguntil first fetch settles; explicitrefresh()after saves;onConfigChangedfor external edits), but any consumer relying on a per-mount refetch would now see session-cached data until an event fires.Pains
xvfb-runin this environment.Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh• Cost:$30.66