Responsive Composer controls
Scope: environment chips above the chat input, and permission, more, active tools, chat model, execution model, effort and context controls below it.
Current implementation and gaps
.composer-bottom-row has no fixed breakpoint either: its need depends on which tool toggles are on and how long the model names are. use-compact-controls-row.ts measures a hidden copy of the row placed beside it at max-content width, with flex shrinking and transitions off (data-measuring). It sums the copy's clusters and the gap, removes the copy before paint, and sets data-compact when that exceeds the row width. The live row is never toggled to measure it: its Buttons transition every property, so a row that just left compact mode would still report compact widths. The row re-measures on resize, on content or class changes and when web fonts finish loading; the permission, model and effort labels then collapse to their role icons. Expanding again needs 12px of slack. .envChips No fixed breakpoint: chat and DAG pages have different numbers of controls and therefore different space requirements at the same width. It picks the first level whose chips fit in its clientWidth, measuring the right edge of the last chip with the right-aligned chips' auto margins zeroed (scrollWidth cannot be used: that auto margin makes the content always fill the row, so a squeezed row would never expand again): it lays the row out at each level with transitions off (data-measuring), then animates to the chosen data-squeeze level; level 3 also sets data-compact, which hides all labels. Stepping back to a roomier level needs 12px of slack. Single-page and split-pane layouts use exactly the same measurement logic.
Existing components already provide four distinct icons: Shield for permission, Message for chat model, Terminal for execution model and Biceps for effort. Every trigger already has a Popover/Menu, tooltip or accessible name, so no new icon components or menus are added. The effort trigger opens the effort picker card; its header, dot-matrix track and Recommended caption are specified in effort-picker.html.
Component boundaries
Composer directories follow interaction responsibilities: input editing belongs in input/, submission in submit/, mode dispatch in modes/, session settings in state/, while attachments, paste, slash and controls retain their directories. The environment bar separates overall layout from individual chips; ProjectBadge, WorkingDirChips and GoalChip stay in shared business modules. The DAG module owns its HUD and supplies it through EnvironmentRow's generic trailing-content slot; EnvironmentRow contains no DAG names, DOM ids or control logic.
| Directory or file | Sole responsibility | Not responsible for |
|---|---|---|
composer/environment-row/environment-row.tsx | Compose environment-bar subcomponents and render caller-supplied trailing content. | No DAG names, portal ids, chat submission or project state. |
composer/environment-row/use-compact-environment-row.ts | Measure actual environment-bar width and maintain the compact attribute. | Does not render components or define visual styles. |
composer/environment-row/environment-row.module.css | Environment-bar layout, pill surfaces, four-level squeeze animation and DAG-slot styling. | No input, bottom controls or other Composer modes. |
composer/environment-row/chips/connection-status-chip.tsx, web-surface-chip.tsx | Connection-status entry and current-web-surface entry, respectively. | Do not control the environment bar's overall responsive state. |
composer/input/ | Textarea, keyboard events and history callbacks. | Does not send messages or switch Composer mode. |
composer/attach/, paste/, modes/question/ | Keep component implementation and dedicated CSS Module in the same responsibility directory. | Do not leave component-specific selectors in the root composer.module.css. |
composer/submit/ | Construct and submit chat messages and maintain submission side effects. | Does not render the input area. |
composer/modes/composer-body.tsx | Select ordinary input, function form or question form from the derived mode. | Does not own input, attachment or session state. |
composer/state/use-composer-settings.ts | Read and update Composer settings for the current SessionScope. | No global focused-session fallback. |
composer/controls/use-*.ts | Manage model availability, tool profiles, unattended state and existing controls, respectively. | Do not request or synchronize these states directly in the Composer root. |
composer/index.tsx | Pass session, tools and toggle arguments and render one EnvironmentRow. | Does not hold environment-bar refs, observers or internal JSX. |
scripts/chat/check-composer-environment-row.mjs | Check only environment-bar component boundaries and responsive contracts. | Do not accumulate unrelated chat-UI checks. |
References and choices
| Reference | Applicable capability | Adoption |
|---|---|---|
| W3C CSS Containment Level 3 | @container Control descendant styles from component-container size rather than viewport size. | Kept only for the context ring label and the effort card width; the label collapse is measured instead, because the row's need is not a function of its width alone. |
| WAI accessible names | Interactive controls must retain computable names when visible labels are hidden. | Reuse existing tooltips, Popover triggers and labels; visual hiding does not remove DOM semantics. |
| Horizontal scrolling | Preserves arbitrary-length text, but moves some settings entries outside the visible area. | Retain as a fallback for extremely narrow widths, not the primary responsive behavior. |
| Wrap into two rows | Keeps all text visible, but changes composer height and transcript-bottom spacing. | Rejected; this task preserves one row. |
Target presentation
Control-button surface hierarchy
Top environment chips use fill and edge hierarchy, while bottom controls are only 20px high. Bottom permission, more, active tools, chat model, execution model, effort and context controls reuse only theme-level --chip-bg for an always-visible subtle fill. Resting surfaces add no border, inset stroke or outer shadow, preserving internal visual space in small controls. Existing hover foreground colors and keyboard focus-visible indicators remain unchanged.
| State | surface | Unchanged |
|---|---|---|
| Resting | background:var(--chip-bg); computed border is 0 and shadow is none | 20px height, 5px radius, icon and label colors |
| hover / open | Mix 8% of --chip-bg into --text-bright | Existing menus, tooltips, animations, hover foreground and effort-level colors |
| Narrow | Same surface as resting state; visually hiding labels does not change it | Four core triggers remain 20px icon buttons |
Context-statistics popover timing
ContextBreakdownPanel Unmounts when closed. If every mount clears existing results and enters loading, reopening the same session and DAG branch first shows a card containing only Loading…, followed by statistics. Even a short network request makes this whole-card replacement visible.
| Scenario | Immediate display | Background behavior |
|---|---|---|
| Reopen the same session and branch | Last successfully retrieved complete statistics | Request again and update in place without whole-card loading |
| First opening or no cache | Existing loading empty state | Request current statistics and cache on success |
| Switch session or DAG branch | Read only the cache for the new sessionId + headId key | Cancel the old request; old responses must not overwrite the current panel |
| Request fails with an existing cache | Keep cached content | A failed refresh must not replace usable statistics with an error page |
Cache exists only in the current frontend process, retaining 32 session branches in least-recently-used order. No persistence, periodic refresh or new dependency. For concurrent refreshes of one key, only the newest request may update the cache. The server remains the sole source of statistics; caching only removes the transitional screen on reopening.
Compaction rules
- Environment-chip labels do not participate in flex shrinking; a capped name ends in an ellipsis, never a cut-off fragment.
- When content overflows, the row moves one level tighter until it fits: capped names, then hidden branch names, then hidden labels with the diff dot; it moves back as space returns.
- Icon triggers preserve size, order and operation.
Interaction and accessibility
- Clicking an icon opens its original menu.
- Chat and execution use distinct icons.
- Effort-icon color continues to indicate the current level.
- Focus, tooltips and accessible names remain when labels are hidden.
Boundaries
- Do not change model, permission or effort state.
- Use native ResizeObserver and MutationObserver without new dependencies.
- No wrapping and no hiding entire controls.
- Do not change composer height or splitRatio.
Implementation brief and acceptance
Top-row implementation is confined to apps/web/components/chat/composer/environment-row/: component, measurement hook, styles and two dedicated chips are separated by responsibility.composer/index.tsx only renders EnvironmentRow. The Context-cache batch is limited to apps/web/components/chat/context-badge.tsx, context-breakdown-panel.tsx and apps/web/lib/chat/context-breakdown-cache.ts. Independent checks are provided by apps/web/scripts/chat/check-composer-environment-row.mjs and included in npm --prefix apps/web run check:chat-ui. These batches do not change Popover, Menu, providers, session store or submission logic.
- Public-entry checks must establish that actual overflow squeezes environment chips through the
data-squeezelevels, ending indata-compact, with no fixed-width special cases or label-levelflex-shrink:1. - Status, surface, project, directory, Fit, zoom percentage and Legend share one animation rule; do not use
display:noneor collapse only some labels. - In the default installed App, dragging the split-pane divider left past the threshold keeps all four right settings entries and left permission/more controls visible without overlap.
- Click permission, chat model, execution model and effort icons separately; original menus or panels open normally.
- Widen the pane until full content fits and all labels return together; reselecting split, switching chat/DAG and refreshing preserve this behavior.
- Every visible bottom control has a nontransparent
--chip-bgsurface without hover, computed border 0 and shadow none; hover or open menus must have a stronger background. Keyboardfocus-visibleindicators are not resting borders and remain available. - After Context statistics load successfully once, closing and reopening the same session and branch immediately reuses cache; background completion updates cache and ring statistics.
- Context cache is isolated by session and branch; rapid switches cancel stale requests, and failed refreshes cannot overwrite existing cache.
Implementation status
| Item | State |
|---|---|
| Design and task boundaries | Complete |
| public-entry RED / GREEN | Complete: the static entry check first failed for the missing narrow-state effort icon, then passed after implementation; real App E2E covers 700px and 360px containers. |
| specification review / quality review | Complete: specification review and independent quality review after repairs both passed. |
| Full checks and default installed-App visible acceptance | Complete: all Web checks, TypeScript, build, desktop webtab/transfer, unit tests and default installed-App CDP acceptance passed. |
| Bottom-control resting surface | Complete: resting state uses only --chip-bg fill without borders or inset strokes; real App computed styles verify border 0 and shadow none, while hover/open only strengthen the background. |
| Composer responsibility directories | Complete: input, submission, mode, session state, attachments and environment chips have separate responsibility directories; input, paste, image attachments and question panels use colocated CSS Modules. EnvironmentRow accepts generic trailing content only, with no DAG ids or control logic. |
| Repeated Context-statistics opening | Implemented: session/branch-isolated cache immediately displays previous results on reopening and refreshes in the background.check:chat-ui executably verifies cache isolation, late-request cancellation, reverse-order responses for one key, retention on refresh failure and 32-entry eviction, and requires synchronous panel recreation by session/branch key. |
Visible acceptance uses the actual /Applications/OpenProgram.app, default 18100 page and 9223 CDP. Tests operate real permission, more, active tool, chat model, execution model, effort and context triggers, checking borderless resting states, stronger hover/open backgrounds, no narrow-state overlap and working menus, then close menus and restore focus and original page styles.