From 6fcd3bfb4e88f7ede58895457e6229960e2ab37c Mon Sep 17 00:00:00 2001 From: operator-main Date: Sun, 4 Oct 2026 03:54:16 +0000 Subject: [PATCH] DOM reference: search functionality\n\n- Quick search dialog structure and data-value encoding\n- chat:thread: gives ready-made thread lookup\n- Radix gotchas: trusted events only, never touch input via JS --- docs/DOM-SEARCH.md | 266 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/DOM-SEARCH.md diff --git a/docs/DOM-SEARCH.md b/docs/DOM-SEARCH.md new file mode 100644 index 0000000..da145d9 --- /dev/null +++ b/docs/DOM-SEARCH.md @@ -0,0 +1,266 @@ +# 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:` | Sidechat thread — **the thread UUID is embedded** | `chat:thread:2e2c90a1-4b17-4590-b8ba-fb17d8018d36` | +| `chat:message::` | 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 + +``` + +```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:"` 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.