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.

Each control row responds to its own available width. The bottom row retains existing settings; the top row squeezes in four levels. Full labels first; then long names cap at about 12 characters with an ellipsis and the Local chip turns icon-only; then git branch names hide, leaving the branch icon and diff badge; finally every label (status, surface, project, directory, DAG Fit, zoom percentage, Legend) hides and the diff badge becomes a half-green, half-red dot. The row steps back up as space returns. One transition animation governs each step; menus, values, keyboard operation, tooltips and accessible names remain unchanged.

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 fileSole responsibilityNot responsible for
composer/environment-row/environment-row.tsxCompose 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.tsMeasure actual environment-bar width and maintain the compact attribute.Does not render components or define visual styles.
composer/environment-row/environment-row.module.cssEnvironment-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.tsxConnection-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.tsxSelect ordinary input, function form or question form from the derived mode.Does not own input, attachment or session state.
composer/state/use-composer-settings.tsRead and update Composer settings for the current SessionScope.No global focused-session fallback.
composer/controls/use-*.tsManage model availability, tool profiles, unattended state and existing controls, respectively.Do not request or synchronize these states directly in the Composer root.
composer/index.tsxPass 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.mjsCheck only environment-bar component boundaries and responsive contracts.Do not accumulate unrelated chat-UI checks.

References and choices

ReferenceApplicable capabilityAdoption
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 namesInteractive 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 scrollingPreserves 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 rowsKeeps all text visible, but changes composer height and transcript-bottom spacing.Rejected; this task preserves one row.

Target presentation

◇Ask permissions+
◯Chat model〉_Execution model⌁Medium◌
◇Ask permissions+
◯Chat model〉_Execution model⌁Medium◌

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.

StatesurfaceUnchanged
Restingbackground:var(--chip-bg); computed border is 0 and shadow is none20px height, 5px radius, icon and label colors
hover / openMix 8% of --chip-bg into --text-brightExisting menus, tooltips, animations, hover foreground and effort-level colors
NarrowSame surface as resting state; visually hiding labels does not change itFour 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.

ScenarioImmediate displayBackground behavior
Reopen the same session and branchLast successfully retrieved complete statisticsRequest again and update in place without whole-card loading
First opening or no cacheExisting loading empty stateRequest current statistics and cache on success
Switch session or DAG branchRead only the cache for the new sessionId + headId keyCancel the old request; old responses must not overwrite the current panel
Request fails with an existing cacheKeep cached contentA 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

  1. Environment-chip labels do not participate in flex shrinking; a capped name ends in an ellipsis, never a cut-off fragment.
  2. 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.
  3. 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.

  1. Public-entry checks must establish that actual overflow squeezes environment chips through the data-squeeze levels, ending in data-compact, with no fixed-width special cases or label-level flex-shrink:1.
  2. Status, surface, project, directory, Fit, zoom percentage and Legend share one animation rule; do not use display:none or collapse only some labels.
  3. 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.
  4. Click permission, chat model, execution model and effort icons separately; original menus or panels open normally.
  5. Widen the pane until full content fits and all labels return together; reselecting split, switching chat/DAG and refreshing preserve this behavior.
  6. Every visible bottom control has a nontransparent --chip-bg surface without hover, computed border 0 and shadow none; hover or open menus must have a stronger background. Keyboard focus-visible indicators are not resting borders and remain available.
  7. After Context statistics load successfully once, closing and reopening the same session and branch immediately reuses cache; background completion updates cache and ring statistics.
  8. Context cache is isolated by session and branch; rapid switches cancel stale requests, and failed refreshes cannot overwrite existing cache.

Implementation status

ItemState
Design and task boundariesComplete
public-entry RED / GREENComplete: 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 reviewComplete: specification review and independent quality review after repairs both passed.
Full checks and default installed-App visible acceptanceComplete: all Web checks, TypeScript, build, desktop webtab/transfer, unit tests and default installed-App CDP acceptance passed.
Bottom-control resting surfaceComplete: 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 directoriesComplete: 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 openingImplemented: 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.