From a4a21633c7f165edbe96b8b57dbfca4b0ebcac68 Mon Sep 17 00:00:00 2001 From: noah Date: Mon, 27 Jul 2026 18:37:12 -0400 Subject: [PATCH 1/2] fix(fork): truncate the slim composer placeholder under the pills The absolute empty-state overlay wraps without growing the editor, so it painted through the inline model pill; ellipsis it on one line instead. Co-authored-by: Cursor --- .fork/customizations.yaml | 13 ++++++++----- .../src/__fork_guards__/forkComposerShell.test.ts | 13 +++++++++++++ apps/web/src/components/chat/ChatComposer.tsx | 8 +++++++- apps/web/src/theme.custom.css | 15 +++++++++++++++ 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/.fork/customizations.yaml b/.fork/customizations.yaml index 2bf7078900c..2ff2fff29a6 100644 --- a/.fork/customizations.yaml +++ b/.fork/customizations.yaml @@ -674,11 +674,14 @@ absolutely positioned overlay outside the editor's scroll box — wraps and paints straight through the 48px box, and stably so (the overlay never changes the observed editor height, so the latch cannot rescue it). A - narrow viewport resolves tall regardless. States the designs do not draw - also resolve tall: an approval request, a pending user-input question, and - the plan follow-up banner all own the box's internals and carry their own - action rows. Attachments deliberately do not force tall — attachment cards - render as a full-width band above the inline row. + narrow viewport resolves tall regardless. On desktop slim the same overlay + still outruns a short editor column: the placeholder truncates with an + ellipsis (nowrap on the Lexical sibling, overflow-hidden on the prompt + column) rather than wrapping under the inline pills. States the designs do + not draw also resolve tall: an approval request, a pending user-input + question, and the plan follow-up banner all own the box's internals and + carry their own action rows. Attachments deliberately do not force tall — + attachment cards render as a full-width band above the inline row. The control row renders in every density, including collapsed; only its left half is conditional. The run controls are gated on a pending approval — they moved out of the box, which upstream unmounted wholesale during diff --git a/apps/web/src/__fork_guards__/forkComposerShell.test.ts b/apps/web/src/__fork_guards__/forkComposerShell.test.ts index 540593c7396..a59426fa294 100644 --- a/apps/web/src/__fork_guards__/forkComposerShell.test.ts +++ b/apps/web/src/__fork_guards__/forkComposerShell.test.ts @@ -303,6 +303,19 @@ describe("fork guard: fork-composer-shell", () => { ).toBe("tall"); }); + it("truncates the slim-shell placeholder instead of letting it wrap under the pills", () => { + // Same absolute-overlay failure mode as the phone case, just on a desktop + // slim row whose editor column is still narrower than the long empty-state + // hint. Truncate on one line; do not apply nowrap to the editor itself. + expect(theme).toMatch( + /\[data-fork-composer-density="slim"\][\s\S]{0,200}\[data-testid="composer-editor"\]\s*~\s*div[\s\S]{0,160}text-overflow:\s*ellipsis/u, + ); + expect(theme).toMatch( + /\[data-fork-composer-density="slim"\][\s\S]{0,200}\[data-testid="composer-editor"\]\s*~\s*div[\s\S]{0,160}white-space:\s*nowrap/u, + ); + expect(chatComposer).toMatch(/isComposerSlim && "overflow-hidden"/u); + }); + it("keeps the branch controls reachable while collapsed, and gates modes on approval", () => { // Two upstream behaviours the control row has to preserve. Upstream showed // BranchToolbar while collapsed (gated on showComposerContextStrip alone), diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 1c969213314..7bcae7458ba 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -2732,7 +2732,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) {/* fork:begin fork-composer-shell — see .fork/customizations.yaml#fork-composer-shell */}
-
+ {/* overflow-hidden on slim: the absolute placeholder can still paint + past the editor column into the inline pills if something misses + the ellipsis rule; clip at the column edge. */} +
{/* fork:end fork-composer-shell */} Date: Mon, 27 Jul 2026 18:46:10 -0400 Subject: [PATCH 2/2] fix(fork): shorten the slim composer hint instead of truncating it Truncation would eat $skills and /commands discovery. Slim gets a shorter string that still names all three affordances; drop the CSS/JSX clip path. Co-authored-by: Cursor --- .fork/customizations.yaml | 15 +++++----- .fork/notes/FORK-CUSTOMIZATION-DECISIONS.md | 5 ++++ .../__fork_guards__/forkComposerShell.test.ts | 29 +++++++++++++------ apps/web/src/components/chat/ChatComposer.tsx | 16 +++++----- apps/web/src/theme.custom.css | 15 ---------- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.fork/customizations.yaml b/.fork/customizations.yaml index 2ff2fff29a6..7662f9d5898 100644 --- a/.fork/customizations.yaml +++ b/.fork/customizations.yaml @@ -674,14 +674,13 @@ absolutely positioned overlay outside the editor's scroll box — wraps and paints straight through the 48px box, and stably so (the overlay never changes the observed editor height, so the latch cannot rescue it). A - narrow viewport resolves tall regardless. On desktop slim the same overlay - still outruns a short editor column: the placeholder truncates with an - ellipsis (nowrap on the Lexical sibling, overflow-hidden on the prompt - column) rather than wrapping under the inline pills. States the designs do - not draw also resolve tall: an approval request, a pending user-input - question, and the plan follow-up banner all own the box's internals and - carry their own action rows. Attachments deliberately do not force tall — - attachment cards render as a full-width band above the inline row. + narrow viewport resolves tall regardless. On desktop slim the default + empty-state hint is a shorter string that still names @, $, and /; tall + keeps the long form. States the designs do not draw also resolve tall: an + approval request, a pending user-input question, and the plan follow-up + banner all own the box's internals and carry their own action rows. + Attachments deliberately do not force tall — attachment cards render as a + full-width band above the inline row. The control row renders in every density, including collapsed; only its left half is conditional. The run controls are gated on a pending approval — they moved out of the box, which upstream unmounted wholesale during diff --git a/.fork/notes/FORK-CUSTOMIZATION-DECISIONS.md b/.fork/notes/FORK-CUSTOMIZATION-DECISIONS.md index e4f49a8cf39..474c3878027 100644 --- a/.fork/notes/FORK-CUSTOMIZATION-DECISIONS.md +++ b/.fork/notes/FORK-CUSTOMIZATION-DECISIONS.md @@ -103,6 +103,11 @@ Related deep-dives that predate this file and stay where they are: beats them regardless of specificity. - Attachments forcing the tall shell was considered and declined; attachment cards render as a full-width band above the inline row instead. +- Absolutely-positioned content in the prompt column is invisible to the wrap latch by + construction: it never changes the observed editor height. Phone widths already force tall for + that reason; on desktop slim the 64-character default hint wraps under the inline pills. Truncating + it would eat `$use skills` / `/ commands`, which have no other discovery surface — so slim uses a + shorter hint that still names `@`, `$`, and `/`, and tall keeps the long form. - A general weakness worth remembering for future guards: the composer-shell guards assert on the text of the stylesheet rather than on what it matches, so a selector that stops matching (for example, upstream adding a second child inside the frame) keeps every guard green. Selecting diff --git a/apps/web/src/__fork_guards__/forkComposerShell.test.ts b/apps/web/src/__fork_guards__/forkComposerShell.test.ts index a59426fa294..6061771b60e 100644 --- a/apps/web/src/__fork_guards__/forkComposerShell.test.ts +++ b/apps/web/src/__fork_guards__/forkComposerShell.test.ts @@ -303,17 +303,28 @@ describe("fork guard: fork-composer-shell", () => { ).toBe("tall"); }); - it("truncates the slim-shell placeholder instead of letting it wrap under the pills", () => { - // Same absolute-overlay failure mode as the phone case, just on a desktop - // slim row whose editor column is still narrower than the long empty-state - // hint. Truncate on one line; do not apply nowrap to the editor itself. - expect(theme).toMatch( - /\[data-fork-composer-density="slim"\][\s\S]{0,200}\[data-testid="composer-editor"\]\s*~\s*div[\s\S]{0,160}text-overflow:\s*ellipsis/u, + it("shortens the empty-state hint in the slim shell instead of truncating it", () => { + // The absolute placeholder is invisible to the wrap latch (empty prompt ⇒ + // latch false; placeholder only exists when empty). Truncating the 64-char + // default hint would eat `$use skills` / `/ commands`, which have no other + // discovery surface — so slim uses a shorter string that still names all + // three affordances, and tall keeps the long one. + expect(chatComposer).toMatch( + /isComposerSlim\s*\?\s*"Ask anything, @tag, \$skills, \/ commands"/u, ); - expect(theme).toMatch( - /\[data-fork-composer-density="slim"\][\s\S]{0,200}\[data-testid="composer-editor"\]\s*~\s*div[\s\S]{0,160}white-space:\s*nowrap/u, + expect(chatComposer).toContain( + "Ask anything, @tag files/folders, $use skills, or / for commands", + ); + // Truncation was considered and declined: no ellipsis rule on the + // placeholder sibling, so a sync that reintroduces one is a regression. + const rules = cssRules(theme); + const placeholderTruncate = rules.find( + (rule) => + rule.selector.includes('[data-testid="composer-editor"]') && + rule.selector.includes("~") && + (rule.body.includes("text-overflow") || rule.body.includes("ellipsis")), ); - expect(chatComposer).toMatch(/isComposerSlim && "overflow-hidden"/u); + expect(placeholderTruncate, "slim placeholder must not truncate").toBeUndefined(); }); it("keeps the branch controls reachable while collapsed, and gates modes on approval", () => { diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 7bcae7458ba..14fec2e0b94 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -2732,13 +2732,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) {/* fork:begin fork-composer-shell — see .fork/customizations.yaml#fork-composer-shell */}
- {/* overflow-hidden on slim: the absolute placeholder can still paint - past the editor column into the inline pills if something misses - the ellipsis rule; clip at the column edge. */} -
+
{/* fork:end fork-composer-shell */} diff --git a/apps/web/src/theme.custom.css b/apps/web/src/theme.custom.css index cee0bf492e2..56f52cb4f6a 100644 --- a/apps/web/src/theme.custom.css +++ b/apps/web/src/theme.custom.css @@ -381,21 +381,6 @@ } } -/* Slim shell: the empty-state hint is an absolute overlay outside the editor's - scroll box. Wrapping never grows the observed editor height, so the wrap latch - cannot flip the shell to tall — and the second line paints straight through the - inline model pill and send button. Truncate on one line instead. The spacer - sibling is empty, so nowrap is a no-op there. Tall keeps wrapping: the prompt - owns the full width and the box has room. */ -:root[data-fork="noahhendrickson-t3code"] - [data-fork-composer-density="slim"] - [data-testid="composer-editor"] - ~ div { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - /* The in-box pills and the control row below it share one 24px ghost treatment: 12px medium type, a 12px glyph, and a full pill. The designs draw this type at 10px; 12px is the fork's own call, and it matches the prompt beside it.