fix(web): wrap follow-up composer placeholder instead of clipping - #7144
fix(web): wrap follow-up composer placeholder instead of clipping#7144worflor wants to merge 1 commit into
Conversation
After a user sends the first message in a project, ChatPane seeds the composer's PlaceholderCarousel with the design-toolbox next-step prompts. Several are full paragraphs (visualPolish is ~270 chars) and the composer CSS clipped them with white-space: nowrap + text-overflow: ellipsis, hiding 28% of the text at 780px composer width and 47% at 456px. Fix touches three spots in chat.css and one in PlaceholderCarousel.tsx because the wrap, the composer's own height, and the caret alignment all interlock: 1. chat.css relaxed the composer-scoped clip rules to pre-wrap + break-word. Using pre-wrap (not normal) preserves the typewriter's trailing spaces at wrap edges so the caret doesn't jitter to the next line for one frame when a space is typed there. 2. chat.css boosts the composer wrap's min-height to 104px only while the carousel is mounted (:has selector), so up to four wrapped lines fit inside the composer's own box and the toolbar row underneath stays uncovered. The min-height was previously sized for a single-line placeholder because that was the only case the original CSS handled. 3. chat.css line-clamps the carousel text at four lines as a guardrail: any future scenario copy above the current ~270-char ceiling still stays inside the wrap's 104px, so a monster prompt cannot overtake the composer chrome. 4. PlaceholderCarousel rendered the blinking caret as a sibling flex item to the text span. That aligns fine on one line but strands the caret at a cross-axis position once text wraps. Nesting the caret inside the text span puts it in the same inline flow, so it follows the last character across wrapped lines. home-hero.css gives the caret inline-block defaults so the same element works in both the home-hero and composer contexts. Home-hero composer is unchanged. Short scenarios still render single-line with the caret inline right after the last character. Coverage: - Vitest structural lock at apps/web/tests/components/home-hero/ PlaceholderCarousel.caret-inline.test.tsx locks the DOM shape. - Playwright layout spec at e2e/ui/composer-carousel-placeholder-wrap.test.ts drives a real fake-codex daemon turn, waits for visualPolish under prefers-reduced-motion, measures the carousel at 780/640/456px. - Playwright fuzz spec in the same file hammers the CSS with baseline (~270 chars), double, quadruple, 600-char unbreakable string, punctuation-only, and CJK text across 780/456/360px composer widths. All cap at four lines with no wrap overflow. Both went red on main and green on this branch.
|
Heads-up: PR #7023 is also open against this area — both PRs touch |
|
Thanks for marking this ready, @worflor. Since this changes visible composer behavior, I've added the QA-required label so it gets a manual pass before merge — please hold off self-merging for now; we'll loop QA in once it's merge-ready. |
mrcfps
left a comment
There was a problem hiding this comment.
@worflor thanks for the careful wrap fix — this is a really solid bug follow-up.
I reviewed the five-file diff against the stated goal (follow-up composer placeholders wrap instead of ellipsis-clipping, caret stays on the last glyph, toolbar stays uncovered, home-hero stays single-line).
Verified:
PlaceholderCarouselnests the caret in the text span so it can follow wrapped glyphs, while home-hero CSS keepsnowrap+ ellipsis so the landing composer does not pick up the new wrap behavior.- Composer-scoped
pre-wrap/break-word/ 4-line clamp plus the:has()104px min-height are internally consistent with the 13px × 1.6 × 4 + padding math, and they do not leak into.home-hero__prompt-editor. - Tests sit on the right seams: the jsdom spec locks the DOM shape, and the Playwright file covers the real follow-up path plus a CSS fixture matrix (baseline / 2× / 4× / unbreakable / punctuation / CJK across widths).
No correctness, safety, or maintainability issues in the changed ranges. Nice work on the red-on-main / green-on-branch loop.
🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.
Why
The composer's placeholder rotates through the design-toolbox next-step prompts after you've sent a message in a project. Several are full paragraphs (visualPolish is ~270 chars), and the current composer CSS clips them with an ellipsis at every realistic width:
Half the suggestion sits behind
….The wrap fix touches four spots because they interlock:
apps/web/src/styles/chat.csshadwhite-space: nowrap+text-overflow: ellipsison the composer's carousel, inherited from the home-hero context where scenarios are one-liners. Relaxed topre-wrap + break-word. Usingpre-wrap(notnormal) preserves the typewriter's trailing spaces at wrap edges so the caret doesn't jitter to the next line for one frame when a space is typed there.chat.cssboosts the composer wrap'smin-heightto104pxonly while the carousel is mounted (:has()selector), so up to four wrapped lines fit inside the composer's own box and the toolbar row underneath stays uncovered. The previous height silently assumed a single-line placeholder because that was the only case the original CSS handled.chat.cssline-clamps the carousel text at four lines as a guardrail. Any future scenario copy above the current ~270-char ceiling still stays inside the wrap's 104px, so a monster prompt cannot overtake the composer chrome.PlaceholderCarouselrendered the blinking caret as a sibling flex item to the text span. That aligns fine on one line but strands the caret at a cross-axis position once text wraps. Nesting the caret inside the text span puts it in the same inline flow, so it follows the last character across wrapped lines.home-hero.cssgives the caret inline-block defaults so the same element works in both the home-hero and composer contexts.What users will see
After the first message in a project, the composer's rotating placeholder wraps to fit the composer width instead of ellipsis-truncating. The caret sits right after the last typed character on the last wrapped line. Toolbar row underneath stays uncovered.
Home-hero composer is unchanged.
Surface area
apps/webScreenshots
Top plate is
main, bottom plate is this branch. Red marks the pre-fix, green marks the fix.Bug fix verification
Three red specs, one per boundary:
apps/web/tests/components/home-hero/PlaceholderCarousel.caret-inline.test.tsx(Vitest, jsdom) locks the DOM shape (caret nested in the text span, not a sibling). Red onmain:caret must be a descendant of the text span: expected null not to be null.e2e/ui/composer-carousel-placeholder-wrap.test.tslayout spec (Playwright, Chromium) drives a real fake-agent daemon turn using the existingreal-daemon-run.test.tsharness, waits for the visualPolish prompt underprefers-reduced-motion: reduce, measures the carousel at 780/640/456px viewports. Red onmainat 456px:carouselHeight: 21, lineCount: 1, whiteSpace: "nowrap", textOverflow: "ellipsis", caretGapDx: -780(caret 780px left of where the visible text ends).main:line-clamp breach at 456px [double (~540 chars)]: {"lineCount":26,...}.All three went red on
mainand green on this branch.Real-app verification: drove the fix end-to-end through the desktop shell's chat pane (fake-agent runtime, viewport 1440x1100), confirmed the composer's toolbar row stays uncovered, the caret sits after the last visible character on line 4, and the wrap's box math is clean (
carouselOverflowsWrap: false,wrapHeight: 104,carouselHeight: 83).Validation
pnpm --dir apps/web exec vitest run -c vitest.config.ts tests/components/home-hero/PlaceholderCarousel.caret-inline.test.tsx tests/components/home-hero/PlaceholderCarousel.paused.test.tsx tests/components/PlaceholderCarousel.test.tsx --maxWorkers=1: 7 passedpnpm --dir e2e exec playwright test -c playwright.config.ts ui/composer-carousel-placeholder-wrap.test.ts --workers=1: 2 passed (~2m)pnpm --filter @open-design/web typecheck: cleanpnpm --dir e2e exec tsc -p tsconfig.json --noEmit: cleanpnpm guard: one pre-existing failure unrelated to this PR (packaged-leaf boundary complaining that.github/workflows/ci.ymlno longer contains its guarded tools-dev + packaged unit + focused E2E block; same failure reproduces on stockmainwith these edits stashed; no.github/files touched here).