# DOM Reference: Message Actions & Thread Interactions Inspected live via CDP on the opm browser (warp-opm netns, CDP 9440), 2026-10-04. Covers main chat (`https://muse.ai/`) and sidechat threads (`/thread/`). Read-mostly probes; the only state-changing probes were opening/closing the "More options" menu and one Reply-draft open/cancel cycle (composer verified clean after). Related: `DOM-CHAT-PANEL.md` (panel/sidechat create), `DOM-MESSAGES.md`, `DOM-PAGE-STRUCTURE.md`. --- ## 1. Message wrapper Messages carry **no `data-testid`**. The structural hook is the Tailwind group class: ```html
``` Selector: ```javascript document.querySelectorAll('.group\\/msg') // note: escaped slash in class name ``` Parent chain (own message, bottom-up): ``` DIV.group/msg (ms-auto, items-end → right-aligned = own message) └── DIV.touch:select-none.flex.w-full.flex-col.items-end └── DIV.relative.flex.w-full.flex-col.items-end ├── DIV. (absolute inset-y-0 end-full …) └── DIV.relative.max-w-full └── DIV.relative.max-w-full └── DIV.hatch-chat-groupable-bubble ├── SPAN.sr-only "You:" (author label, a11y only) └── P.text-[16px].leading-[22px].whitespace-pre-wrap (body) ``` Message list container: ```javascript document.querySelector('div[role="log"][aria-label="Chat messages"]') ``` ### Bubble variants | Variant | Bubble classes | Alignment | |---|---|---| | Own (user) message | `hatch-chat-groupable-bubble … bg-chat-user-bubble text-chat-user-text` | `group/msg` has `ms-auto items-end` (right) | | Agent message | `hatch-chat-groupable-bubble … bg-[var(--hatch-agent-bubble-bg)]` | left-aligned | | Agent body text | `DIV.!text-[15px].text-text-primary` (prose container) | — | --- ## 2. Action rail (hover-revealed) Each `div.group/msg` contains exactly one action-rail div: ```javascript // selector: the rail inside a message msg.querySelector(':scope > div > div > div[class*="end-full"]') ``` Full classes (desktop): ``` touch:static touch:mb-0 touch:h-auto touch:scale-100 touch:opacity-100 absolute inset-y-0 end-full my-auto flex h-fit shrink-0 scale-95 opacity-0 transition-[opacity,scale] group-hover/msg:scale-100 group-hover/msg:opacity-100 ``` Key facts: - Positioned `absolute inset-y-0 end-full` — vertically centered on the message's outer (end) side, `my-auto`. - Hidden with `opacity-0`, revealed purely by CSS `group-hover/msg` — **no JS hover needed for automation**; query the button directly. - `touch:` variants force it visible on touch devices. - Width driven by `--hatch-message-compact-action-rail-width: 2.75rem`. - ⚠️ `getComputedStyle().display/visibility` report the button as visible even when the rail is `opacity-0` — check `opacity` for true on-screen visibility. ### The single action button: "More options" Every message (own AND agent) exposes exactly **one** action button — a Radix dropdown trigger: ```html ``` ```javascript msg.querySelector('button[aria-label="More options"]') ``` - 28×28 px, `rounded-full`, icon-only (SVG, no text content). - `id` follows the Radix pattern `radix-_r__` — **unstable across renders, do not match on id**; match on `aria-label="More options"`. - This button is the gateway to ALL message actions (reply, react, copy, delete). There are no separate always-visible reply/react/copy buttons. --- ## 3. "More options" menu (Radix dropdown) ### Opening it (headless quirk) A plain `.click()` did **not** open the menu. The full pointer sequence was required: ```javascript const b = msg.querySelector('button[aria-label="More options"]'); b.scrollIntoView({block: 'center'}); const r = b.getBoundingClientRect(); const opts = {bubbles: true, cancelable: true, clientX: r.x + r.width/2, clientY: r.y + r.height/2, button: 0}; b.dispatchEvent(new PointerEvent('pointerdown', opts)); b.dispatchEvent(new MouseEvent('mousedown', opts)); b.dispatchEvent(new PointerEvent('pointerup', opts)); b.dispatchEvent(new MouseEvent('mouseup', opts)); b.dispatchEvent(new MouseEvent('click', opts)); ``` - When open, the trigger gets `aria-expanded="true"` and `data-state="open"`. - Opening was flaky in headless (~1 in 4 attempts needed a retry): loop up to 4×, checking for `[role="menu"][data-state="open"]`, Escape between attempts. - Close with: `document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape', keyCode: 27, bubbles: true}))`. ### Menu container ```html