Files
box/docs/DOM-CHAT-PANEL.md
T

16 KiB

muse.ai Chat Panel & Sidechat DOM Reference

Captured live via CDP on opm browser (warp-opm netns, CDP 9440) on 2026-10-04. Page: https://muse.ai/, title "Chat — operator-main". This is a development reference for reliable automation of the chat navigation panel and sidechat creation. Prefer these selectors over text matching.


1. Chat Panel Switcher

Selector: [data-testid="hatch-chat-switcher-trigger"]

The button that toggles the chat navigation panel open/closed. Always present in the DOM, top-left of the viewport.

<button type="button"
        data-testid="hatch-chat-switcher-trigger"
        data-slot="popover-anchor"
        data-pel-click="chat_threads_open_click"
        class="bg-fill-blur-thick shadow-blur-elevation-01 ... rounded-full ...">
  <span class="relative flex shrink-0 items-center">
    <span data-testid="hatch-chat-switcher-icon-knockout" class="flex size-5 shrink-0 items-center justify-center">
      <span data-testid="hatch-chat-switcher-navigation-icon" aria-hidden="true" style="width: 20px; height: 20px;">
        <!-- hamburger SVG: three horizontal lines -->
      </span>
    </span>
  </span>
  <span class="flex min-w-0 flex-col" title="Chats">
    <span class="block min-w-0 truncate">Chats</span>
  </span>
  <span class="sr-only">Open chat and side chats</span>
</button>

Key facts:

Property Value
Tag BUTTON
Always in DOM Yes (both panel states)
Visible Yes (offsetParent !== null)
Position Top-left: x:84, y:12, w:82, h:36
aria-expanded Not set (null) — do NOT use for state detection
Visible text Chats
Screen-reader text Open chat and side chats
Icon Hamburger menu (3 horizontal lines), not a chat bubble

Parent chain (bottom-up, 4 levels):

1. DIV  [data-testid="hatch-chat-leading-controls"]
         class="pointer-events-auto absolute start-3 top-0 flex h-[60px] items-center gap-2"
2. DIV  (no testid)  class="pointer-events-none absolute inset-x-0 top-0 z-10 h-44"
3. DIV  (no testid)  class="absolute inset-0 overflow-hidden"
4. DIV  (no testid)  class="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden"

Behavior: The switcher toggles. Clicking when the panel is closed opens it; clicking when open closes it. Always check panel state before clicking — never blind-click.

Child testids (for reference):

  • [data-testid="hatch-chat-switcher-icon-knockout"] — icon wrapper span
  • [data-testid="hatch-chat-switcher-navigation-icon"] — the hamburger SVG span

2. Sidechat Create Button ("New side chat")

Selector: [data-testid="hatch-chat-compose"]

The + button that creates a new sidechat. Only exists in the DOM when the panel is OPEN. This makes it the most reliable "panel is open and ready" signal.

<button aria-label="New side chat"
        title="New side chat"
        data-slot="button"
        data-testid="hatch-chat-compose"
        class="inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full ... size-6 ... text-text-secondary hover:enabled:bg-fill-secondary me-1">
  <span class="inline-flex shrink-0 items-center justify-center m-0.5" style="width: 20px; height: 20px;">
    <svg viewBox="0 0 24 24" fill="currentColor" width="15" height="15">
      <path d="M12 3.13C12.48 3.13 12.88 3.52 12.88 4V11.12H20C20.48 11.12 20.87 11.51 20.88 12C20.88 12.48 20.48 12.87 20 12.87H12.88V20C12.88 20.48 12.48 20.88 12 20.88C11.52 20.87 11.13 20.48 11.13 20V12.87H4C3.52 12.87 3.13 12.48 3.13 12C3.13 11.51 3.52 11.12 4 11.12H11.13V4C11.13 3.52 11.52 3.13 12 3.13Z"></path>
    </svg>
  </span>
</button>

Key facts:

