Skip to content

fix: stop the transcript mask painting over composer status bars - #6912

Merged
chenmingwei23 merged 1 commit into
mainfrom
fix/status-bars-clipped-by-transcript-mask
Aug 30, 2026
Merged

fix: stop the transcript mask painting over composer status bars#6912
chenmingwei23 merged 1 commit into
mainfrom
fix/status-bars-clipped-by-transcript-mask

Conversation

@buluoray

@buluoray buluoray commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

The composer status bars render with their top border, both top corners, and the first line's ascenders shaved off. It reads as the bar being clipped by the UI — the reporter's words were "the yellow part is covered by the UI".

Reported against SubagentDeliveryProgress (the "N sub-agent results ready — processing after the current turn" line), but TaskProgressBar, WorkflowProgressBar and QueueStack's front card share the same defect.

Why it matters

The status stack is where the chat tells you about work you cannot otherwise see — a sub-agent result being held behind the current turn, a task's progress, a queued message. A bar whose top edge is sliced off reads as a rendering fault, and the reader's first instinct is to distrust the surface rather than the message. QueueStack's front card is the worst case: its top border is what separates a queued message from the input box it is fused to.

What changed (motivation → approach → change)

Nothing was clipping them. The transcript's bottom mask is relative z-[1] and deliberately overshoots COMPOSER_MASK_OVERSHOOT_PX (10px) below the scrollport edge so it lands flush against the composer box, with no hairline strip between the two. That overshoot is sized to a distance ChatInput owns — its pt-1 (4px) plus its h-[6px] top spacer — and that distance only holds while the composer status stack is empty.

With a bar present, the bar occupies that strip. The mask's tail is fully opaque there (from-bg from-[62%] makes the bottom 62% solid, and the 10px overshoot sits entirely inside it), so it painted over the bar's top 10px.

SubagentProgressBar was immune by accident: its wave chip already sits at z-[46] to clear theme-experience overlays. That is why the defect only ever surfaced on the bars carrying no z-index at all.

The change lifts each of them above the mask: TaskProgressBar, WorkflowProgressBar and SubagentDeliveryProgress to relative z-[2], and QueueStack from zIndex: 0 to 2. All stay far below the composer's own z-10, so QueueStack's collapsed front card keeps sliding its -OVERLAP fuse under the input box rather than over it. The mask's tail now paints harmlessly behind the topmost bar, whose own box is what sits flush under the transcript.

Why per-child and not one z-index on the stack wrapper. One z-index there would fix all five at once and is the tempting shortcut, but it would make the wrapper a stacking context and confine the wave chip's z-[46], re-exposing it to the very overlays it was lifted to clear. ChatPage.tsx's COMPOSER_MASK_OVERSHOOT_PX doc comment now records the corrected premise, since its old wording asserted a distance that is only true for an empty stack.

Tests

website/src/test/ChatPage.statusStackAboveMask.test.tsx — a source-text guard, matching how the two neighbouring mask invariants (ChatPage.fadeClearance, ChatPage.composerChromeOcclusion) are already pinned: the values live in five files, several as Tailwind classes jsdom cannot resolve into a paint order, and the invariant is the comparison between them.

Case What breaks it
anchor: every compared value was found a regex silently stops matching, making the orderings pass on undefined > undefined vacuity
each of the 5 children paints above the mask that child loses its lift
QueueStack stays below the composer its fuse would surface on top of the input box
the stack wrapper has no z-index and no position the shortcut above, which would confine the wave chip's 46
renders exactly the 5 children checked here a sixth bar added without a lift

All 6 mutations verified: each of the four reverted lifts, the wrapper shortcut, and a sixth unlifted bar. Every one turned exactly its own case red, and the sources were byte-identical to their pre-mutation copies afterwards.

A seventh case and mutation (the mask staying opaque across the overshoot) was dropped after review: ChatPage.fadeClearance.test.tsx:69-77 already pins the same arithmetic strictly more strongly — toBeGreaterThan plus an upper bound, against this file's toBeGreaterThanOrEqual — so it added no coverage while making a second regex track the same class string.

