docs/DOM-CHAT-PANEL.md — re-verified 2026-10-04 on opm/muse/pip; unread indicator, stripped state, row differences
Session: sidechat/dom-panel
This commit is contained in:
+241
-146
@@ -1,18 +1,20 @@
|
||||
# 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".
|
||||
> Originally captured live via CDP on `opm` browser (warp-opm netns, CDP 9440).
|
||||
> **Re-verified 2026-10-04** via CDP `Runtime.evaluate` on three nodes:
|
||||
> `opm` (warp-opm, CDP 9440), `muse` (warp-muse, CDP 9410), `pip` (warp-pip, CDP 9420).
|
||||
> Page: `https://muse.ai/`. Sections marked ✅ verified 2026-10-04,
|
||||
> 🔧 corrected, 🆕 new since original capture.
|
||||
> 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
|
||||
## 1. Chat Panel Switcher ✅ (with corrections)
|
||||
|
||||
**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.
|
||||
The button that toggles the chat navigation panel open/closed.
|
||||
|
||||
```html
|
||||
<button type="button"
|
||||
@@ -23,31 +25,35 @@ top-left of the viewport.
|
||||
<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 -->
|
||||
<!-- hamburger SVG: three horizontal <path> elements -->
|
||||
</span>
|
||||
</span>
|
||||
<!-- 🆕 unread dot appears here when unread chats exist (see §1b) -->
|
||||
</span>
|
||||
<span class="flex min-w-0 flex-col" title="Chats">
|
||||
<span class="block min-w-0 truncate">Chats</span>
|
||||
<span class="block min-w-0 truncate">Chats</span> <!-- 🔧 actually the ACTIVE CHAT TITLE, see below -->
|
||||
</span>
|
||||
<span class="sr-only">Open chat and side chats</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
**Key facts:**
|
||||
**Key facts (✅ verified 2026-10-04 on opm/muse/pip):**
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Tag | `BUTTON` |
|
||||
| Always in DOM | Yes (both panel states) |
|
||||
| In DOM on `https://muse.ai/` or `/thread/<uuid>` | ✅ Yes (both panel states) |
|
||||
| In DOM on `/thread/new` stripped state | 🔧 **NO — completely absent** (see §8) |
|
||||
| Visible | Yes (`offsetParent !== null`) |
|
||||
| Position | Top-left: `x:84, y:12, w:82, h:36` |
|
||||
| Position | Top-left: `x:84, y:12, w:82, h:36` (exact on opm) |
|
||||
| `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 |
|
||||
| `data-pel-click` | `chat_threads_open_click` |
|
||||
| `data-slot` | `popover-anchor` |
|
||||
| Visible text | 🔧 **Active chat title, NOT static "Chats".** Shows e.g. `Heartbeat check for job scheduler`, `646 pip cross-operator coordination chat`; reads `Chats` only when main chat is active |
|
||||
| Screen-reader text | `Open chat and side chats` (+ `Unread chats` when unread exist) |
|
||||
| Icon | Hamburger menu (3 horizontal `<path>` lines), not a chat bubble |
|
||||
|
||||
**Parent chain (bottom-up, 4 levels):**
|
||||
**Parent chain (bottom-up, 4 levels):** ✅ unchanged
|
||||
|
||||
```
|
||||
1. DIV [data-testid="hatch-chat-leading-controls"]
|
||||
@@ -57,17 +63,42 @@ top-left of the viewport.
|
||||
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.
|
||||
**Behavior (✅ verified atomically 2026-10-04):** The switcher **toggles in place —
|
||||
no navigation**. Click when closed → panel opens (shell `innerHTML` 0 → 60667,
|
||||
rows 0 → 26 on opm). Click when open → panel closes. Always check panel state
|
||||
before clicking — never blind-click. (An earlier non-atomic probe appeared to
|
||||
navigate; that was a race with the live heartbeat job driving the same browser —
|
||||
see §9.)
|
||||
|
||||
**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
|
||||
- 🆕 `[data-testid="hatch-chat-switcher-unread-indicator"]` — unread blue dot (see §1b)
|
||||
|
||||
---
|
||||
|
||||
## 2. Sidechat Create Button ("New side chat")
|
||||
## 1b. Unread Indicator 🆕 (verified 2026-10-04 on muse)
|
||||
|
||||
When unread chats exist, the switcher gains:
|
||||
|
||||
```html
|
||||
<span data-testid="hatch-chat-switcher-unread-indicator"
|
||||
class="bg-fill-blue absolute -end-0.5 size-2 rounded-full"
|
||||
aria-hidden="true"></span>
|
||||
<span class="sr-only">Unread chats</span>
|
||||
```
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Selector | `[data-testid="hatch-chat-switcher-unread-indicator"]` |
|
||||
| Visual | Blue dot (`bg-fill-blue`, 8px circle) overlaid on the switcher icon |
|
||||
| `aria-hidden` | `true` — screen readers get the sr-only `Unread chats` text instead |
|
||||
| Programmatic signal | **Presence = unread chats exist.** Most reliable unread check |
|
||||
| Companion signal | `document.title` gains an `(N)` prefix, e.g. `(1) Chat — operator-pip` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Sidechat Create Button ("New side chat") ✅
|
||||
|
||||
**Selector:** `[data-testid="hatch-chat-compose"]`
|
||||
|
||||
@@ -88,48 +119,31 @@ is OPEN.** This makes it the most reliable "panel is open and ready" signal.
|
||||
</button>
|
||||
```
|
||||
|
||||
**Key facts:**
|
||||
**Key facts (✅ verified):**
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Tag | `BUTTON` |
|
||||
| In DOM when panel closed | **No** — completely absent |
|
||||
| In DOM when panel open | Yes |
|
||||
| In DOM when panel open | ✅ Yes (`visible: offsetParent !== null`) |
|
||||
| Text content | None (icon-only) |
|
||||
| `aria-label` | `New side chat` |
|
||||
| `title` | `New side chat` |
|
||||
| `aria-label` / `title` | `New side chat` |
|
||||
| Icon | Plus sign (`+`), single SVG path |
|
||||
|
||||
**Parent chain (bottom-up, 8 levels):**
|
||||
**Parent chain (bottom-up, 8 levels):** ✅ unchanged — `SPAN.contents > DIV.group/nav-section`
|
||||
(header row: `H3 "Side chats"` + compose) inside the `SECTION` "Side chats".
|
||||
|
||||
```
|
||||
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).
|
||||
**What clicking does (✅ verified 2026-10-04):** Creates a new sidechat draft.
|
||||
Browser navigates `https://muse.ai/` → `https://muse.ai/thread/new`
|
||||
(placeholder URL; the real thread UUID appears after the first message is sent
|
||||
or navigation settles). **No sidechat exists server-side until a message is sent**
|
||||
— navigating to `/thread/new` and back is harmless.
|
||||
|
||||
---
|
||||
|
||||
## 3. Chat Navigation Panel Structure
|
||||
## 3. Chat Navigation Panel Structure ✅
|
||||
|
||||
### 3a. Panel shell (always in DOM)
|
||||
### 3a. Panel shell (always in DOM — except §8)
|
||||
|
||||
**Selector:** `[data-testid="hatch-side-chats-panel-shell"]`
|
||||
|
||||
@@ -141,70 +155,50 @@ the first message is sent or navigation settles).
|
||||
</div>
|
||||
```
|
||||
|
||||
| State | `innerHTML` | `innerText` |
|
||||
| State | `innerHTML` | `style.width` / computed |
|
||||
|---|---|---|
|
||||
| Closed | `""` (empty string) | `""` |
|
||||
| Open | Full `<nav>` tree (see below) | `Main chat\njust now\nSide chats\n<chat titles>...` |
|
||||
| Closed | ✅ `""` (empty string) | ✅ `0px` |
|
||||
| Open | ✅ Full `<nav>` tree (60KB+ on opm, 26 rows) | ✅ `240px` |
|
||||
|
||||
**Panel width:** Fixed `240px`.
|
||||
### 3b. Open panel tree ✅ (verified — 26 rows on opm)
|
||||
|
||||
### 3b. Open panel tree
|
||||
Structure unchanged from original capture. `NAV[aria-label="Side chats"]` →
|
||||
`DIV[data-testid="hatch-side-chats-panel-content"]` → search toolbar (60px) +
|
||||
scrollable thread list → `DIV[data-testid="hatch-thread-row"][role="button"]`
|
||||
(Main chat first) → `SECTION` (Side chats: collapsible `H3` header + compose +
|
||||
chat list) → `DIV[data-testid="active-side-chat-load-more-sentinel"]`.
|
||||
|
||||
When open, the shell contains:
|
||||
### 3c. Related panel testids ✅ (extended)
|
||||
|
||||
```
|
||||
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 | Status |
|
||||
|---|---|---|---|
|
||||
| `hatch-side-chats-panel-shell` | `DIV` | Outer shell; always in DOM (except §8); 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 (`input[aria-label="Search"][placeholder="Search"]`) | ✅ |
|
||||
| `hatch-side-chats-options` | `BUTTON` | `aria-label="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-side-chats-open-drag-handle-divider-line` | `DIV` | Drag handle divider | 🆕 |
|
||||
| `hatch-chat-nav-fade` | `DIV` | Fade effect element | ✅ |
|
||||
| `hatch-chat-switcher-unread-indicator` | `SPAN` | Unread blue dot on switcher | 🆕 (see §1b) |
|
||||
|
||||
### 3c. Related panel testids
|
||||
### 3d. Thread row structure (each chat / sidechat) ✅ (with corrections)
|
||||
|
||||
| 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:
|
||||
Every row — Main chat and each sidechat — uses the same base structure:
|
||||
|
||||
```html
|
||||
<div data-testid="hatch-thread-row"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
draggable="false"
|
||||
data-state="closed"
|
||||
data-slot="context-menu-trigger"
|
||||
data-state="closed" <!-- 🔧 sidechat rows ONLY; absent on Main chat row -->
|
||||
data-slot="context-menu-trigger" <!-- 🔧 sidechat rows ONLY; absent on Main chat row -->
|
||||
data-pel-click="chat_thread_options_open"
|
||||
aria-current="page" <!-- ONLY on the currently active chat -->
|
||||
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">
|
||||
@@ -212,60 +206,61 @@ Every row — Main chat and each sidechat — uses the same structure:
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-caption-1 text-text-secondary shrink-0 whitespace-nowrap ...">just now</span>
|
||||
<!-- hover-only action buttons -->
|
||||
<div class="ms-[3px] shrink-0 items-center"> <!-- 🔧 sidechat rows ONLY -->
|
||||
<button aria-label="More thread actions">…</button> <!-- 🆕 hover-only -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Key facts:**
|
||||
**Key facts (✅ verified 2026-10-04):**
|
||||
|
||||
| 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 |
|
||||
| Navigation | React `onClick` — **zero `<a href="/thread/...">` links exist** in the panel (verified: `a[href*="/thread/"]` count = 0) |
|
||||
| Thread UUIDs in panel DOM | ✅ **None.** Regex scan of panel `innerHTML` for UUIDs: 0 hits. Zero `data-value` attributes |
|
||||
| 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. |
|
||||
| Timestamp | Trailing span: `just now`, `1m`, `4m`, `11m`, etc. |
|
||||
| Main chat row | First `hatch-thread-row`, text `Main chat` |
|
||||
|
||||
**🔧 Main chat row vs sidechat rows** — they are NOT identical:
|
||||
|
||||
| Attribute | Main chat row | Sidechat rows |
|
||||
|---|---|---|
|
||||
| `data-state` | absent | `closed` |
|
||||
| `data-slot` | absent | `context-menu-trigger` |
|
||||
| Hover action container (`DIV.ms-[3px]`) | absent | present |
|
||||
| Hover button | none | `button[aria-label="More thread actions"]` (🆕 named) |
|
||||
| `aria-current="page"` | when main is active | when that sidechat is active |
|
||||
|
||||
**Distinguishing Main chat from sidechats:** Same `data-testid`. Match on text
|
||||
content (`Main chat` vs sidechat title) or check `aria-current="page"`.
|
||||
There is **no thread identity in the DOM** — title text (or position +
|
||||
`aria-current`) is the only handle. (Search results are the exception:
|
||||
`data-value="chat:thread:<uuid>"` — see DOM-SEARCH.md.)
|
||||
|
||||
### 3e. "Side chats" section header
|
||||
### 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.
|
||||
Unchanged: `H3 > BUTTON` toggles section collapse (`aria-expanded="true"/"false"`;
|
||||
verified `true` on opm). The compose `+` button sits beside it.
|
||||
|
||||
---
|
||||
|
||||
## 4. Panel Open vs Closed — Detection Guide
|
||||
## 4. Panel Open vs Closed — Detection Guide ✅
|
||||
|
||||
### Reliable signals
|
||||
### Reliable signals (✅ all re-verified)
|
||||
|
||||
| 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...` | `""` |
|
||||
| Panel shell computed width | `240px` | `0px` |
|
||||
|
||||
### Recommended checks (in order of reliability)
|
||||
### Recommended checks (in order of reliability) ✅ unchanged
|
||||
|
||||
```javascript
|
||||
// BEST: panel is open and ready for sidechat creation
|
||||
@@ -279,16 +274,17 @@ const panelOpenAlt = shell && shell.innerHTML !== "";
|
||||
const rowCount = document.querySelectorAll('[data-testid="hatch-thread-row"]').length;
|
||||
```
|
||||
|
||||
### Signals that DO NOT work
|
||||
### Signals that DO NOT work ✅ unchanged
|
||||
|
||||
| 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. |
|
||||
| `document.body.innerText.includes('Side chats')` | **False positive.** Matches the switcher's sr-only text and 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 |
|
||||
| Switcher visible text | 🔧 **Dynamic** — shows the active chat title, not panel state |
|
||||
|
||||
### Safe toggle sequence
|
||||
### Safe toggle sequence ✅ (behavior verified both directions 2026-10-04)
|
||||
|
||||
```javascript
|
||||
// 1. Detect actual panel state (not body text)
|
||||
@@ -305,25 +301,37 @@ const nowOpen = !!document.querySelector('[data-testid="hatch-chat-compose"]');
|
||||
if (!nowOpen) throw new Error("Panel did not open");
|
||||
```
|
||||
|
||||
Do the detect → click → verify **atomically** (single evaluate + `awaitPromise`)
|
||||
— the fleet browsers are live and timer jobs may navigate between separate
|
||||
round-trips (see §9).
|
||||
|
||||
---
|
||||
|
||||
## 5. Full data-testid Inventory (chat-related)
|
||||
## 5. Full data-testid Inventory (chat-related) ✅ (extended 2026-10-04)
|
||||
|
||||
Captured on `https://muse.ai/` main chat view. `visible` = computed style check.
|
||||
🆕 = found 2026-10-04, not in original capture.
|
||||
|
||||
| testid | Tag | aria-label | Notes |
|
||||
|---|---|---|---|
|
||||
| `hatch-dock-rail` | `DIV` | — | Left dock rail |
|
||||
| `hatch-nav-chat` | `A` | `Chat` | Dock nav: Chat |
|
||||
| `hatch-dock-rail-divider` | `DIV` | — | Rail divider |
|
||||
| `hatch-nav-chat` | `A` | `Chat` | Dock nav: Chat (`href="/"`) |
|
||||
| `hatch-nav-search` | `BUTTON` | `Search` | Dock nav: Search |
|
||||
| `hatch-nav-system-item-*` | `DIV` | `Feed`/`Ideas`/`Goals`/`Library` | Dock system items |
|
||||
| `hatch-nav-system-item-66656564` | `DIV` | `Feed` | 🆕 suffix = hex("feed") |
|
||||
| `hatch-nav-system-item-676f616c73` | `DIV` | `Goals` | 🆕 suffix = hex("goals") |
|
||||
| `hatch-nav-system-item-6964656173` | `DIV` | `Ideas` | 🆕 suffix = hex("ideas") |
|
||||
| `hatch-nav-system-item-6c696272617279` | `DIV` | `Library` | 🆕 suffix = hex("library") |
|
||||
| `hatch-dock-more` | `BUTTON` | `Settings` | Dock settings |
|
||||
| `app-download-dock-mark` | `DIV` | — | 🆕 App download badge in dock |
|
||||
| `hatch-chat-leading-controls` | `DIV` | — | Top-left container for switcher |
|
||||
| `hatch-chat-switcher-trigger` | `BUTTON` | — | **Panel toggle** (text: "Chats") |
|
||||
| `hatch-chat-switcher-trigger` | `BUTTON` | — | **Panel toggle** (label = active chat title) |
|
||||
| `hatch-chat-switcher-icon-knockout` | `SPAN` | — | Icon wrapper |
|
||||
| `hatch-chat-switcher-navigation-icon` | `SPAN` | — | Hamburger SVG |
|
||||
| `hatch-chat-switcher-unread-indicator` | `SPAN` | — | 🆕 Unread blue dot (see §1b) |
|
||||
| `hatch-side-chats-open-drag-handle` | `DIV` | — | Panel resize handle |
|
||||
| `hatch-side-chats-panel-shell` | `DIV` | — | **Panel shell** (always in DOM) |
|
||||
| `hatch-side-chats-open-drag-handle-divider-line` | `DIV` | — | 🆕 Handle divider |
|
||||
| `hatch-side-chats-panel-shell` | `DIV` | — | **Panel shell** (always in DOM, §8 excepted) |
|
||||
| `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) |
|
||||
@@ -335,27 +343,40 @@ Captured on `https://muse.ai/` main chat view. `visible` = computed style check.
|
||||
| `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-composer-stop-button` | `BUTTON` | — | 🆕 Stop-generation button (in composer) |
|
||||
| `hatch-status-panel-sliding-surface` | `DIV` | — | Agent status panel |
|
||||
| `hatch-status-panel-close` | `DIV` | — | 🆕 Status panel (closed state) |
|
||||
| `hatch-status-panel-close-toolbar` | `DIV` | — | 🆕 |
|
||||
| `hatch-status-panel-close-drag-handle` | `DIV` | — | 🆕 |
|
||||
| `hatch-status-panel-close-drag-handle-divider-line` | `DIV` | — | 🆕 |
|
||||
| `hatch-browser-task-banner-host` | `DIV` | — | Task banner (hidden) |
|
||||
|
||||
**🆕 `hatch-nav-system-item-*` suffixes are hex-encoded names** (verified):
|
||||
`66656564`→`feed`, `676f616c73`→`goals`, `6964656173`→`ideas`,
|
||||
`6c696272617279`→`library` — each matches the element's `aria-label`.
|
||||
Do not hardcode the full testid; match on the decoded suffix or aria-label.
|
||||
|
||||
---
|
||||
|
||||
## 6. Automation Notes
|
||||
## 6. Automation Notes ✅ (extended)
|
||||
|
||||
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.
|
||||
1. **Ctrl+J is unreliable** as a generic panel toggle. 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"`.
|
||||
2. **The `+` button has no text.** Never search for `"+"` text — 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.
|
||||
3. **After creating a sidechat**, the browser navigates to
|
||||
`https://muse.ai/thread/new`. This **stripped state** has no switcher at
|
||||
all (§8) — click `[data-testid="hatch-nav-chat"]` (or navigate to
|
||||
`https://muse.ai/`) before the next panel operation. Navigating to
|
||||
`/thread/new` and back creates nothing server-side until a message is sent.
|
||||
|
||||
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.
|
||||
4. **Sidechat rows are not links and carry no identity.** 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 — capture it from `location.href` after the first
|
||||
message (poll `/thread/new` → `/thread/<uuid>`).
|
||||
|
||||
5. **Thread UUID capture:** After sending the first message in a new sidechat,
|
||||
`window.location.href` transitions from `/thread/new` to `/thread/<uuid>`.
|
||||
@@ -365,3 +386,77 @@ Captured on `https://muse.ai/` main chat view. `visible` = computed style check.
|
||||
|
||||
7. **The "Side chats" H3 is collapsible** (`aria-expanded`). If rows are missing
|
||||
while the panel is open, check whether the section is collapsed.
|
||||
|
||||
8. 🆕 **Switcher label is the active chat title** — never match on it for state.
|
||||
Use compose presence (§4).
|
||||
|
||||
9. 🆕 **Unread detection:** `[data-testid="hatch-chat-switcher-unread-indicator"]`
|
||||
presence, or `/^\(\d+\)/` prefix on `document.title`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Panel innerText Format (reference) ✅
|
||||
|
||||
Open panel shell `innerText` (opm, 26 rows), for text-based fallbacks:
|
||||
|
||||
```
|
||||
Main chat\n\n2m\nSide chats\n\nCheck the board\n\n1m\n\nAlign chat and board API\n\n4m\n\n…
|
||||
```
|
||||
|
||||
Pattern: `<title>\n\n<relative-ts>\n\n…`. Timestamps are relative
|
||||
(`just now`, `1m`, `4m`, `11m`). The `H3` contributes the literal `Side chats`
|
||||
line — which is why body-text matching for panel state is unreliable (§4).
|
||||
|
||||
---
|
||||
|
||||
## 8. Stripped State: `/thread/new` 🆕 (verified 2026-10-04)
|
||||
|
||||
After clicking compose (before any message is sent), the browser is at
|
||||
`https://muse.ai/thread/new`. The chat chrome is **stripped**:
|
||||
|
||||
| Element | State |
|
||||
|---|---|
|
||||
| `[data-testid="hatch-chat-switcher-trigger"]` | **ABSENT** — not just closed, gone from DOM |
|
||||
| `[data-testid="hatch-side-chats-panel-shell"]` | Present, `innerHTML` empty, computed width `0px` |
|
||||
| `[data-testid="hatch-chat-compose"]` | Absent |
|
||||
| `[data-testid="hatch-thread-row"]` | 0 |
|
||||
| `[data-testid="hatch-chat-leading-controls"]` | Present (empty container) |
|
||||
| `[data-testid="hatch-nav-chat"]` | ✅ Present — `A`, `aria-label="Chat"`, `href="/"` — **the recovery path** |
|
||||
| Dock rail, `hatch-chat-nav-fade`, drag handle | Present |
|
||||
|
||||
**Recovery (verified):** click `[data-testid="hatch-nav-chat"]` or set
|
||||
`location.href = 'https://muse.ai/'`. This is the click-based activation from
|
||||
the 72574ba fix — do NOT use Ctrl+J here (unreliable in stripped states).
|
||||
|
||||
**Automation rule:** any panel operation must first assert the switcher exists.
|
||||
If `!document.querySelector('[data-testid="hatch-chat-switcher-trigger"]')`,
|
||||
you are in the stripped state — recover via `hatch-nav-chat` before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## 9. Per-Node Differences 🆕 (verified 2026-10-04)
|
||||
|
||||
| Node | CDP | Page title | Notes |
|
||||
|---|---|---|---|
|
||||
| `opm` | 9440 | `Chat — operator-main` | 26 panel rows at capture; doc baseline |
|
||||
| `muse` | 9410 | `Chat — muse` | Unread indicator present at capture; panel closed |
|
||||
| `pip` | 9420 | `(1) Chat — operator-pip` | `(1)` = unread count in title; panel was OPEN while on a `/thread/<uuid>` page — panel open-state persists across thread navigation |
|
||||
|
||||
All panel selectors, toggle behavior, and detection signals are identical
|
||||
across nodes. Only titles, row contents, and unread state differ.
|
||||
|
||||
---
|
||||
|
||||
## 10. Probing Methodology Notes 🆕
|
||||
|
||||
- Drive the DOM via CDP `Runtime.evaluate` (`returnByValue: true`).
|
||||
- The fleet browsers are **live**: timer jobs (e.g. heartbeat every 5 min)
|
||||
navigate the same pages. Non-atomic probe sequences (click → separate
|
||||
round-trip → read) can race with job navigation and produce misleading
|
||||
results. Do toggle tests **atomically**: click + wait + snapshot in a single
|
||||
evaluate with `awaitPromise: true`.
|
||||
- `document.title` is `Muse` during load and becomes `Chat — <account>`
|
||||
(optionally `(N)`-prefixed) once settled — don't assert titles too early.
|
||||
- Sibling inspectors share `/tmp` on bl: namespace probe files
|
||||
(e.g. `/tmp/dip1_*.js`) — a generic `/tmp/cdp_eval.py` was overwritten
|
||||
mid-task by another agent.
|
||||
|
||||
Reference in New Issue
Block a user