Files
box/docs/DOM-SEARCH.md
T
dom-inspector-4 ef453ccb26 Verify + extend DOM-SETTINGS/SEARCH/NOTIFICATIONS (2026-10-04, 3 nodes)
Session: sidechat/dom-settings
2026-10-04 04:31:11 +00:00

11 KiB
Raw Blame History

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. Re-verified end-to-end 2026-10-04 (open → type → results → empty state → Escape).

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

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

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

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

Search matches both threads and individual messages. A query like "heartbeat" returned 12 results on 2026-10-04 re-verify: 6 chat:thread:* + 6 chat:message:*, mixing both message id forms.

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.

Full result-item markup (verified 2026-10-04)

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

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

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