2026-10-04 03:54:16 +00:00
# DOM Reference: Search (`docs/DOM-SEARCH.md`)
2026-10-05 15:58:37 +00:00
> **Box is the main surface.** All operator work goes through Box (box.muse-dev.online). The web UI, `box` CLI, and agents share the same API endpoints. No UI-only powers.
2026-10-04 03:54:16 +00:00
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.
2026-10-04 04:31:11 +00:00
Re-verified end-to-end 2026-10-04 (open → type → results → empty state → Escape).
2026-10-04 03:54:16 +00:00
Related: `docs/DOM-CHAT-PANEL.md` (chat panel), `docs/DOM-MESSAGES.md` ,
`docs/DOM-PAGE-STRUCTURE.md` .
---
## 1. Search trigger — nav rail button
``` javascript
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` ):
``` javascript
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** — `querySelector`
returns `null` (`ABSENT` ), not merely hidden. Detection must handle absence, not
just `offsetParent` .
- No `PRESENT_HIDDEN` state was observed; it is either open or gone.
---
## 3. Search input
``` javascript
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 === input`
immediately 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
``` javascript
document . querySelector ( '[data-testid="hatch-quick-search-frame"] [role="listbox"]' )
```
- `DIV[role="listbox"]` , cmdk-managed (class contains `cmdk-list-sizer` ).
- Contains group headings + items.
### Result items
``` javascript
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, `|` -separated |
### `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` |
2026-10-04 04:31:11 +00:00
| `chat:message:assistant-msg-<uuid>:<n>` | Individual assistant message match inside a thread | `chat:message:assistant-msg-b2a4e27d-4970-8b1e-8863-a96ed0aa1401:20420` |
| `chat:message:<uuid>:<n>` | Individual message match (non-assistant id form) | `chat:message:08a44cdd-91a7-408e-9073-743e7e15d50f:20890` |
2026-10-04 03:54:16 +00:00
Search matches **both threads and individual messages ** . A query like `"heartbeat"`
2026-10-04 04:31:11 +00:00
returned 12 results on 2026-10-04 re-verify: 6 `chat:thread:*` + 6 `chat:message:*` ,
mixing both message id forms.
2026-10-04 03:54:16 +00:00
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.
2026-10-04 04:31:11 +00:00
### Full result-item markup (verified 2026-10-04)
``` html
< div data-slot = "command-item"
data-testid = "command-palette-item"
id = "radix-_r_1e9_" < ! -- random per item per render — do not match -- >
cmdk-item=""
role="option"
aria-disabled="false" aria-selected="true"
data-disabled="false" data-selected="true"
data-value="chat:thread:778fbbfa-3bf8-4163-bfe6-92c0f88657e8"
class="... h-12 items-center gap-2 p-1.5 ...">
< span class = "bg-fill-secondary flex size-9 ...rounded-full" > …svg icon…< / span >
< span class = "flex min-w-0" > …title + snippet + relative time…< / span >
< / div >
```
Notes:
- The **first result is auto-highlighted ** on render (`aria-selected="true"` /
`data-selected="true"` ); the input's `aria-activedescendant` points at it.
Enter would activate it (navigation not tested).
- `data-value` is the stable automation hook; `id` is random per render.
2026-10-04 03:54:16 +00:00
---
## 5. Groups
Initial (empty query) view:
``` javascript
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:
``` javascript
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:
``` html
< 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 >
```
``` javascript
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
1. **Open with a real mouse click ** , not JS:
```javascript
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)
` ``
JS ` .click()` on the trigger is unreliable (Radix wants trusted events).
2026-10-04 04:31:11 +00:00
2. **Type with CDP ` Input.insertText`** (verified 2026-10-04 — simplest reliable
path). The raw ` rawKeyDown` → ` char` → ` keyUp` sequence also works but is
unnecessary. **Never JS-touch the input** (` focus()`, ` click()`, native value
setter): synthetic interaction on the input **closes the dialog** (verified
twice). One exception observed once: ` input.select()` on the already-open
dialog did *not* close it — usable for clearing, but Escape-and-reopen
remains the safest clear.
2026-10-04 03:54:16 +00:00
3. **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.
4. **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.
5. **No scopes to set.** One global search; filter client-side on ` data-value`
prefix (` chat:thread:` vs ` chat:message:` vs ` chat:main`).
6. **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.
7. Dialog ` id` (` radix-_r_NN_`) and ` aria-activedescendant` values are generated
per mount — never match on them.