Full frontend suite green on this base: tsc -b 0, eslint 0 errors, 1667 files / 26350 tests passing.

Manual verification

Measured in an isolated repro of the same column geometry (scroller → mask with the same height/negative margins → status stack → composer), driven by Playwright:

  • mask.bottom − bar.top === 10exactly COMPOSER_MASK_OVERSHOOT_PX, confirming the mask's box is what covers the bar's top.
  • Lifting the bar left every rect identical (bar.top 323.02, box.top 367.81 before and after) while the rendered pixels differed — proving this is paint order, not layout, so the composer does not move.

Not reproduced in the live app: the reported state needs a real sub-agent delivery held behind a busy turn, which cannot be staged from a test harness. The repro reproduces the reported symptom exactly and the measured overlap equals the production constant, but a reviewer wanting the live surface should treat that as the remaining gap.

Screenshots / video

Isolated repro of the same column at the same geometry — not the live app (see Manual verification for why). Left: today, the mask's tail shaves the bar's top border, both corners and the glyph tops. Right: with the lift, the card is intact.

Repro before the fix: the transcript mask's opaque tail covers the top 10px of the status bar, removing its top border and rounded corners

Repro after the fix: the status bar renders as a complete rounded card flush under the transcript, composer unmoved

Related Issues

No linked issue — reported directly by a user against the running dashboard.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable) — the COMPOSER_MASK_OVERSHOOT_PX doc comment, whose stated premise this fix corrects
  • No secrets, credentials, or internal references in the diff

@buluoray
buluoray requested a review from a team August 30, 2026 04:27
@buluoray
buluoray requested a review from a team as a code owner August 30, 2026 04:27
@buluoray
buluoray requested a review from patrigao August 30, 2026 04:27
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 30, 2026
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

UX-level review of 087ba923bc7202e221bb3d5d92e9967eec3f968a — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The screenshots confirm the claims: the before image shows the status bar's top border and corners shaved flat by the mask; the after image shows the intact rounded card with the composer unmoved. The remaining file in the diff is a source-text guard test with no user-facing surface. No strings, flows, states, or controls change — this is a pure paint-order repair of an existing visual defect, and the fix is exactly what the pixels show.

UX-Verdict: PASS

Pure paint-order repair: the status bars regain their top border and corners, nothing else about the experience changes, and the screenshots prove it.

[UX-REVIEWED] 087ba92

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 30, 2026
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 087ba923bc7202e221bb3d5d92e9967eec3f968a — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Root-cause paint-order fix at minimal blast radius; the tempting wrapper shortcut is rejected for a verified stacking-context reason and test-pinned against reintroduction.

I verified the premises in the tree: the mask is relative z-[1] with a 10px overshoot (ChatPage.tsx:7581), SubagentProgressBar's pre-existing z-[46] explains the selective symptom, lifted children stay below the composer's z-10 so QueueStack's fuse still slides under the input box, and temp-screenshots/ is this repo's documented committed-deliverable convention. The source-text guard matches the two neighbouring mask invariants' style, anchors against vacuous regexes, and pins the child list so a sixth bar can't silently inherit the bug. No design-level concerns.

[DESIGN-REVIEWED] 087ba92

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 087ba923bc7202e221bb3d5d92e9967eec3f968a and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 087ba92

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 087ba923bc7202e221bb3d5d92e9967eec3f968a: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of 087ba923bc7202e221bb3d5d92e9967eec3f968a — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All verification is done. The mask geometry claims match ChatPage.tsx:7581-7586, the wave chip's z-[46] and its derived rationale exist at SubagentProgressBar.tsx:204-211 (ThemeExperienceLayer clamps overlays at OVERLAY_Z_MAX = 45, outside the stack subtree — so the rejected wrapper shortcut really would break it), the source-text test style matches the two pre-existing sibling guards, the component-level lifts automatically cover the other consumers (ChatPane.tsx:717-721, SideChat.tsx:612), and temp-screenshots/ is a documented PR-template convention with its own cleanup workflow.

