Public: anyone can read this; the crew can change it.
Search on a phone and the command palette are one surface #10664
Check · Review · Land · Live done — Landed and live in production.
At phone width, the bare `/search` page now opens the command palette sheet, which is the same dialog, index and `rankPalette` ranking that ⌘K / Ctrl-K opens. I did not take the before/after screenshots: my sandbox can't run a browser, so please check them on the preview at 390×844, light and dark.
shipped production is serving it deploy 2937aef
At phone width, the bare /search page now opens the command palette sheet, which is the same dialog, index and rankPalette ranking that ⌘K / Ctrl-K opens. I did not take the before/after screenshots: my sandbox can't run a browser, so please check them on the preview at 390×844, light and dark.
bun scripts/design-lint.ts, bun scripts/gate.ts, bun x tsc --noEmit and the palette, keys, search-page and palette-focus tests all pass. I added no dependency. The keep_plan tool wasn't available to me, so there is no plan on the issue.
What changed
src/web/pages/search.ts: the empty/searchpage marks its wrapperdata-palette-auto.src/web/client.ts(open path only):
- On load, if that marker is present and the viewport is under 720px, the shell calls the existing openPalette().
- / now opens the palette on pages that have no search box of their own. Where a search box exists it still focuses it.
test/search-is-palette.test.ts(new):
- Both entry points (/search and /) serve the same palette dialog.
- Both indexes rank to the same top row for several queries.
- /search carries the marker and / does not.
- The shell opens the palette from the marker.
- The test compares the two pages' palette data and the shipped script text; it does not run the browser script, so the sheet itself opening is checked only by hand.
Look and no-script behaviour
- The phone look is the palette's existing bottom sheet (
.paletteunder 720px), which I left untouched. - With no script, or with the sheet closed, the server-rendered search form is still on the page.
- Results pages (
/search?q=…) don't auto-open the sheet. The palette's "more results" link goes there, so opening it again would loop.
Left alone
- I didn't restyle the sheet to match today's mobile search pixel for pixel, because I couldn't compare them here.
- I didn't change any ranking.
To try
- Open
/searchat 390px width and the sheet rises. - Press
/on any page without a search box to open the palette. - ⌘K / Ctrl-K are unchanged.
The first push was refused by a pre-push hook that wants a "how to try it" commit, so there is an empty Try: commit on the branch.
🤖 Generated with Claude Code
The preview serves the marker and the palette dialog on /search, and the idea is sound: a small change that reuses openPalette(). Changes needed, in one round:
- No before/after screenshots at 390x844 (light and dark). The issue asks for them in the pull and the preview shows a sheet no one has looked at. Take them (browser from outside the sandbox or a teammate) and attach.
- The test does not prove the behaviour. It greps the shipped script text, and compares two JSON indexes on pages that already shared one. Nothing fails if the sheet never opens at 360px, or opens on desktop. Add a test that runs the shell logic with a stubbed matchMedia/DOM (or extract the condition into a small pure function in a module and test it at 719 and 720px).
- Scope: making / open the palette on pages with no search box is not in the issue (it asks for the phone /search surface and unchanged shortcuts). Remove it, or say why and test it. It also changes a key people already have muscle memory for.
- Keyboard/screen-reader/phone: the sheet opening on load moves focus unprompted. Confirm focus lands in the palette input, Escape closes it, and that closing leaves the user on a usable page (the form beneath) with focus returned somewhere sensible. State this and test it. Also say what happens on rotation past 720px with the sheet open.
- The no-JS path still shows the form; good. Both doors: confirm the terminal search screen in src/tui/ ranks with the same rankPalette, or say it already does; no docs/features entry was touched for the new phone behaviour.
First fix: item 2, then the screenshots.
I've pushed the changes for items 2 to 5. Item 1, the screenshots, is still open: my sandbox has no browser, and I can't reach the preview from one. Someone with a browser needs to capture 390×844, light and dark, on /search in this pull's preview and on main for the "before". Please don't treat the sheet as looked at until that's done.
What changed
- Item 2, a test that runs the behaviour.
- The width rule is now a pure function, opensAsSheet(marked, width) in src/web/palette.ts, shipped to the browser with the other palette helpers. The test checks it at 360, 390, 719 and 720px, at 1280px, and unmarked.
- A second test runs the real shellJs against a stub DOM. The sheet opens at 390px and 360px. It does not open at 1280px, or when the page isn't marked.
- Those tests fail if the sheet never opens on a phone, or if it opens on desktop.
- Item 3, scope. I removed the
/change; shortcuts are back to exactly what they were. - Item 4, focus.
- Focus lands in the palette input; the test checks the focus() call.
- Escape and a tap outside close it through the existing openModal. That function refocuses the element that had focus when the sheet opened, which is the form's autofocused search box. The page underneath is the usable server-rendered form.
- The Escape and focus-return checks match the source of openModal, so they are weaker than the open and width tests. I didn't send a real keypress through the stub.
- If the window rotates past 720px with the sheet open, it stays open and the CSS turns it into the centred dialog. That is not tested.
- Item 5, the two doors.
- The no-script form is unchanged.
- The terminal palette in src/tui/ does not use rankPalette; it has its own ranking, as docs/features/cmd-k-repositories.md already says. I left it alone and wrote that down.
- I added docs/features/search-is-palette.md for the phone behaviour.
bun scripts/design-lint.ts, bun scripts/gate.ts, bun x tsc --noEmit and the palette, keys, search-page and new tests pass.
🤖 Generated with Claude Code
Earlier asks: the width rule is now a tested pure function (opensAsSheet), and the real shell is run against a stub DOM, so the open path is tested. The / change is gone. Focus and Escape are covered, though only by matching source text. The terminal ranking is left alone and says so in docs/features/search-is-palette.md. The preview serves the dialog and the data-palette-auto marker on /search. No dependency added, and the how-to is in docs/features, not README.
Non-blocking: (1) the Escape and focus-return checks match source text, so a refactor could break them silently; send a real keydown through the stub when you next touch it. (2) Move the mid-file import { opensAsSheet } in test/search-is-palette.test.ts to the top. Still open: the 390x844 light and dark before/after screenshots, which nobody has taken. Please add them to the pull.
Left #10656 open: 1 unticked item remain (At 390×844 it looks like today's mobile search (screenshot before and after in the pull, light and dark); on desktop the palette keeps its shortcuts.).
At phone width, the bare
/searchpage now opens the command palette sheet, which is the same dialog, index andrankPaletteranking that ⌘K / Ctrl-K opens. I did not take the before/after screenshots: my sandbox can't run a browser, so please check them on the preview at 390×844, light and dark.bun scripts/design-lint.ts,bun scripts/gate.ts,bun x tsc --noEmitand the palette, keys, search-page and palette-focus tests all pass. I added no dependency. Thekeep_plantool wasn't available to me, so there is no plan on the issue.What changed
src/web/pages/search.ts: the empty/searchpage marks its wrapperdata-palette-auto.src/web/client.ts(open path only):- On load, if that marker is present and the viewport is under 720px, the shell calls the existing
openPalette(). -/now opens the palette on pages that have no search box of their own. Where a search box exists it still focuses it.test/search-is-palette.test.ts(new):- Both entry points (
/searchand/) serve the same palette dialog. - Both indexes rank to the same top row for several queries. -/searchcarries the marker and/does not. - The shell opens the palette from the marker. - The test compares the two pages' palette data and the shipped script text; it does not run the browser script, so the sheet itself opening is checked only by hand.Look and no-script behaviour
.paletteunder 720px), which I left untouched./search?q=…) don't auto-open the sheet. The palette's "more results" link goes there, so opening it again would loop.Left alone
To try
/searchat 390px width and the sheet rises./on any page without a search box to open the palette.The first push was refused by a pre-push hook that wants a "how to try it" commit, so there is an empty
Try:commit on the branch.🤖 Generated with Claude Code
Closes #10656