9.3 KiB
DOM Reference: Search (docs/DOM-SEARCH.md)
Muse.ai global search (quick-search / command palette). Inspected live on bl via CDP against the opm browser (warp-opm netns, CDP 127.0.0.1:9440) on 2026-10-04. All selectors verified against the live DOM.
Related: docs/DOM-CHAT-PANEL.md (chat panel), docs/DOM-MESSAGES.md,
docs/DOM-PAGE-STRUCTURE.md.
1. Search trigger — nav rail button
document.querySelector('[data-testid="hatch-nav-search"]')
| Property | Value |
|---|---|
| Tag | BUTTON |
data-testid |
hatch-nav-search |
aria-label |
"Search" (stable — unlike hatch-nav-chat, no notification count) |
| Inner text | none (icon-only, SVG magnifier) |
| Geometry | x:0, y:136, 72×44 (left nav rail, second button) |
| Visibility | always present and visible on chat pages |
Parent chain (4 levels):
BUTTON[data-testid="hatch-nav-search"].group.relative.flex
└── DIV.hatch-dock-reveal-item
└── DIV.my-auto.flex.shrink-0
└── DIV.scrollbar-hide.flex.min-h-0 (nav rail container)
Nav rail button order (siblings, via closest("div.my-auto")):
hatch-nav-chat
hatch-nav-search <-- search
hatch-nav-system-item-66656564 ("feed", hex)
hatch-nav-system-item-6964656173 ("ideas", hex)
hatch-nav-system-item-676f616c73 ("goals", hex)
hatch-nav-system-item-6c696272617279 ("library", hex)
Opening the dialog
| Method | Result |
|---|---|
Real mouse click on the button (CDP Input.dispatchMouseEvent press+release at button center) |
Reliable — opens every time |
Ctrl+K via CDP Input.dispatchKeyEvent |
Opens (verified multiple times; one flaky miss observed — prefer mouse click) |
JS button.click() |
Unreliable — Radix trigger appears to require trusted events; often silently does nothing |
2. Search dialog — quick-search frame
Opens as a Radix popover portal (direct child of a plain DIV under BODY):
document.querySelector('[data-testid="hatch-quick-search-frame"]')
| Property | Value |
|---|---|
| Tag | DIV |
role |
dialog |
data-testid |
hatch-quick-search-frame |
data-slot |
popover-content |
data-state |
"open" when visible |
data-side / data-align |
"bottom" / "center" |
id |
radix-_r_NN_ (unstable — generated per mount, do not match) |
tabindex |
-1 |
Parent chain:
DIV#radix-_r_44_[data-testid="hatch-quick-search-frame"][role="dialog"]
└── DIV (Radix portal wrapper)
└── BODY
└── HTML
Lifecycle:
- On close (Escape): the dialog is fully removed from the DOM —
querySelectorreturnsnull(ABSENT), not merely hidden. Detection must handle absence, not justoffsetParent. - No
PRESENT_HIDDENstate was observed; it is either open or gone.
3. Search input
document.querySelector('[data-testid="command-palette-search-input"]')
| Property | Value |
|---|---|
| Tag | INPUT type="text" |
data-testid |
command-palette-search-input |
data-slot |
command-input |
cmdk-input |
"" (boolean attribute — this is a cmdk command palette) |
role |
combobox |
aria-label |
"Search Muse" |
placeholder |
"Search" |
aria-autocomplete |
"list" |
aria-expanded |
"true" while open |
aria-controls |
radix-_r_NN_ (unstable) |
aria-activedescendant |
set to the highlighted item's id on ArrowDown |
autocomplete / autocorrect / spellcheck |
off / off / false |
Behavior:
- Auto-focuses when the dialog opens (verified:
document.activeElement === inputimmediately after open). - Do not touch the input via JS (
focus(),click(), native value setter): synthetic interaction on the input closes the dialog (verified twice). Type with trusted CDP key events instead (rawKeyDown → char → keyUp per char).
4. Results structure
Listbox
document.querySelector('[data-testid="hatch-quick-search-frame"] [role="listbox"]')
DIV[role="listbox"], cmdk-managed (class containscmdk-list-sizer).- Contains group headings + items.
Result items
document.querySelectorAll('[data-testid="hatch-quick-search-frame"] [data-testid="command-palette-item"]')
| Property | Value |
|---|---|
| Tag | DIV |
data-testid |
command-palette-item |
data-value |
encodes the target (see below) — the automation gold |
| Inner | SPAN.bg-fill-secondary.flex (icon, contains SVG) + SPAN.flex.min-w-0 (text) |
| Text | thread title + snippet + relative time, ` |
data-value formats (the important part)
| Prefix | Meaning | Example |
|---|---|---|
chat:main |
Main chat | chat:main |
chat:thread:<uuid> |
Sidechat thread — the thread UUID is embedded | chat:thread:2e2c90a1-4b17-4590-b8ba-fb17d8018d36 |
chat:message:<msg-id>:<n> |
Individual message match inside a thread | chat:message:assistant-msg-b2a4e27d-4970-8b1e-8863-a96ed0aa1401:20420 |
Search matches both threads and individual messages. A query like "heartbeat"
returned 12 results mixing chat:thread:* and chat:message:* values.
This is a ready-made thread-lookup mechanism: search for a sidechat name, read
data-value, extract the UUID — no title-matching or URL polling needed.
5. Groups
Initial (empty query) view:
document.querySelectorAll('[data-testid="hatch-quick-search-frame"] [cmdk-group-heading]')
// => ["Recents"]
- One group heading:
"Recents"(6 items observed). - After typing a query, group headings disappear (
headings: []) — filtered results render flat.
6. Filters / scopes
None. The dialog has no scope tabs, filter chips, or role="tab" elements:
document.querySelectorAll('[data-testid="hatch-quick-search-frame"] [role="tab"], [data-testid*="tab"], [data-testid*="scope"], [data-testid*="filter"]')
// => []
Search is global over chats/messages with a single Recents group.
7. Empty / no-results state
For a query matching nothing (e.g. "zzzznonexistentqueryzzzz"):
- Dialog stays open, item count drops to
0. - Empty marker:
<div data-slot="command-empty"
class="text-footnote text-text-secondary flex h-12 items-center justify-center p-1.5 text-center"
cmdk-empty=""
role="presentation">No results found</div>
document.querySelector('[data-testid="hatch-quick-search-frame"] [cmdk-empty]')
// innerText === "No results found"
Detection: querySelector('[cmdk-empty]') non-null, or item count === 0.
8. Keyboard shortcuts
| Keys | Action | Verified |
|---|---|---|
Ctrl+K |
Open search dialog | Yes (CDP key events; one flaky miss — mouse click preferred) |
Cmd+K |
(likely macOS equivalent) | Not isolated — dialog was already open in test |
Escape |
Close dialog (removes it from DOM) | Yes |
ArrowDown / ArrowUp |
Move highlight; sets aria-activedescendant on input |
Yes (aria-activedescendant="radix-_r_19o_") |
Enter |
Activate highlighted result (navigate) | Expected combobox behavior — not tested (would navigate the browser away) |
/ |
— | No effect on dialog state |
9. Testid inventory (search-related)
data-testid |
Tag | Purpose |
|---|---|---|
hatch-nav-search |
BUTTON |
Nav rail trigger (aria-label="Search") |
hatch-quick-search-frame |
DIV |
Dialog root (role="dialog", Radix popover) |
command-palette-search-input |
INPUT |
Search box (role="combobox", cmdk) |
command-palette-item |
DIV |
Result row (data-value encodes target) |
hatch-chat-search-toolbar |
DIV |
Seen once alongside an open dialog (context unclear — absent on main chat page; possibly panel inline search in some states) |
hatch-chat-search-field |
DIV |
Same as above — observed but not mapped; treat as contextual |
10. Automation notes
- Open with a real mouse click, not JS:
JS
const b = document.querySelector('[data-testid="hatch-nav-search"]'); const r = b.getBoundingClientRect(); // CDP: Input.dispatchMouseEvent mousePressed + mouseReleased at (r.x + r.width/2, r.y + r.height/2).click()on the trigger is unreliable (Radix wants trusted events). - Never JS-touch the input.
focus(),click(), or the native value setter oncommand-palette-search-inputcloses the dialog. The input auto-focuses on open — just send trusted key events (rawKeyDown→char→keyUp). - Closed === absent. After Escape the dialog node is gone; check
querySelector(...) === null, with optional chaining (?.offsetParent) — without?.a missing node throws and a truthy error string can false-positive an "open" check. - Thread UUIDs for free.
data-value="chat:thread:<uuid>"on result items is the most reliable thread-identity source found so far — better than title-matching or post-send URL polling. - No scopes to set. One global search; filter client-side on
data-valueprefix (chat:thread:vschat:message:vschat:main). - Ctrl+A via synthetic CDP events did not select input text in one test (subsequent typing appended). For clearing, prefer Escape-and-reopen or repeated Backspace over select-all.
- Dialog
id(radix-_r_NN_) andaria-activedescendantvalues are generated per mount — never match on them.