refactor: give CollapsibleText a children-based max-height variant and migrate AgentCard onto it (#4170) - #4265
Merged
Merged
Conversation
…d migrate AgentCard onto it (#4170) AgentCard's TaskDescription was a second "long text with a Show more toggle" implementation one directory over from the shared primitive. It guessed at overflow from `text.length > 200`, capped with a bespoke `max-h-[3.5rem]` + gradient fade, and its toggle carried no `aria-expanded`/`aria-controls`. CollapsibleText now supports a `children` clamp strategy — a `maxHeight` cap for content CSS `line-clamp` cannot clamp, i.e. anything emitting block children like rendered markdown. The ResizeObserver attaches to the uncapped inner wrapper as well as the capped container, since the outer box never changes size when its children grow.
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
client/src/components/cos/tabs/AgentCard.jsx'sTaskDescriptionwas a second "long text behind a Show more toggle" implementation sitting one directory over from the sharedclient/src/components/ui/CollapsibleText.jsx. It guessed at overflow fromtext.length > 200, clamped with a bespokemax-h-[3.5rem]+ gradient fade, and its toggle carried noaria-expanded/aria-controls.It was never consolidated because
AgentCardrenders markdown blocks viaMarkdownOutput, and CSSline-clampapplies to a container's own inline content — against block children it silently does nothing.So
CollapsibleTextnow offers a second clamp strategy, picked by which content prop you pass:text(unchanged) — line-clamp atlinesdepth.children(new) — amaxHeightcap (default3.5rem) for contentline-clampcan't clamp.childrenwins overtext.Both strategies share the same measured-overflow logic, the same toggle, and the same
aria-expanded/aria-controlswiring, so the disclosure affordance and its accessibility are now identical everywhere.One wrinkle worth calling out for review: in
childrenmode theResizeObserverattaches to the uncapped inner wrapper as well as the capped container. The outer element is height-capped, so growing children never change its box — an observer bound to it alone would never fire for content that arrives or changes after mount. Observing the inner wrapper also meanschildren(a fresh element object every render) doesn't have to go in the effect deps, which would churn the observer on every parent re-render — andAgentCardre-renders once a second while an agent is running.Visible change: AgentCard's fade treatment goes away
Flagged in the issue and repeated here because it's a deliberate, user-visible regression in polish:
bg-gradient-to-t from-port-card) over the bottom of a clamped description is gone. The shared primitive clips flat.hover:text-port-accent/80rather thanhover:text-white.text.length > 200. This is a behavior fix, not just a visual one: a 300-character description that fit inside the cap previously rendered a no-op "Show more" that expanded to nothing, and a short-but-tall description (a few bullet list items) previously got clipped with no affordance at all. Both are covered by new tests.ResizeObserverpath).Other files
client/src/components/ui/README.md— catalog row updated to name both strategies.No changes to the three existing
text-mode call sites (TaskItem.jsx×4,Review.jsx×2) — the line-clamp path,expandedContent,expandedClassName,lines, andforceToggleall behave exactly as before, and their existing tests are untouched and green.Test plan
client/src/components/ui/CollapsibleText.test.jsx— 7 new cases for thechildrenvariant: caps and uncaps on toggle, no toggle when the children fit,aria-controls/aria-expandedwiring,classNameforwarding,childrenpreferred overtext, the observer covering the inner wrapper as well as the container, and the in-flight-resize race that must not drop the toggle mid-expand. All 12 pre-existing line-clamp cases untouched and passing.client/src/components/cos/tabs/AgentCard.test.jsx— 2 new cases: a long description gets anaria-wired toggle that lifts the cap on click, and a 300-character description that fits gets no toggle (the case the oldlength > 200heuristic got wrong). Both fail against the pre-change component.cd client && npm test— 647 files, 7888 tests. The 2 failures are insrc/pages/SongBookViewer.test.jsx(anact()warning inSongLinksEditor), untouched by this branch and passing when that file runs on its own — a known full-suite parallelism flake.cd client && npm run lint— clean (2042 files, biome,--error-on-warnings).Closes #4170