First-Principles-Verdict: PASS

A reported paint defect fixed at its mechanism — the mask outranking the bars — with every rider declared and the rejected general shortcut given a derived reason.

What this change ships

Intent: stop the transcript's bottom mask shaving the top edge off composer status bars — a FIX.

  1. Sub-agent delivery bar's top border/corners no longer shaved — justified (the reported defect)
  2. Task progress bar gets the same lift — justified, same defect, declared
  3. Workflow progress bar gets the same lift — justified, same defect, declared
  4. Queued-message front card's top border restored (zIndex 0→2) — justified, declared
  5. Corrected COMPOSER_MASK_OVERSHOOT_PX doc comment — justified, mandated by the same-commit doc rule
  6. New source-text guard pinning the ordering, wrapper, and child list — justified, matches two existing sibling guards
  7. Two repro screenshots under temp-screenshots/ — justified, documented PR-template convention

Per-child lift vs. one wrapper z-index: the wrapper alternative was considered and rejected on a verifiable constraint (ThemeExperienceLayer.tsx:54 clamps overlays to 45; a wrapper stacking context would confine the wave chip's z-[46]), and the child-list test case covers the sixth-bar gap that rejection opens. Sibling count: all five stack children now declare a z-index above the mask's z-[1]; the components' other render sites (ChatPane.tsx:717-721, SideChat.tsx:612) inherit the fix because it lives on the components — zero unfixed siblings found. No new public surface, config key, or flag; nothing here duplicates an existing mechanism (the only z constant in website/src is OVERLAY_Z_MAX, which serves a different boundary).

[FIRST-PRINCIPLES-REVIEWED] 087ba92

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 087ba923bc7202e221bb3d5d92e9967eec3f968a — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 087ba92

Verdict parsed from the review's SHA-scoped output markers for commit 087ba923bc7202e221bb3d5d92e9967eec3f968a.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 087ba923bc7202e221bb3d5d92e9967eec3f968a: <one-sentence reason>

@buluoray

Copy link
Copy Markdown
Contributor Author

CI status: both failures are pre-existing on main, not from this diff

Four checks are red (Frontend Lint & Type Check, Build Wheel, Build Desktop (ubuntu-22.04) / (ubuntu-22.04-arm), E2E). They trace to two breaks that are already on main, in files this PR does not touch. Recording the evidence here so a reviewer does not read the red as this change's.

This PR's complete file list is 5 source files + 2 evidence PNGs: ChatPage.tsx, QueueStack.tsx, TaskProgressBar.tsx, WorkflowProgressBar.tsx, and the new ChatPage.statusStackAboveMask.test.tsx. Neither failing file appears in it.

1. TS2300: Duplicate identifier 'Modal'website/src/pages/ChannelPage.tsx

main imports the same symbol twice in that file:

  2:import Modal from '../components/Modal'
 16:import Modal from '../components/Modal'

Landed in 07eaab97 ("fix(channels): name errors with shared modal", 2026-08-30T02:34:12Z). This is what fails tsc -b, which is why it takes out Frontend Lint & Type Check, both Build Desktop lanes and Build Wheel together — they all run the same typecheck/build.

2. productName.test.tsen:components.pullRequestPanel.owner_not_configured_guidance

AssertionError: en:components.pullRequestPanel.owner_not_configured_guidance:
  expected [ Array(1) ] to deeply equal []

The catalog value hardcodes the product name instead of using the interpolation variable. The key is present on main in en.manual.json (which feeds the en: catalog) and the 11 translated locales.

How this was attributed

Not by reasoning about the diff — by running the gates on a pristine origin/main checkout with this PR's changes absent entirely (detached worktree at 53fd2257f):

Gate pristine main this branch rebased on main
npx tsc -b exit 1, same two TS2300 lines exit 1, same two TS2300 lines
npx vitest run src/i18n/productName.test.ts exit 1, same assertion exit 1, same assertion

Identical on both sides, so neither failure is attributable to this change.

For completeness, on the base this branch was reviewed and pushed from, the full frontend suite was green with this diff applied: tsc -b 0, eslint 0 errors, 1658 files / 26171 tests passing.

Disposition

accepted-and-deferred — not fixed here. Both are repo-wide base breaks; fixing them inside a chat-composer paint-order PR would mix an unrelated import removal and a 12-locale i18n change into this diff. I found no open PR for either, so they need their own. This PR will go green on a rebase once they land.

The composer status bars rendered with their top border, both top corners
and the first line's ascenders shaved off, which reads as the bar being
clipped by the UI.

Nothing clipped them. The transcript's bottom mask is `relative z-[1]` and
deliberately overshoots COMPOSER_MASK_OVERSHOOT_PX (10px) BELOW the
scrollport edge so it ends flush against the composer box with no hairline
between them. That overshoot is sized for the distance ChatInput owns --
`pt-1` plus its `h-[6px]` top spacer -- a distance that only holds while the
composer status stack is EMPTY. With a bar present the bar occupies that
strip, and the mask's tail (opaque: `from-bg from-[62%]` makes the bottom
62% solid, and the overshoot sits entirely inside it) painted over the bar's
top 10px.

