feat(ui): split the composer add menu into upload, modes and skills - #1716
Merged
Conversation
The composer hid three unrelated capabilities behind one + menu: file import, the Plan/Swarm/Graph switches with the expert teams, and nothing at all for Skills — those were reachable only by typing `/`, which is discoverable once you know it exists and invisible otherwise. Each capability now has its own named toolbar trigger: - upload: a direct button (Upload mark) that opens the file picker in one click instead of two. Import still no-ops mid-turn, so it disables while streaming rather than vanishing. - modes: one menu holding Plan/Swarm/Graph plus the 专家团 group. It stays reachable mid-turn (#1444), and #1433's subtraction still holds — the switches are menu items, not standalone toolbar switches. - skills: a picker panel with a search field, 全选 / 清除全部, and checkbox rows. It writes the SAME structured selection `/` produces (useComposerSkillDraft), so both paths share one draft rather than a second selection model. The panel is an absolute overlay anchored to its trigger, so opening it never grows the composer box (composer-constant-footprint-contract).
CI's desktop contract suite caught six violations the @maka/ui tests and
the check-* scripts do not cover:
- role="dialog" belongs to the Astryx Dialog primitive, which owns focus
trapping, Escape, and the top layer. The picker is a disclosure the
trigger's aria-expanded/aria-controls already describe, so it is a
labelled role="group" instead (dialog-source-contract).
- outline-offset must be var(--focus-ring-offset); the inset calc() is
gone (focus-ring recipe).
- the count badge used a bare 3px pad, a line-height of 1, and a
--font-size-2xs fallback literal. It now sits on --space-0-5,
--leading-none, and --font-size-caption, and is sized to fit that
smallest governed size.
- the primitives contract pinned the retired + trigger's button={{...}}
shape; it now pins the standalone upload IconButton (which carries the
import-pending state) and the modes trigger that kept the governed
icon-only ghost/sm shape.
jackwener
force-pushed
the
feat/composer-toolbar-split
branch
from
July 31, 2026 17:20
f12b1b8 to
6cd2490
Compare
jackwener
added a commit
that referenced
this pull request
Jul 31, 2026
These 149 tests read the project's own source and CSS off disk and asserted on its text. They imported nothing under test and ran no code, so they could only ever catch what a linter catches — "don't write a bare 3px", "don't hand-write role=dialog" — while charging every refactor a rewrite of its own guards. #1711 deleted two of them to land an Astryx migration; #1716 had to rewrite six to move three buttons. Deleted: 149 test files (~675 tests) plus one orphaned source helper. Kept: the 219 behavioral tests in the same directory, every e2e spec, and the fast scripts/check-*.mjs commands (dead-css, a11y, copy, console) that cover the same ground in seconds without a build. Also kept, deliberately, seven static guards that protect non-cosmetic invariants rather than styling: the preload/IPC surface pins, the renderer token-leak gate, external-link noopener, and secret redaction. Those are cheap and guard things review reliably misses. The CSS governance doc kept pointing at deleted guards, so its enforcement section now says what actually holds the line: conventions plus the check-* scripts, verified on the rendered surface.
jackwener
added a commit
that referenced
this pull request
Jul 31, 2026
* chore(desktop): drop the source-scanning contract test suite These 149 tests read the project's own source and CSS off disk and asserted on its text. They imported nothing under test and ran no code, so they could only ever catch what a linter catches — "don't write a bare 3px", "don't hand-write role=dialog" — while charging every refactor a rewrite of its own guards. #1711 deleted two of them to land an Astryx migration; #1716 had to rewrite six to move three buttons. Deleted: 149 test files (~675 tests) plus one orphaned source helper. Kept: the 219 behavioral tests in the same directory, every e2e spec, and the fast scripts/check-*.mjs commands (dead-css, a11y, copy, console) that cover the same ground in seconds without a build. Also kept, deliberately, seven static guards that protect non-cosmetic invariants rather than styling: the preload/IPC surface pins, the renderer token-leak gate, external-link noopener, and secret redaction. Those are cheap and guard things review reliably misses. The CSS governance doc kept pointing at deleted guards, so its enforcement section now says what actually holds the line: conventions plus the check-* scripts, verified on the rendered surface. * chore(desktop): drop postcss and unused source helpers with the suite The deleted CSS-parse contract was postcss's only consumer, and the tailwind-compile contract was the only TS importer of tailwindcss — which styles.css still needs through `@import "tailwindcss"`, so it stays declared and is now ignored by knip instead. Nine helper exports lost their last readers with the suite; knip must report zero for apps/desktop.
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
The composer hid three unrelated capabilities behind one + menu: file import, the Plan/Swarm/Graph switches with the 专家团 group, and — for Skills — nothing at all. Skills were reachable only by typing
/, which is discoverable once you know it exists and invisible otherwise.Each capability now gets its own named trigger in the composer toolbar:
runImportAction/ drop target), so it disables while streaming rather than vanishing./popup produces (useComposerSkillDraft), so both paths share one draft instead of a second selection model.The panel is an absolute overlay anchored to its own trigger, so opening it never grows the composer box (composer-constant-footprint-contract) — the same discipline the mention popup follows. Esc / outside click dismiss it, and Esc returns focus to the trigger.
Copy is locale-aware in both zh and en;
composer.add/composer.addTitleretire with the + trigger they labelled.Verification
npm --workspace @maka/ui run typecheck+ clean build +test:dist— 290 passed, including the newcomposer-skill-picker.test.ts(key/filter helpers, closed-trigger markup, localization) and the updatedconversation-localizationcontracts.tsc -p tsconfig.renderer.jsonandtsconfig.storybook.json— clean.e2e/composer-toolbar-controls.spec.ts(2 tests — three separate triggers present with no + left over; picker → chip → count badge → Esc focus return → 清除全部) and the existingcomposer-mode-indicator.spec.ts+composer-skill-invocation.spec.ts(7 tests, proving the/path still shares the same draft) — 9 passed.biome lint,check-a11y,check-copy,check-console— clean.build:renderer) — clean.Review focus
The Skills picker is new UI surface: the panel geometry (340px, list capped at 240px with its own scroll) and the checkbox row semantics (
role="checkbox"buttons inside the labelled dialog) are the parts most worth a second opinion.