2026-10-04 03:50:49 +00:00
# muse.ai DOM Index — Master Reference
2026-10-05 15:58:37 +00:00
> **Box is the main surface.** All operator work goes through Box (box.muse-dev.online). The web UI, `box` CLI, and agents share the same API endpoints. No UI-only powers.
2026-10-04 03:50:49 +00:00
> **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`/`id`
> > class names. Tailwind classes shift with deploys; testids don't.
---
## 1. Document Map
| Doc | Covers | Last verified |
|---|---|---|
2026-10-04 04:29:10 +00:00
| [DOM-CHAT-PANEL.md ](./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 ](./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 ](./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 ](./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 ](./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 ](./DOM-NOTIFICATIONS.md ) | Notification surface: toast live region (no bell, no notification center), activity indicators | 2026-10-04 (CDP, opm) |
| [DOM-SEARCH.md ](./DOM-SEARCH.md ) | Global search / command palette: trigger, input, results | 2026-10-04 (CDP, opm) |
| [DOM-SETTINGS.md ](./DOM-SETTINGS.md ) | Settings, profile & dock rail structure (no credential values recorded) | 2026-10-04 (CDP, opm) |
2026-10-04 03:50:49 +00:00
| [DOM-APPROVALS-SPEC.md ](./DOM-APPROVALS-SPEC.md ) | Approval/permission dialog detection, classification (trusted IPs), handling, exit-code contract (`2` = `APPROVAL_NEEDED` ) | 2026-10-03 |
2026-10-04 04:29:10 +00:00
> Naming note: inspector 3/5's brief mentioned `DOM-APPROVALS.md`; the file on
> disk is `DOM-APPROVALS-SPEC.md` (since 2026-10-03). This index references the
> actual filename.
2026-10-04 03:50:49 +00:00
**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 |
|---|---|---|---|
2026-10-04 04:29:10 +00:00
| `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 |
2026-10-04 03:50:49 +00:00
| `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 |
2026-10-04 04:29:10 +00:00
| `hatch-dock-rail-divider` | `SPAN` | Rail divider | P |
2026-10-04 03:50:49 +00:00
| `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 |
2026-10-04 04:29:10 +00:00
| `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 |
2026-10-04 03:50:49 +00:00
---
## 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 |
2026-10-04 04:29:10 +00:00
| `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) |
2026-10-04 03:50:49 +00:00
| 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
``` javascript
// 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)
``` javascript
( ( ) => {
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
``` javascript
// 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 share` in an element < 500 chars, **and ** an IPv4 is present
- Trusted IPs (`34.139.37.135` , `100.123.153.75` , `100.81.31.9` ) → auto-click `allow 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 |
2026-10-04 04:29:10 +00:00
| 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 |
2026-10-04 03:50:49 +00:00
---
## 6. Gaps — Unmapped Territory
2026-10-04 04:29:10 +00:00
1. * * `hatch-inline-approval-card` structure** — testid + surface text documented (PAGE-STRUCTURE §6, this index §2), full structure deferred to inspector 3/5 in DOM-APPROVALS-SPEC.md.
2026-10-04 03:50:49 +00:00
2. **Feed / Ideas / Goals / Library pages ** — dock testids listed, page content unmapped.
2026-10-04 04:29:10 +00:00
3. **Search overlay ** (`hatch-nav-search` target) — trigger documented in DOM-SEARCH.md; result-row interactions unmapped.
2026-10-04 03:50:49 +00:00
4. **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.
5. * * `cmd_sidechat_main` 's exact-text `Main chat` lookup** — documented in AGENTS.md and §3/§4e here, no DOM-doc section.
6. **Partial-load state ** (title `Muse` , ~21 buttons, transient) — mentioned in MESSAGES §5, no recovery procedure beyond "navigate to `/` and re-probe".
7. **Message read-back verification selectors ** — MESSAGES §3 covers extraction; the `dm.py` tail-match verification loop is code-only.
2026-10-04 04:29:10 +00:00
8. * * `/tmp` script collisions between inspectors** — two inspectors overwrote each other's `/tmp/cdp_eval.py` on 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).
2026-10-04 03:50:49 +00:00
---
## 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 04:29:10 +00:00
- 2026-10-04 regeneration: document map now covers all 10 `DOM-*.md` files
(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.