Skip to content

Composer: actions and settings in a menu at the bar's left; chips move into it when narrow - #677

Merged
danbarua merged 1 commit into
mainfrom
composer-menu
Oct 15, 2026
Merged

danbarua merged 1 commit into
mainfrom
composer-menu

Conversation

@danbarua

@danbarua danbarua commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Problem

  • The composer's bar held a button each for attaching files, commands, mentions and the session settings, then a chip each for the mode, model and thinking level, then Send.
  • In the chat sidebar, whose composer box is 344 pixels wide at the sidebar's default 400, and at phone width, the bar wrapped onto three rows. Send sat alone on the last row with the settings gear.
  • At the same widths, the placeholder "Message the agent · / for commands · @ to mention" wrapped onto a second line, which the one-line box cut off.

Change

One commit. @labkit/ui's composer has a menu button at the bottom-left of its bar.

What Where it is now
Attach files, Commands, Mention, Session settings Rows of the menu, at every width. While the composer takes no input yet, Attach files, Commands and Mention are disabled rows, as their buttons were.
Mode, model and thinking chips In the bar while the composer's box is at least 480 pixels wide. Below that width they are rows of the menu that show their values. A boolean one is a checkbox row.
Placeholder "Message the agent" below 480 pixels; as before otherwise.
  • ComposerMenu (composer/composer-menu.tsx) follows the WAI-ARIA menu button pattern:
    • Down or Up on the button opens the menu on its first or last row.
    • Up and Down move through the rows and wrap. Home and End jump to the first and last row.
    • Escape, Tab or a press outside closes the menu. Tab leaves focus on the button rather than moving on.
    • Choosing a row closes the menu and puts focus on the button first, so a picker the row opens hands focus back to the button when it closes.
  • The menu is the composer's fifth overlay state, beside its four pickers, so one state decides what is open.
  • useNarrowerThan (base/media.ts) measures an element with a ResizeObserver, before the first paint. A CSS container query could not change the placeholder text, and Chrome does not apply text-overflow: ellipsis to a textarea's placeholder.
  • The bar no longer wraps. Between 480 pixels and the chips' full width, the chips' values shorten with an ellipsis.
  • IconButton passes a ref through to its button.

What a person can see change

  • Attach files, Commands, Mention and Session settings are in the menu at the bar's left, not buttons in the bar.
  • In the chat sidebar at its default width, the bar is the menu button and Send. The mode, model and thinking level are in the menu. The chips come back when the sidebar is dragged to about 540 pixels or wider: the composer's box is the sidebar's width less about 56 pixels of padding and border.
  • In a narrow composer, the placeholder reads "Message the agent".

Not done

  • The graph card's header at phone width, which squeezes out the "Graph 0" title. This waits for the mobile work.
  • Reloading the workspace page with a ?session= the agent does not hold shows "Session … not found" in the chat. Seen while checking this, on chats that had been opened and never sent a message; the cause was not looked into, and nothing here changes it.

Controls

  • tests/composer-menu.test.tsx, new:
    • which actions and settings become rows, for a wide and a narrow composer, and for one that cannot change its settings;
    • the menu's markup: the button's aria-haspopup="menu" and aria-expanded, the menu and menuitem roles, aria-haspopup="dialog" on a row that opens a picker, aria-disabled, the checkbox row, the separator between groups, and no button with no rows;
    • the bar's menu button, chips and file chooser.
  • The tests that looked for the old buttons in the composer's markup now check the menu's rows: the composer that takes no input yet, the file chooser, the settings row, and a composer with no commands or mentions.
  • Three end-to-end tests on the gallery's composer, development only:
    • The menu opens above its button, the arrows wrap, and Escape closes it onto the button.
    • Commands opens its picker, and Escape hands focus back to the menu button. With the button's focus call removed, this test fails.
    • At 1280 pixels the model is a chip and not a row. At 390 pixels there is no chip, the placeholder is the short one, and the Model row opens the model picker.

Verification

  • bun run check: all 28 passed; bun test 1125 pass, 0 fail.
  • E2E_PORT_API=9019 E2E_PORT_DEV=8970 E2E_PORT_BUILT=8971 bun run e2e: 180 passed, 0 failed, 14 skipped. All 14 skipped tests are in the built project: the 11 development-only pages as before, and the three new gallery tests.
  • Checked by hand in Chrome on the :8850 dev stack, in the gallery at composer box widths of 311, 466, 496 and 818 pixels, and in the workspace page's chat at 390 and 600 pixels.

Linked issues

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GVjyjkaADPAqLvtnf3xUNs

The composer's bar held a button each for attaching files, commands,
mentions and the session settings, then a chip each for the mode, model
and thinking level. In the chat sidebar, whose composer box is 344
pixels wide by default, the bar wrapped onto three rows, and the
placeholder wrapped onto a second line that the one-line box cut off.

- `ComposerMenu` is a menu button at the bar's left, with a menu that
  opens upward from it, after the WAI-ARIA menu button pattern: arrows,
  Home and End move through the rows, Escape, Tab or a press outside
  closes it, and focus returns to the button. A row that opens a picker
  leaves focus on the button, so the picker hands focus back to it.
- Attach files, Commands, Mention and Session settings are rows of the
  menu at every width. A row the composer cannot use yet is disabled.
- The mode, model and thinking chips stay in the bar while the
  composer's box is at least 480 pixels wide. Below that width they are
  rows of the menu showing their values, and the placeholder reads
  "Message the agent". `useNarrowerThan` measures the box before the
  first paint.
- The bar no longer wraps: the chips' values shorten when space runs out.
- `IconButton` passes a `ref` through to its button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVjyjkaADPAqLvtnf3xUNs
@github-actions github-actions Bot added the area:web The web app and shared UI: app-web, ui, design, records, view-model, acp-client label Oct 15, 2026
@danbarua
danbarua merged commit eb188ba into main Oct 15, 2026
10 checks passed
@danbarua
danbarua deleted the composer-menu branch October 15, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:web The web app and shared UI: app-web, ui, design, records, view-model, acp-client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant