DOM reference: chat panel and sidechat structure\n\n- 367-line map of switcher, compose button, panel shell\n- 24 data-testid inventory with states and detection signals\n- Documents toggle behavior and false-positive traps
This commit is contained in:
@@ -0,0 +1,367 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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"]`
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3c. Related panel testids
|
||||||
|
|
||||||
|
| 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:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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...` | `""` |
|
||||||
|
|
||||||
|
### Recommended checks (in order of reliability)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 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
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 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");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Full data-testid Inventory (chat-related)
|
||||||
|
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user