Skip to content

fix(web): composer alignment, Cool Darker hovers, transcript cutoff - #107

Merged
NoahHendrickson merged 4 commits into
customfrom
t3code/fork-ui-fixes
Sep 2, 2026
Merged

NoahHendrickson merged 4 commits into
customfrom
t3code/fork-ui-fixes

Conversation

@NoahHendrickson

@NoahHendrickson NoahHendrickson commented Sep 2, 2026 •

Copy link
Copy Markdown
Owner

Problem

Four fork UI defects surfaced after the 2026-09-01 upstream sync (#105), all seen on the Cool Darker palette.

  1. Prompt text sat low in the composer. Upstream's new attach-files button (feat(web): attach PDFs, ZIPs, and other files to a turn pingdotgg/t3code#8236) rides the prompt row's action cluster at icon-sm (28px) beside the fork's 24px send button. The row is items-end, so the taller cluster pushed the one-line prompt 5px below the send axis and grew the 44px base row to 48.
  2. Popup row hover did not match the selected row. Upstream hovers menu, select and combobox rows with --accent and marks the selected row with an 8% foreground wash. The fork palettes make --accent an opaque grey: on the Cool palettes it is the popup's own fill, so a hovered row vanished; under Cool Darker glass the popup stands on the wallpaper-tinted floor and the same opaque cool fill landed as a bluish slab on a warm surface (the git actions menu); and in the branch picker the hovered row never matched the "current" row beside it.
  3. Every accent hover under Cool Darker glass was a bluish slab. Under glass nothing --accent lands on is #282d30 any more — the stage is a neutral tint with the wallpaper's cast, the cards are washes, the popups stand on the warm floor. A sweep of the web app found ~40 bg-accent hover/selected utilities in the main pane, all affected.
  4. The transcript painted straight through the composer while streaming. Upstream fix(web): reduce title bar scroll fade height pingdotgg/t3code#8799 renamed --topbar-scroll-fade-height to --workspace-titlebar-scroll-fade-height. The fork's timeline cutoff folds upstream's top fade into its own mask stack by reading that variable, so whenever the top fade was on the unresolved var() invalidated the whole mask-size declaration at computed-value time. Every layer fell back to auto, the solid scrollbar-gutter layer covered the scroller, and the cutoff disappeared. The guard pinned the old name as literal text, so it stayed green.

Fix

  • theme.custom.css: every button in the prompt row's right action cluster shares the 24px square; the attach button hovers with the ghost controls' 4% lift (--fork-composer-control-hover) rather than --accent.
  • theme.custom.css: menu, select and combobox rows hover with the selected row's 8% foreground wash, 12% on a selected/checked row. Dark only, like the rest of the popup-surface set.
  • theme.custom.palettes.css: the Cool Darker glass root block and both v2 sidebar blocks (docked and floating) restate --accent and --fork-pill-hover as rgb(255 255 255 / 8%). 8% lands on the stage where the opaque token used to (+19 per channel), so upstream's bg-accent/N variants keep their relative weight. Non-glass Cool Darker keeps its opaque ladder per the flatten-alpha doctrine; the only non-glass failure was the attach button, patched above.
  • theme.custom.css: the cutoff's top-fade companion rule reads --workspace-titlebar-scroll-fade-height.
  • Guards: forkComposerShell pins the cluster geometry and the attach hover, and now reads the fade variable name out of index.css's own topbar-scroll-fade utility instead of pinning a literal; forkPopupSurface pins the six row slots and both wash rules; forkCoolDarkerSidebarVibrancy pins the three glass --accent washes.
  • .fork/customizations.yaml: fork-composer-shell, fork-popup-surface and fork-cool-darker-sidebar-vibrancy intents updated.

Left alone on purpose: --muted / --secondary are the same opaque grey and paint badges, code blocks and tab strips as slabs under glass. That is a fill problem, not a hover one, and belongs in its own change.

Verification

Measured in a dev-server preview on Cool Darker (computed styles, not eyeballed):

Before After
Composer base row height 48px 44px
Prompt text span inside the row 17.5–35.5px 13.5–31.5px (centred on the send axis)
Attach button 28px 24px
Branch picker hovered row vs selected row --accent vs foreground 8% both foreground 8%
Git actions "Commit" row, glass floor opaque #282d30 on rgb(33 28 33) foreground 8% wash
--accent under glass (root and sidebar) #282d30 rgb(255 255 255 / 8%)
Timeline mask-size with the top fade on auto, auto, auto (no cutoff) 100% 24px, 100% calc(100% - 24px), 6px 100%
  • vp test run src/__fork_guards__ → 45 files, 381 tests passing (before the cutoff commit; forkComposerShell re-run green after it).
  • vp run typecheck in apps/web clean.
  • Before screenshots are in the reporting thread; no after images attached because the fork has no upload path for PR assets from the agent session.

Web/desktop only. No contracts, server or mobile changes.

Claude Fable 5.1 via Claude Code.

🤖 Generated with Claude Code

Three fork UI fixes surfaced after the 2026-09-01 upstream sync.

Prompt text sat 5px low. Upstream's new attach-files button (pingdotgg#8236) rides
the prompt row's action cluster at icon-sm (28px) beside the fork's 24px
send button; the row is items-end, so the taller cluster dropped the
one-line prompt and grew the 44px base row to 48. Every button in the
cluster now shares the 24px square, and the attach button hovers with
the ghost controls' 4% lift instead of --accent, which on the Cool
palettes is the prompt surface it sits on.

Popup row hovers used --accent, which the fork palettes make an opaque
grey: the popup's own fill on the Cool palettes (hover vanished), a
bluish slab on the wallpaper-tinted floor under Cool Darker glass, and
never a match for the branch picker's selected "current" row. Menu,
select and combobox rows now hover with the selected row's 8% foreground
wash (12% on a selected or checked row).

Under Cool Darker glass nothing --accent lands on is #282d30 any more —
the stage is a neutral tint with the wallpaper's cast, the cards are
washes, the popups stand on the warm floor — so every accent hover in
the main pane was a bluish slab. The glass root block and both sidebar
blocks restate --accent (and --fork-pill-hover) as rgb(255 255 255 / 8%),
which lands on the stage where the opaque token used to so upstream's
bg-accent/N variants keep their relative weight.

Guards pin the cluster geometry, the attach hover, the popup row rules
and the glass washes. Manifest entries updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Sep 2, 2026

@cursor cursor 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.

The glass --accent restatement is the right move: one token change instead of chasing ~40 bg-accent utilities. Popup row hover belongs in fork-popup-surface and correctly matches the selected-row wash rather than the opaque grey. Those parts are structured correctly.

Do not merge as-is. The composer cluster rules encode a false invariant — that every direct-child button in [data-chat-composer-actions="right"] is a 24px ghost — and the guard now pins that selector. ComposerPrimaryActions already puts a labeled Refine submit in that same slot as a direct child. Target attach the way send/stop are targeted, and fold the new rules into the size + ghost-hover declarations that already exist.

Open in Web View Automation 

Sent by Cursor Automation: Thermo nuke 4.6

Comment thread apps/web/src/theme.custom.css Outdated
Comment on lines +713 to +718
:root[data-fork="noahhendrickson-t3code"] [data-chat-composer-actions="right"] > button {
width: 24px;
height: 24px;
min-height: 24px;
border-radius: 4px;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pushes a false model into an already busy composer overlay: “whatever else rides the prompt row’s action cluster” is a 24px square. That is not true of this slot.

ComposerFooterPrimaryActions is a fragment, so ComposerPrimaryActions children hoist into [data-chat-composer-actions="right"]. The default send/stop path is a direct <button> (already sized by [data-fork-composer-action]). The plan-follow-up Refine path is also a direct child — a labeled size="sm" submit, not an icon — and this rule will squash it to 24×24 / radius 4. The hover sibling above (> button:not([data-fork-composer-action])) will also steal that filled control’s hover for the 4% ghost lift. Implement happens to escape only because it wraps in a div; Refine does not. That is spaghetti via negative space, and the new composer-shell guard locks the over-broad selector in.

Code judo: stop describing attach as “everything that isn’t send.” Give it the same vocabulary send/stop already have (data-fork-composer-action="attach", fenced in ChatComposer the way the other actions are), then:

  • fold the 24px box into the existing [data-fork-composer-action] rule (delete this copy)
  • fold the hover into the existing --fork-composer-control-hover ghost rule, or key it on ="attach"

A CSS-only :not([type="submit"]) patch would dodge Refine today and still break the next type="button" that lands in this cluster. The cluster is not a 24px icon row; only attach needed to join it.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 6cf45a9. The attach button is now stamped data-fork-composer-action="attach" (fenced in ChatComposer), the 24px box comes from the existing [data-fork-composer-action] rule, and the 4% ghost hover is keyed on ="attach". Both position-based > button rules are gone. The guard now pins the stamp and rejects any rule matching data-chat-composer-actions … > button, so the over-broad selector cannot come back. Verified live: attach 24×24 at 4px radius, pressed fill rgba(255,255,255,0.04), send untouched.

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.4 KiB 13.1 KiB −306 B (−2.2%) 15.1 KiB ✅
Codex Thread snapshot wire 6.9 KiB 6.9 KiB −2 B (−0.0%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.6 KiB 6.3 KiB −304 B (−4.5%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 57.0 KiB 54.7 KiB −2.3 KiB (−4.1%) 66.4 KiB ✅
Codex Live turn messages 10 8 −2 (−20.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.1 KiB −329 B (−2.4%) 15.1 KiB ✅
Claude Thread snapshot wire 6.9 KiB 6.9 KiB −8 B (−0.1%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.5 KiB 6.2 KiB −321 B (−4.8%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.8 KiB 55.5 KiB −2.3 KiB (−4.0%) 66.4 KiB ✅
Claude Live turn messages 10 8 −2 (−20.0%) 21 ✅

Baseline: 11b1b69 · PR result: d5ae096 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Upstream pingdotgg#8799 renamed --topbar-scroll-fade-height to
--workspace-titlebar-scroll-fade-height. The fork's timeline cutoff folds
upstream's top fade into its own mask stack by reading that variable, so
whenever the top fade was on the reference resolved to nothing, the whole
mask-size declaration went invalid at computed-value time, every layer
fell back to auto, and the solid scrollbar-gutter layer covered the
scroller — the transcript, streaming replies included, painted straight
through the composer.

The rule now reads upstream's current name. The guard used to pin the
old name as literal text, which is why it stayed green while the cutoff
broke; it now reads the variable name out of index.css's own
topbar-scroll-fade utility and asserts the fork references that.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@NoahHendrickson NoahHendrickson changed the title fix(web): composer prompt alignment and Cool Darker hover fills fix(web): composer alignment, Cool Darker hovers, transcript cutoff Sep 2, 2026
@github-actions github-actions Bot added size:L and removed size:M labels Sep 2, 2026

@NoahHendrickson NoahHendrickson left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Blocking issue: the new direct-child selector in theme.custom.css sizes every button under [data-chat-composer-actions="right"] to 24x24, not just Attach files. ComposerPrimaryActions renders the labeled Refine submit as a direct child in the showPlanFollowUpPrompt + promptHasText state, so that primary action is clipped to a 24px square. The adjacent ghost-hover selector also matches Refine because it lacks data-fork-composer-action, replacing its message-action hover. Please stamp the attach control explicitly (the way send/stop are stamped) and scope both sizing and hover to that marker; update the guard to pin the narrower contract.

…sition

Review on #107: the `[data-chat-composer-actions="right"] > button` rules
encoded a false invariant. ComposerPrimaryActions hoists a labelled
Refine submit into that cluster as a direct child, so the sizing rule
would squash it to a 24px square and the ghost-hover rule would replace
its message-action hover.

The attach button now carries data-fork-composer-action="attach", fenced
in ChatComposer the way send and stop are stamped. The 24px box comes
from the existing [data-fork-composer-action] rule and the 4% ghost hover
is keyed on ="attach". The guard pins the stamp and rejects any rule that
selects the cluster's buttons by position.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@NoahHendrickson

Copy link
Copy Markdown
Owner Author

Addressed the blocking issue in 6cf45a9: the attach control is stamped data-fork-composer-action="attach" and both sizing and hover are scoped to that marker. No rule selects the cluster's buttons by position any more, so the Refine submit keeps its own size and message-action hover. Guard updated to pin the stamp and reject positional selectors on the cluster. Guards 35/35, fork lint clean, web typecheck clean.

@NoahHendrickson
NoahHendrickson merged commit 13f01d9 into custom Sep 2, 2026
19 checks passed
@NoahHendrickson
NoahHendrickson deleted the t3code/fork-ui-fixes branch September 2, 2026 17:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants