Review — Command palette (8 Sep 2026)

The one sub-component the AD approved as canon (7 Sep 2026, ACRM-134) had an empty Examples tab: the registry recorded "No Ladle stories — the component lives in apps/web, not packages/ui", so the DS site showed a canon lozenge over nothing. This pass closes that hole and records what the work uncovered.

Source of truth: origin/main acrm (e31039e1a, worktree worktree-ds-command-palette-stories). Everything below was measured in a browser against the built catalog, not read out of the CSS. Preview key ds-command-palette.

0. Summary

The palette is one of the better-argued components in the system: the CSS says why every value is what it is, cites the approved wireframe, and marks each deviation from a token as a deliberate carry-over. Nothing about its look needed changing. What the pass found is of two kinds: two places where the code and its own documentation have drifted apart since the two-column rework (M3B B3), and two errors in the canon record itself — the kind another team's agent would read out of _index.json and act on.

The premise "an app component cannot be catalogued" was also wrong: Filter bar has been catalogued for weeks by rebuilding its markup 1:1 from the canonical classes. The palette is now catalogued the same way — five stories, no import from apps/web, and the cursor genuinely drives the right column so the catalog shows how the surface works, not only how it looks.

1. Findings in the design system

A1 — The empty state's two actions sat flush against the left edge — FIXED (ACRM-161, 8 Sep 2026)

.acrm-empty centres itself (ds.css:1588: align-items: center, text-align: center), while .gsp-acts immediately after it is a plain flex row with no justify-content and no inset (global-search.css). In an 800px palette that measures:

measured
dialog width800px
„Založit osobu" left inset0px
centre offset of the empty-state text0px

So the user read a centred sentence and then found the two buttons that answer it hard against the left edge of the dialog.

Decided (AD, ACRM-161 — „tlačítka vycentruj", drawn on this very catalogue page): centre them. justify-content: center on .gsp-acts in apps/web/src/ds/global-search.css, not padding — an inset would move the buttons off the edge but still not line them up with the sentence they answer. The sentence and its actions are one empty-state block, so they share one axis.

Measured after the fix (800px palette): group centre offset 0px, sentence centre offset 0px, inset 290px left == 290px right, gap 8px (--sp-3). Before: 0px left inset.

Worth recording as evidence for the method: the defect had been shipping in the product, and what surfaced it was a story that ported the flaw instead of quietly drawing a nicer example. A catalogue that corrects the canon on the way in cannot find anything.

A2 — The status strip now lives inside the 220px results column

.gsp-strip is a statement about the capability of the whole surface — its own CSS header says so, and says it therefore sits "nad patkou (mimo .gsp-body)". It does not: GlobalSearch.tsx:1400 renders it inside leftColumn, which since the two-column rework becomes the .sv-aside capped at minmax(0, 220px). Measured in the composition story it wraps onto two lines inside the results column and reads as a property of that list.

It is not only drift — it breaks R17 (measured 8 Sep 2026). .sv-fill > .sv-aside has overflow-y: auto, so the strip scrolls with the results. Measured in Chromium on components--command-palette--composition after padding the list until the column overflows: the column scrolls by 915px and the strip ends at -915px — completely out of sight. R17 (!2871) requires the opposite: the strip must stay visible once results arrive.

That settles the choice. „Correct the CSS header instead" is no longer an option: the header is right and the placement is wrong. The strip belongs above the footer, outside the scroll region — one row of the dialog's auto 1fr auto grid, next to .gsp-foot.

Found by the catalogue, not by reading: an AI review on !3124 flagged the story for drawing the strip inside .gsp-body, which is what the app does. Measuring the reviewer's hypothetical („with a longer list…") turned a documentation mismatch into a demonstrable defect.

A3 — Two sheets the catalog never loaded

packages/ui/.ladle/components.tsx mirrors the app's stylesheet order, but neither ds/split-view.css nor ds/global-search.css was in it. Any palette story therefore rendered as a bare ItemList: .gsp2-def with no grid (facts stacked label-over-value), the card with no inset, the columns not split. Both are now imported in the app's own order (split-view first — global-search.css overrides > .sv-aside). Worth noting because the gap was invisible until something used those classes: a sheet missing from the catalog fails silently, exactly like a missing icon name.

2. Findings in the canon record

D1 — The registry documented the wrong prefix

The note read "Prefixes: @ people, # companies, > commands, ? assistant". The code deliberately moved commands to : — palette-model.ts:345 explains that > needs right-Alt on a Czech keyboard, and that since R40 > is ordinary text with no token. An agent in another repo reading _index.json would have built a > affordance that does nothing. Corrected.

D2 — .gsp-sec-trunc was missing from classes

Emitted on main in two files (PaletteActionSection.tsx:66, ValuePane.tsx:84) — the single-line section label with an ellipsis, used wherever the label carries a record name. Added.

D3 — The "no stories possible" note was self-fulfilling

Removed and replaced with how the stories are built, so the next family in the same position (an app-level canon) does not repeat the conclusion.

3. Not a finding — and why it changed mid-review

.gsp2-ans / .gsp2-ans-body (~40 lines) had zero emitters on main when this pass started, which reads like dead CSS. It was not: the consumer, search/AnswerPane.tsx, was in flight on !3117 — the CSS had landed ahead of it. !3117 then merged (main d108262b9), so the assistant answer is now code and the palette's fourth mode (?) has a story like the other three. Only the done state is shown; streaming and the error state belong to the assistant's own family, not to the palette's shell.

Worth keeping as a method note: "no emitter" is not proof of dead CSS. Checking the open MRs took one command and prevented a confident, wrong finding — and then a stale one in the other direction.

4. What was measured

CheckExpected (source)Measured
dialog widthmin(800px, 94vw) (GlobalSearch.tsx)800px
query row height52px (.gsp-q)52px
body height460px (.gsp-body)460px
footer height44px (.gsp-foot)44px
results columnminmax(0, 220px), scrolls on its own220px, overflow-y: auto
R33 — fact glyph vs action glyphone vertical (20px column)18px / 18px
R33 — fact label vs action textone vertical48px / 48px
cursor drives the right columndetail follows the rowchanged on click ✓
console / page errors across 5 storiesnone0

5. Stories

packages/ui/stories/command-palette.stories.tsx — Default · Variants · States · With icon & kbd · Composition. Sizes is deliberately empty: the palette is one dialog role with a fixed body height, so there is no size ladder to show — the same reasoning Filter bar records.

Everything is a port, not an invention: the shell, the query row, the section labels, the record card, the action card, the prefix hint, the footer and the phone's second step each come from a named file and line. The mobile step and the desktop split are the same markup the app emits, so what the site shows is what ships.

6. Open for the AD

  1. ~~A1 — centre the empty-state actions, or give them the body's --sp-5 inset?~~ Answered 8 Sep 2026 (ACRM-161): centre. Fixed in .gsp-acts, measured, story baseline regenerated. Merged to main with !3124 (with the family's stories), verified in ds/global-search.css.
  2. A2 — the status strip. The question narrowed on 8 Sep 2026: measurement showed it scrolls out of sight with the results (915px), which breaks R17, so „correct the header instead" is off the table. What remains is when to move it above the footer, not whether. Still open.

A2 is a change to the product surface, not to the catalogue, so it stays unmade until the AD says so.

Source: docs/REVIEW-command-palette-family.md · GitLab