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.
|