# muse.ai DOM Index — Master Reference > **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. > **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 | |---|---|---| | [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) | | [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 | > 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. **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-` = assistant, bare `` = user | M | | `button[aria-label="Back to main chat"]` | **Primary main-chat affordance** from any thread state (`/thread/` 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 ```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/`). 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/ 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') === ''); 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 | | 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 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. 2. **Feed / Ideas / Goals / Library pages** — dock testids listed, page content unmapped. 3. **Search overlay** (`hatch-nav-search` target) — trigger documented in DOM-SEARCH.md; result-row interactions unmapped. 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. 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). --- ## 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-*.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.