VIBEBRATOR
Take the Vibe Check
← Butcher Block
Cut 01 · from Linear · 19 bones

Command bar under the knife

A keyboard-first command bar that opens with Cmd/Ctrl+K, searches pages, terms and actions as you type, groups the results, and puts focus back exactly where it found it when it closes.

XX-raySStealCmd K / Ctrl Kopen the bar
The plate · live
Sample app

Linear did not invent the command palette; Sublime Text and Alfred were there first. What Linear did was make it the front door. Every page, every action, every issue is one shortcut and a few letters away, and the bar feels instant because it does almost nothing on each keystroke.

Copying the look takes ten minutes. Copying the feel takes the nineteen bones on this page: the highlight that never points off the end of the list, the Escape that clears before it closes, the focus that goes back to where it came from, the empty state that repeats what you typed so you can see your own typo. Most re-implementations skip half of them and users can tell without knowing why.

Turn on the X-ray, press the keys the pins name, and watch which bone lights up. Untick anything you do not want. The ticket rewrites itself.

The ticket, as it will be pasted

Compiled from the bones above by a deterministic function. No model in the loop: the same selection produces the same bytes every time.

You are working inside this repository. Before you edit anything, read the files that own navigation, keyboard shortcuts and the design tokens, and say which ones you read.
Work test-first: write the tests named below, watch them fail, then implement until they pass.

# Build: Command bar
Pattern: the Linear command bar (Cmd+K)
Source: https://www.vibebrator.com/cuts/command-bar

## What it is
A keyboard-first command bar that opens with Cmd/Ctrl+K, searches pages, terms and actions as you type, groups the results, and puts focus back exactly where it found it when it closes.

## Where it goes
Placement: a global overlay, opened from a button in the header and from the keyboard shortcut anywhere on the page
Stack: the stack already in this codebase; do not add a framework or a UI library for this
Design tokens: the existing tokens; do not introduce new colours, radii or fonts

## Bones (17 of 19)
### State
- STATE-01 Open state lives in one place
  Spec: The bar has exactly one open/closed state. Every affordance that opens or closes it (shortcut, trigger button, Escape, backdrop) sets that state and nothing edits the DOM directly. The trigger button's aria-expanded mirrors it.
  Test: Open with the shortcut, close with a backdrop click, open with the button: the bar is never half-open and aria-expanded on the trigger matches at each step.
- STATE-02 Results are a pure function of the query
  Spec: The results list is derived from (query, index, highlighted index) on every change. Nothing appends to or removes from the rendered list; it is rebuilt from the query.
  Test: Type 'lex', delete to 'le', type 'x' again: the list is identical both times, including order.
- STATE-03 Highlight clamps to real rows
  Spec: The highlighted row index resets to 0 whenever the result set changes and never exceeds the last row.
  Test: Highlight row 4, then narrow the query to 2 results: the highlight is on row 1, not off the end.
### Edge cases
- EDGE-01 Empty query shows a default set
  Spec: With an empty query the bar shows the Actions and Pages groups in a fixed order. It is never an empty list.
  Test: Open the bar without typing: at least 5 rows are visible under labelled group headings.
- EDGE-02 Case and whitespace are normalised
  Spec: Matching lowercases both sides and trims and collapses whitespace, so '  Vibe  CHECK ' matches the same rows as 'vibe check'.
  Test: Type '  LEXicon ' and then 'lexicon': same rows in the same order.
- EDGE-03 No results echoes the query
  Spec: When nothing matches, the list is replaced by one line that repeats the query verbatim: No results for “<query>”. The query is inserted as text, never as markup.
  Test: Type zzzz<b>: the empty state shows those literal characters and no bold element exists in the DOM.
### Motion
- MOTION-01 Open fades and rises in 120ms, close is instant
  Spec: Opening animates opacity 0 to 1 and translateY 8px to 0 over 120ms ease-out. Closing does not animate. When prefers-reduced-motion is set, opening is instant too.
  Test: Emulate prefers-reduced-motion: reduce and open the bar: no transform and no opacity transition runs.
### Keyboard
- KEYS-01 Cmd/Ctrl+K opens from anywhere and toggles
  Spec: Cmd+K on macOS and Ctrl+K elsewhere open the bar from any focus position except inside another text field, where K must type. Pressing the shortcut while open closes the bar.
  Test: Focus the page body and press the shortcut: the bar opens with its input focused. Press it again: the bar closes.
- KEYS-02 Arrows move, Enter chooses, ends wrap
  Spec: ArrowDown and ArrowUp move the highlight and wrap around at either end. Enter activates the highlighted row. Home and End jump to the first and last row.
  Test: On the last row press ArrowDown: the first row is highlighted. Press Enter: its action runs.
- KEYS-03 Escape clears, then closes, then returns focus
  Spec: Escape with a non-empty query clears the query. Escape with an empty query closes the bar and returns focus to the element that opened it (the trigger button or the previously focused element).
  Test: Open from the button, type 'a', press Escape twice: the bar is closed and focus is on the button.
- KEYS-04 Tab stays inside the bar
  Spec: While open, Tab and Shift+Tab cycle within the bar (input and rows). Focus never lands on the page behind it.
  Test: Open the bar and press Tab 20 times: document.activeElement is inside the bar every time.
### Accessibility
- A11Y-01 Dialog and combobox semantics
  Spec: The bar is role=dialog with aria-modal=true and an aria-label. The input is role=combobox with aria-expanded, aria-controls pointing at the list, and aria-activedescendant tracking the highlighted row. Rows are role=option inside role=listbox.
  Test: With the bar open, axe-core reports no violations and aria-activedescendant equals the highlighted row's id.
- A11Y-02 Result count is announced
  Spec: A visually hidden aria-live=polite region announces '<n> results' after each query change, debounced so typing does not spam the reader.
  Test: Type 'le': the live region text carries the same number as the visible rows.
### Copy
- COPY-01 Placeholder names the scope
  Spec: The input placeholder names what is searchable: 'Search pages, terms and actions…'. Group headings are plain nouns: Actions, Pages, Recent.
  Test: Read the placeholder: it names at least two searchable things.
- COPY-02 Shortcut hint matches the platform
  Spec: The trigger button shows ⌘K on macOS and Ctrl K elsewhere, detected from the platform once at load and rendered inside a kbd element.
  Test: With a Mac user agent the hint reads ⌘K; on Windows it reads Ctrl K.
### Performance
- PERF-01 The index is built once
  Spec: The search index (pages, terms, actions) is built once at load into a flat array of {label, hay, group, action}. Querying reads that array and never rebuilds it or reads the DOM.
  Test: Run 100 queries: the index array is the same object before and after.
- PERF-02 Input is debounced 60ms, last keystroke wins
  Spec: Keystrokes update the query immediately but the list re-renders at most once per 60ms, inside a requestAnimationFrame. The final render always reflects the last keystroke.
  Test: Type 8 characters within 40ms: the list renders once, with the full query, never with a stale prefix.

## Rules
- Implement every bone listed above and nothing that is not listed.
- Each Test line is an acceptance test. Write it as a real, runnable test before the code it proves.
- Put the bone ID in a comment next to the code that implements it.
- If a bone conflicts with something that already exists, stop and ask. Do not pick silently.

When you finish, reply with a table: bone ID, the file:line that implements it, the test that proves it. Then run the full test suite and paste its summary line.
17 bones