Property Value
Tag BUTTON
In DOM when panel closed No — completely absent
In DOM when panel open Yes
Text content None (icon-only)
aria-label New side chat
title New side chat
Icon Plus sign (+), single SVG path

Parent chain (bottom-up, 8 levels):

1. SPAN  (no testid)  class="contents"                        ← direct wrapper
2. DIV   (no testid)  class="group/nav-section flex w-full items-center"
                       children: [H3 "Side chats", SPAN(contents) > BUTTON#compose]
3. SECTION (no testid) class="flex flex-col"                    ← "Side chats" section
                       children: [DIV(header: H3 + compose), DIV(chat list)]
4. DIV   (no testid)  class="flex flex-col relative grow-1"
                       children: [DIV[data-testid="hatch-thread-row"] (Main chat),
                                  SECTION (Side chats),
                                  DIV[data-testid="active-side-chat-load-more-sentinel"]]
5. DIV   (no testid)  class="flex flex-col overflow-hidden relative z-0 overflow-y-auto overscroll-y-contain min-h-0 min-w-0 flex-1 px-3 pb-3"
6. DIV   (no testid)  class="flex min-h-0 flex-1 flex-col"
7. DIV   (no testid)  class="text-text-primary flex h-full min-h-0 w-full flex-col overflow-hidden"
8. DIV   (no testid)  class="min-h-0 min-w-0 flex-1"

Location in UI: Inside the "Side chats" section header row, to the right of the H3 "Side chats" collapsible heading.

What clicking does: Creates a new sidechat. Browser navigates to https://muse.ai/thread/new (placeholder URL; the real thread UUID appears after the first message is sent or navigation settles).


3. Chat Navigation Panel Structure

3a. Panel shell (always in DOM)

Selector: [data-testid="hatch-side-chats-panel-shell"]

<div data-testid="hatch-side-chats-panel-shell"
     class="relative h-full overflow-hidden"
     style="width: 240px;">
  <!-- EMPTY when panel closed; full nav tree when open -->
</div>
State innerHTML innerText
Closed "" (empty string) ""
Open Full <nav> tree (see below) Main chat\njust now\nSide chats\n<chat titles>...

Panel width: Fixed 240px.

3b. Open panel tree

When open, the shell contains:

DIV[data-testid="hatch-side-chats-panel-shell"]  (width: 240px)
└── DIV[data-hatch-toast-pane="side-chats"]
    └── DIV[data-testid="hatch-side-chats-panel-sliding-surface"]  (absolute inset-y-0 start-0, width: 240px)
        └── NAV[aria-label="Side chats"]  (data-pel-impression="chat_threads_overflow_impression", width: 240px)
            └── DIV[data-testid="hatch-side-chats-panel-content"]  (flex h-full w-full flex-col)
                ├── DIV[data-testid="hatch-chat-search-toolbar"]  (height: 60px)
                │   ├── DIV[data-testid="hatch-chat-search-field"]
                │   │   └── INPUT[aria-label="Search"][placeholder="Search"]
                │   └── BUTTON[data-testid="hatch-side-chats-options"]
                │       [aria-label="Side chat options"]  (three-dots menu, dropdown trigger)
                └── DIV (scrollable: min-h-0 min-w-0 flex-1)
                    └── DIV (thread list container)
                        ├── DIV[data-testid="hatch-thread-row"][role="button"]  ← Main chat row
                        ├── SECTION (flex flex-col)  ← Side chats section
                        │   ├── DIV.group/nav-section (header)
                        │   │   ├── H3 > BUTTON[aria-expanded="true"]  "Side chats" (collapsible)
                        │   │   └── SPAN.contents > BUTTON[data-testid="hatch-chat-compose"]
                        │   └── DIV (chat list)
                        │       └── DIV.overflow-hidden > DIV.flex.flex-col.gap-px > DIV.flex.flex-col.gap-px
                        │           └── DIV[data-testid="hatch-thread-row"][role="button"]  ← per sidechat
                        └── DIV[data-testid="active-side-chat-load-more-sentinel"]  ← infinite scroll
Testid Element Purpose
hatch-side-chats-panel-shell DIV Outer shell; always in DOM; empty when closed
hatch-side-chats-panel-sliding-surface DIV Sliding animation surface (open state only)
hatch-side-chats-panel-content DIV Panel content wrapper
hatch-chat-search-toolbar DIV Top toolbar (search + options)
hatch-chat-search-field DIV Search input container
hatch-side-chats-options BUTTON "Side chat options" three-dots dropdown
hatch-thread-row DIV Each chat row (Main chat + every sidechat)
hatch-chat-compose BUTTON "New side chat" + button
active-side-chat-load-more-sentinel DIV Infinite-scroll sentinel at list end
hatch-side-chats-open-drag-handle DIV Resize drag handle (separate from shell)
hatch-chat-nav-fade DIV Fade effect element

3d. Thread row structure (each chat / sidechat)

Every row — Main chat and each sidechat — uses the same structure:

<div data-testid="hatch-thread-row"
     role="button"
     tabindex="0"
     draggable="false"
     data-state="closed"
     data-slot="context-menu-trigger"
     data-pel-click="chat_thread_options_open"
     aria-current="page"                <!-- ONLY on the currently active chat -->
     class="group/nav-row relative flex h-9 w-full shrink-0 items-center gap-0.5 rounded-12 px-2 text-start cursor-pointer ...">
  <div class="min-w-0 flex-1 px-1">
    <p class="text-text-primary flex items-center gap-1 truncate text-subheadline">
      <span class="min-w-0 flex-1 truncate" title="Check the board">Check the board</span>
    </p>
  </div>
  <span class="text-caption-1 text-text-secondary shrink-0 whitespace-nowrap ...">just now</span>
  <!-- hover-only action buttons -->
</div>

Key facts:

Property Value
Selector (all rows) [data-testid="hatch-thread-row"]
Tag DIV (not an anchor)
role button
Navigation React onClick — no <a href="/thread/..."> links exist in the panel
Active chat marker aria-current="page" on the active row only
Title Inner span[title] — the chat's display name
Timestamp Trailing span: just now, 1m, 4m, etc.
Main chat row First hatch-thread-row, text Main chat

Distinguishing Main chat from sidechats: Same data-testid. Match on text content (Main chat vs sidechat title) or check aria-current="page".

3e. "Side chats" section header

<div class="group/nav-section flex w-full items-center">
  <h3 class="flex min-w-0 flex-1">
    <button type="button" aria-expanded="true" aria-controls="_r_19e_"
            class="flex h-9 min-w-0 flex-1 cursor-pointer items-center gap-1 rounded-full px-2.5 text-start ...">
      <span class="text-subheadline-medium text-text-secondary truncate">Side chats</span>
      <!-- chevron collapse icon -->
    </button>
  </h3>
  <span class="contents">
    <button data-testid="hatch-chat-compose" ...>...</button>
  </span>
</div>

The H3 > BUTTON toggles section collapse (aria-expanded="true"/"false"). The compose + button sits beside it.


4. Panel Open vs Closed — Detection Guide

Reliable signals

Signal Panel OPEN Panel CLOSED
[data-testid="hatch-chat-compose"] exists ✅ Yes ❌ No
[data-testid="hatch-thread-row"] count ≥ 1 0
Panel shell innerHTML Full nav tree "" (empty)
Panel shell innerText Main chat\njust now\nSide chats\n... ""
// BEST: panel is open and ready for sidechat creation
const panelOpen = !!document.querySelector('[data-testid="hatch-chat-compose"]');

// ALTERNATIVE: panel shell has content
const shell = document.querySelector('[data-testid="hatch-side-chats-panel-shell"]');
const panelOpenAlt = shell && shell.innerHTML !== "";

// Row count (useful for "how many chats")
const rowCount = document.querySelectorAll('[data-testid="hatch-thread-row"]').length;

Signals that DO NOT work

Signal Why it fails
document.body.innerText.includes('Side chats') False positive. Body text contains "Side chats" in both states — it matches the switcher's sr-only text "Open chat and side chats" and chat message content.
document.body.innerText.includes('Main chat') Same problem — matches message content.
Switcher aria-expanded Never set (null in both states).
a[href*="/thread/"] links Zero exist. Panel rows are DIV[role=button], not anchors.

Safe toggle sequence

// 1. Detect actual panel state (not body text)
const isOpen = !!document.querySelector('[data-testid="hatch-chat-compose"]');

// 2. Click switcher ONLY if closed
if (!isOpen) {
  document.querySelector('[data-testid="hatch-chat-switcher-trigger"]').click();
}

// 3. Wait, then re-verify
await new Promise(r => setTimeout(r, 2000));
const nowOpen = !!document.querySelector('[data-testid="hatch-chat-compose"]');
if (!nowOpen) throw new Error("Panel did not open");

Captured on https://muse.ai/ main chat view. visible = computed style check.

testid Tag aria-label Notes
hatch-dock-rail DIV — Left dock rail
hatch-nav-chat A Chat Dock nav: Chat
hatch-nav-search BUTTON Search Dock nav: Search
hatch-nav-system-item-* DIV Feed/Ideas/Goals/Library Dock system items
hatch-dock-more BUTTON Settings Dock settings
hatch-chat-leading-controls DIV — Top-left container for switcher
hatch-chat-switcher-trigger BUTTON — Panel toggle (text: "Chats")
hatch-chat-switcher-icon-knockout SPAN — Icon wrapper
hatch-chat-switcher-navigation-icon SPAN — Hamburger SVG
hatch-side-chats-open-drag-handle DIV — Panel resize handle
hatch-side-chats-panel-shell DIV — Panel shell (always in DOM)
hatch-side-chats-panel-sliding-surface DIV — Open-state sliding surface
hatch-side-chats-panel-content DIV — Panel content wrapper
hatch-chat-search-toolbar DIV — Search toolbar (60px)
hatch-chat-search-field DIV — Search input container
hatch-side-chats-options BUTTON Side chat options Three-dots dropdown
hatch-chat-nav-fade DIV — Nav fade effect
hatch-thread-row DIV — Each chat row (Main + sidechats)
hatch-chat-compose BUTTON New side chat Create sidechat + (open only)
active-side-chat-load-more-sentinel DIV — Infinite scroll sentinel
hatch-invite-friends-button BUTTON Invite Invite button
hatch-composer-placeholder-overlay SPAN — Message input placeholder
hatch-status-panel-sliding-surface DIV — Agent status panel
hatch-browser-task-banner-host DIV — Task banner (hidden)

6. Automation Notes

  1. Ctrl+J is unreliable as a generic panel toggle. It navigates toward main chat but does not guarantee the panel ends up open. Prefer the detect → click-switcher → verify sequence in §4.

  2. The + button has no text. Never search for "+" text — the button contains only an SVG. Use [data-testid="hatch-chat-compose"] or aria-label="New side chat".

  3. After creating a sidechat, the browser navigates to https://muse.ai/thread/new. This stripped state has no switcher/panel — navigate back to https://muse.ai/ (or click Main chat) before the next panel operation.

  4. Sidechat rows are not links. To open a specific sidechat, click its DIV[data-testid="hatch-thread-row"] (match by inner span[title] text). There is no URL to navigate to directly until the thread UUID is known.

  5. Thread UUID capture: After sending the first message in a new sidechat, window.location.href transitions from /thread/new to /thread/<uuid>. Poll for this to capture the durable thread URL.

  6. Panel width is fixed at 240px — useful for visual grounding / screenshots.

  7. The "Side chats" H3 is collapsible (aria-expanded). If rows are missing while the panel is open, check whether the section is collapsed.