Session: sidechat/dom-structure
15 KiB
muse.ai DOM Index — Master Reference
Entry point for all muse.ai DOM automation work. Read this first. It indexes every
data-testid, every key selector, every automation recipe, and flags known contradictions between the detailed docs.Rule of thumb from the field: prefer
data-testid>aria-label>role/idclass names. Tailwind classes shift with deploys; testids don't.
1. Document Map
| Doc | Covers | Last verified |
|---|---|---|
| DOM-CHAT-PANEL.md | Chat panel switcher, compose + button, panel shell/tree, thread rows, open-vs-closed detection, 24-testid inventory |
2026-10-04 (CDP, opm) |
| DOM-MESSAGES.md | Message composer, send/stop buttons, message list (data-message-id), chat activation button (hatch-nav-chat), loading/empty states |
2026-10-04 ~04:30 UTC (CDP, all 3 nodes) |
| DOM-PAGE-STRUCTURE.md | Page states/modes, key selectors, nav rail, approval-dialog summary, overlays, URL patterns, testid inventory, automation sequences | 2026-10-04 ~04:35 UTC (CDP, muse/pip/opm) |
| DOM-EDGE-STATES.md | Loading/partial-load (S3), stripped states (S8), empty states, typing indicator, error/toast traps, offline, rate limits, iframes/shadow DOM, readiness cheat-sheet | 2026-10-04 ~04:35 UTC (CDP, muse/pip/opm) |
| DOM-MESSAGE-ACTIONS.md | Message actions & thread interactions (.group/msg, more-options menus, reactions, reply drafts) |
2026-10-04 ~04:30 UTC (CDP, all 3 nodes) |
| DOM-NOTIFICATIONS.md | Notification surface: toast live region (no bell, no notification center), activity indicators | 2026-10-04 (CDP, opm) |
| DOM-SEARCH.md | Global search / command palette: trigger, input, results | 2026-10-04 (CDP, opm) |
| DOM-SETTINGS.md | Settings, profile & dock rail structure (no credential values recorded) | 2026-10-04 (CDP, opm) |
| DOM-APPROVALS-SPEC.md | Approval/permission dialog detection, classification (trusted IPs), handling, exit-code contract (2 = APPROVAL_NEEDED) |
2026-10-03 |
Naming note: inspector 3/5's brief mentioned
DOM-APPROVALS.md; the file on disk isDOM-APPROVALS-SPEC.md(since 2026-10-03). This index references the actual filename.
Cross-reference guide:
- Sidechat creation mechanics → CHAT-PANEL (§4 safe toggle, §6 automation notes)
- What to type into / how to send → MESSAGES (§1 composer, §2 send)
- "Which state am I in?" → PAGE-STRUCTURE (§1 states, §7.2 one-liner) or §3 below
- "Am I blocked by a dialog?" → APPROVALS-SPEC (detection + exit codes)
- "Chat function not active" → MESSAGES (§4
hatch-nav-chat) + §4 recipe below
2. Master data-testid Table (deduplicated)
Docs column: C = CHAT-PANEL, M = MESSAGES, P = PAGE-STRUCTURE.
Chat core (panel toggle + creation)
| testid | Tag | Purpose | Docs |
|---|---|---|---|
hatch-chat-switcher-trigger |
BUTTON |
Panel open/close toggle. Inner text is the current thread title (not Chats) — never match by text. Never blind-click — check compose first |
C, P |
hatch-side-chats-open-drag-handle-divider-line |
DIV |
Divider for the closed-panel drag handle; absent when panel is open (verified 2026-10-04, all nodes) | P |
hatch-chat-switcher-icon-knockout |
SPAN |
Icon wrapper inside switcher | C, P |
hatch-chat-switcher-navigation-icon |
SPAN |
Hamburger SVG inside switcher | C, P |
hatch-chat-leading-controls |
DIV |
Top-left container holding the switcher | C, P |
hatch-chat-compose |
BUTTON |
New side chat +. Icon-only (aria-label="New side chat"). Exists only when panel is open → the canonical "ready" signal |
C, M, P |
hatch-chat-nav-fade |
DIV |
Nav fade effect | C, P |
hatch-chat-search-toolbar |
DIV |
Panel search toolbar (60px) | C, P |
hatch-chat-search-field |
DIV |
Search input container | C, P |
hatch-side-chats-options |
BUTTON |
"Side chat options" three-dots dropdown | C, P |
hatch-chat-switcher-unread-indicator |
— | Unread badge on switcher | M |
Sidechat panel tree
| testid | Tag | Purpose | Docs |
|---|---|---|---|
hatch-side-chats-panel-shell |
DIV |
Outer shell, always in DOM, innerHTML === "" when closed, 240px fixed |
C, P |
hatch-side-chats-panel-sliding-surface |
DIV |
Sliding animation surface (open only) | C, P |
hatch-side-chats-panel-content |
DIV |
Content wrapper — presence = panel open | C, P |
hatch-side-chats-open-drag-handle |
DIV |
Panel resize handle | C, P |
hatch-thread-row |
DIV[role=button] |
Every chat row (Main + sidechats). Not links — click to navigate. Active row has aria-current="page" |
C, P |
hatch-side-chat-unread-indicator |
— | Per-chat unread indicator | P |
active-side-chat-load-more-sentinel |
DIV |
Infinite-scroll sentinel at list end | C, P |
Dock rail (left nav)
| testid | Tag | Purpose | Docs |
|---|---|---|---|
hatch-dock-rail |
DIV |
Left dock rail container | C, M, P |
hatch-dock-rail-divider |
SPAN |
Rail divider | P |
hatch-nav-chat |
A |
Chat section link (href="/"). aria-label is dynamic (Chat / Chat, N notifications) — match testid, never exact aria-label. aria-current="page" when Chat active. The reliable chat activator |
C, M, P |
hatch-dock-chat-notification-badge |
— | Notification badge on nav-chat | P |
hatch-nav-search |
BUTTON |
Search nav | C, M, P |
hatch-nav-system-item-66656564 etc. |
DIV |
Feed / Ideas / Goals / Library (hex-encoded ids) | C, P |
hatch-dock-more |
BUTTON |
Settings (aria-label="Settings") |
C, M, P |
app-download-dock-mark |
— | App download mark | P |
hatch-panel-resize-divider-line |
DIV |
Panel resize divider | P |
Composer & messages
| testid | Tag | Purpose | Docs |
|---|---|---|---|
hatch-composer-placeholder-overlay |
SPAN |
"Message" ghost text — visible when composer empty | C, M, P |
hatch-composer-stop-button |
BUTTON |
Stop-generation — present only while streaming | M |
hatch-chat-typing-indicator |
— | Typing indicator — present only while generating | M |
Status / misc
| testid | Tag | Purpose | Docs |
|---|---|---|---|
hatch-status-panel-sliding-surface |
DIV |
Agent status panel | C, P |
hatch-status-panel-close |
BUTTON |
Status panel close | P |
hatch-status-panel-close-drag-handle |
DIV |
Status panel drag handle | P |
hatch-status-panel-close-drag-handle-divider-line |
DIV |
Divider | P |
hatch-status-panel-close-toolbar |
DIV |
Toolbar | P |
hatch-invite-friends-button |
BUTTON |
Invite (aria-label="Invite") |
C, P |
hatch-browser-task-banner-host |
DIV |
Browser task banner (hidden) | C, P |
hatch-inline-approval-card |
DIV |
Browser-task approval card (A task needs review + Review button), no IP in text. Structure TBD — inspector 3/5, DOM-APPROVALS-SPEC.md |
P |
3. Non-testid Selectors (equally load-bearing)
| Selector | Purpose | Doc |
|---|---|---|
textarea[aria-label="Message"] |
Message composer input. Fallbacks: [contenteditable="true"], textarea[placeholder*="Message"], div[role="textbox"] |
M |
button[aria-label*="send" i] |
Send button (no testid, SVG-only). Fallback: Enter keydown on input | M |
#hatch-chat-scroll |
Message scroll container | M |
div[role="log"][aria-label="Chat messages"] |
Message list | M |
[data-message-id] |
Each message. assistant-msg-<uuid> = assistant, bare <uuid> = user |
M |
button[aria-label="Back to main chat"] |
Primary main-chat affordance from any thread state (/thread/<uuid> and /thread/new), panel open or closed. Verified 2026-10-04 on all 3 nodes |
P (§2.7) |
Exact-text Main chat element |
Main-chat navigation target — match (e.innerText).trim() === 'Main chat' on button, [role="button"], never substring |
P (§2.7), AGENTS.md |
4. Quick-Reference Automation Recipes
4a. Activate chat (recover from ANY state) — replaces Ctrl+J
// Priority: compose (fast path) → switcher → nav-chat (stripped-state recovery)
function ensureChatActive() {
if (document.querySelector('[data-testid="hatch-chat-compose"]')) return true; // already ready
const sw = document.querySelector('[data-testid="hatch-chat-switcher-trigger"]');
if (sw) { sw.click(); return "clicked-switcher"; } // closed panel
const nc = document.querySelector('[data-testid="hatch-nav-chat"]');
if (nc) { nc.click(); return "clicked-nav-chat"; } // /thread/new stripped
return false; // not on a chat page at all — abort
}
// Poll up to ~20s, then verify compose exists before proceeding.
Why not Ctrl+J: it needs keyboard focus, which stripped states (/thread/new)
lack — the keystroke goes nowhere. DOM clicks don't need focus. (Commit 72574ba.)
4b. Detect current state (one-liner)
(() => {
const url = window.location.href, title = document.title;
const q = s => !!document.querySelector(s);
if (title === 'muse.ai') return 'LANDING';
if (url.includes('/thread/new')) return 'THREAD_NEW_STRIPPED';
if (url.includes('/thread/')) return q('[data-testid="hatch-chat-switcher-trigger"]') ? 'SIDECHAT' : 'THREAD_NEW_STRIPPED';
if (url === 'https://muse.ai/') {
if (!q('[data-testid="hatch-chat-switcher-trigger"]')) return 'UNKNOWN_NO_SWITCHER';
return q('[data-testid="hatch-chat-compose"]') ? 'MAIN_PANEL_OPEN' : 'MAIN_PANEL_CLOSED';
}
return 'UNKNOWN';
})()
State table: LANDING · MAIN_PANEL_CLOSED · MAIN_PANEL_OPEN · THREAD_NEW_STRIPPED
(/thread/new, no switcher) · SIDECHAT (/thread/<uuid>). Full matrix: PAGE-STRUCTURE §1.
4c. Create sidechat (robust)
1. ensureChatActive() (§4a) — poll ≤20s, abort if false
2. Verify: !!document.querySelector('[data-testid="hatch-chat-compose"]')
3. Click compose
4. Poll window.location.href ≤15s for "/thread/" → /thread/new (placeholder)
5. Send first message directly (browser is on the new sidechat)
6. Poll href for /thread/<uuid> to capture the durable thread URL
4d. Send a message
1. ta = document.querySelector('textarea[aria-label="Message"]') (+ fallbacks)
2. Verify ready: ta.offsetParent !== null && !ta.disabled && !ta.readOnly
3. ta.focus(); document.execCommand('insertText', false, message)
(React fallback: native value setter + bubbling 'input' event)
4. send = [...document.querySelectorAll('button')].find(b => b.getAttribute('aria-label')?.toLowerCase().includes('send'))
→ send.click() else Enter-keydown on ta
5. If streaming: [data-testid="hatch-composer-stop-button"] appears; wait for it to vanish
4e. Open a specific sidechat / go to main chat
// By title (rows are DIV[role=button], NOT links — no hrefs exist):
const row = [...document.querySelectorAll('[data-testid="hatch-thread-row"]')]
.find(r => r.querySelector('span[title]')?.getAttribute('title') === '<title>');
row?.click();
// Main chat: exact text match, never substring:
const main = [...document.querySelectorAll('button, [role="button"]')]
.find(e => (e.innerText || '').trim() === 'Main chat');
main?.click();
4f. Check for blocking approval dialogs
Text-based (no stable testids observed). See APPROVALS-SPEC for the full contract:
- Body text contains
Allow+to sharein an element < 500 chars, and an IPv4 is present - Trusted IPs (
34.139.37.135,100.123.153.75,100.81.31.9) → auto-clickallow once/allow - Anything else → print
APPROVAL_NEEDED, exit 2, do NOT click
5. Known Contradictions (resolved here — detail docs may lag)
| # | Conflict | Resolution (this index wins) |
|---|---|---|
| 1 | PAGE-STRUCTURE §7.1 and §2.7 recommend Ctrl+J as "proven reliable" for main-chat navigation / state recovery | Superseded. Ctrl+J needs keyboard focus; stripped states lack it. Use the §4a click sequence (hatch-nav-chat → switcher). CHAT-PANEL §6.1 already agrees Ctrl+J is unreliable |
| 2 | PAGE-STRUCTURE §1.3 "Recovery: Send Ctrl+J" from /thread/new |
Use button[aria-label="Back to main chat"] click or hatch-nav-chat click instead |
| 3 | /thread/new title: MESSAGES §5 and PAGE-STRUCTURE §1.3 say Muse; a 2026-10-04 probe table said Chat — … |
Docs consensus: Muse. Treat title as advisory only — detect via URL + switcher absence (§4b), never title alone |
| 4 | "Sidechat create navigates to main first" (older code comment) | Unnecessary. The ready state for create is any chat-active view; hatch-nav-chat recovery lands on main anyway |
| 5 | CHAT-PANEL / older notes describe switcher text as Chats (or Chats\nUnread chats…) |
Thread title. Verified 2026-10-04 on all nodes: switcher innerText = <current thread title>\nOpen chat and side chats. Never match the switcher by text |
6. Gaps — Unmapped Territory
hatch-inline-approval-cardstructure — testid + surface text documented (PAGE-STRUCTURE §6, this index §2), full structure deferred to inspector 3/5 in DOM-APPROVALS-SPEC.md.- Feed / Ideas / Goals / Library pages — dock testids listed, page content unmapped.
- Search overlay (
hatch-nav-searchtarget) — trigger documented in DOM-SEARCH.md; result-row interactions unmapped. - OTP / account-selection states — body-text markers known (
To log in, enter the code,Your email matches multiple accounts), DOM unmapped. Automation should abort to human here. cmd_sidechat_main's exact-textMain chatlookup — documented in AGENTS.md and §3/§4e here, no DOM-doc section.- Partial-load state (title
Muse, ~21 buttons, transient) — mentioned in MESSAGES §5, no recovery procedure beyond "navigate to/and re-probe". - Message read-back verification selectors — MESSAGES §3 covers extraction; the
dm.pytail-match verification loop is code-only. /tmpscript collisions between inspectors — two inspectors overwrote each other's/tmp/cdp_eval.pyon bl mid-task (2026-10-04). Namespace throwaway scripts per-inspector (e.g./tmp/cdp_eval_<area><n>.py).
Resolved 2026-10-04:
button[aria-label="Back to main chat"]now has a dedicated section in PAGE-STRUCTURE §2.7 (was gap #1 in the previous index).
7. Maintenance
- This index is the entry point; detail docs hold the deep dumps. When a selector changes, update the detail doc and the tables/recipes here.
- Contradiction rule: newer verified probe beats older doc text; record the resolution in §5 with the commit or date.
- "Last verified" in §1 is per-doc. If you re-probe a doc's selectors, bump its date.
- Prefer adding a dated note over rewriting history — these docs are also an audit trail.
- 2026-10-04 regeneration: document map now covers all 10
DOM-*.mdfiles (was 4); master table carries the two new testids (hatch-side-chats-open-drag-handle-divider-line,hatch-inline-approval-card) and the switcher-text + dock-rail-divider corrections. When a new DOM doc lands, add its row to §1 and its testids to §2 in the same commit.