267 lines
9.3 KiB
Markdown
267 lines
9.3 KiB
Markdown
# 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
|
||
|
||
```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` |
|
||
| `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:
|
||
|
||
```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).
|
||
2. **Never JS-touch the input.** `focus()`, `click()`, or the native value setter
|
||
on `command-palette-search-input` closes the dialog. The input auto-focuses on
|
||
open — just send trusted key events (`rawKeyDown` → `char` → `keyUp`).
|
||
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.
|