Measured in an isolated repro of the same column: mask.bottom - bar.top ==
10, exactly the overshoot, and lifting the bar changed pixels while leaving
every rect identical -- paint order, not layout.

SubagentProgressBar was immune only by accident: its wave chip already sits
at `z-[46]` to clear theme-experience overlays, which is why the defect only
surfaced on the bars that had no z-index at all -- TaskProgressBar,
WorkflowProgressBar, SubagentDeliveryProgress, and QueueStack at `zIndex: 0`.

Each of those is lifted to `z-[2]`. QueueStack stays far below the
composer's own `z-10`, so its collapsed front card's -OVERLAP fuse keeps
sliding UNDER the input box rather than over it.

The fix is per-child rather than one z-index on the stack wrapper on
purpose: a z-index there would make the wrapper a stacking context and
CONFINE the wave chip's 46, re-exposing it to the overlays it was lifted to
clear. ChatPage.statusStackAboveMask.test.tsx therefore pins three things --
the ordering, the wrapper staying context-free, and the child LIST, so a
sixth bar cannot quietly inherit the bug.
@buluoray
buluoray force-pushed the fix/status-bars-clipped-by-transcript-mask branch from a1e704c to 087ba92 Compare August 30, 2026 06:25
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 30, 2026
@buluoray

Copy link
Copy Markdown
Contributor Author

Both base breaks have landed; rebased onto them

Follow-up to the CI note above — the two main breaks it recorded as accepted-and-deferred are now fixed upstream, so this PR's red checks should clear on their own:

Rebased onto main with both present. Verified after the rebase that the three source lines this PR's guard test reads are still intact (the from-bg from-[62%] mask className, the composer-status-stack wrapper className, and the ref={inputAreaRef} className="relative z-10" line), then re-ran the gates: tsc -b 0, eslint 0 errors, full suite 1667 files / 26350 tests passing.

Also addressed one review finding in the same push

A reviewer noted that this PR's new keeps the mask opaque across the whole overshoot case duplicates ChatPage.fadeClearance.test.tsx:69-77. Verified against source and agreed — that file asserts the same from-bg from-[N%] × (TRANSCRIPT_MASK_ABOVE_PX + COMPOSER_MASK_OVERSHOOT_PX) arithmetic strictly more strongly (toBeGreaterThan plus an upper bound, against this file's toBeGreaterThanOrEqual), so the new case added no coverage while making a second regex track the same class string.

Dropped. disposition: fixed at 087ba92 — the guard goes from 10 cases to 9, and the PR body's test table and mutation count were corrected to match rather than left asserting the removed case.

Evidence image URLs re-pinned to the new head and verified 200.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Aug 30, 2026
@chenmingwei23
chenmingwei23 merged commit 536a62a into main Aug 30, 2026
110 of 117 checks passed
@chenmingwei23
chenmingwei23 deleted the fix/status-bars-clipped-by-transcript-mask branch August 30, 2026 14:00
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants