diff --git a/docs/DOM-INDEX.md b/docs/DOM-INDEX.md new file mode 100644 index 0000000..c204571 --- /dev/null +++ b/docs/DOM-INDEX.md @@ -0,0 +1,224 @@ +# 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`/`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-test id 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 ~03:45 UTC (CDP, opm) | +| [DOM-PAGE-STRUCTURE.md](./DOM-PAGE-STRUCTURE.md) | Page states/modes, key selectors, approval dialogs, overlays, URL patterns, 57-testid inventory, automation sequences | 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 | + +**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**. Text: `Chats`. Never blind-click — check compose first | C, 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` | `DIV` | 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 | + +--- + +## 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"]` | **Escape hatch from `/thread/new`** stripped state (empty text, match aria-label) | *(subagent report 2026-10-04 — not yet in a detail doc)* | +| 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 | + +--- + +## 6. Gaps — Unmapped Territory + +1. **`button[aria-label="Back to main chat"]`** — verified in live probes, referenced in §3, but has no dedicated section in any detail doc yet. +2. **Feed / Ideas / Goals / Library pages** — dock testids listed, page content unmapped. +3. **Search overlay** (`hatch-nav-search` target) — 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. + +--- + +## 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.