` (bare, no prefix) | `User message: ...` * |
\* User-message text prefix not directly observed (no user messages were
rendered at capture time); author detection via the id prefix is the
reliable signal.
### Extracting text / author
```js
const msgs = [...document.querySelectorAll('[data-message-id]')].map(m => {
const id = m.getAttribute('data-message-id');
return {
id,
author: id.startsWith('assistant-msg') ? 'assistant' : 'user',
text: m.innerText
};
});
```
### Simple read-back (what `muse-chat-api.py messages` does)
```js
[...document.querySelectorAll('p')].slice(-n * 2).map(p => p.innerText.slice(0, width))
```
Works because message bodies render as `` elements; the last `2n`
paragraphs cover roughly the last `n` messages. Crude but effective for
"did my text land?" verification.
### Message-related testids seen in the wild
| testid | Meaning | Present when |
|---------------------------------------|--------------------------------------|--------------|
| `hatch-chat-typing-indicator` | assistant is typing/generating | generating only |
| `hatch-composer-stop-button` | stop-generation button | generating only |
| `hatch-composer-placeholder-overlay` | "Message" ghost text | composer empty |
| `hatch-chat-switcher-unread-indicator`| unread badge on the chat switcher | unread chats exist |
---
## 4. Chat activation button (speech-bubble / nav)
This is the dock-rail button that activates the Chat section. The user's
finding: sidechat create only works when the chat function is active, and
this button (not Ctrl+J) is the reliable activator.
### Selector
```js
document.querySelector('[data-testid="hatch-nav-chat"]')
```
### Observed attributes
| Attribute | Value |
|----------------|------------------------------------------------------------|
| tag | `A` (anchor, not button) |
| `data-testid` | `hatch-nav-chat` |
| `href` | `/` |
| `aria-label` | dynamic: `Chat` or `Chat, N notifications` (e.g. `Chat, 2 notifications`) |
| `aria-current` | `page` when Chat is the active section (absent otherwise) |
| visible | `true` (dock rail is always rendered) |
### Parent chain (4 levels)
```
a[data-testid="hatch-nav-chat"][aria-label="Chat, 2 notifications"][aria-current="page"]
└── div
└── div
└── div
└── div[data-testid="hatch-dock-rail"]
```
### States
| State | Signal |
|-------|--------|
| Chat section active | `aria-current="page"` present |
| Chat section inactive | `aria-current` absent (button still visible/clickable) |
| Unread messages | `aria-label` contains `N notifications` |
### Usage
```js
// Activate chat reliably (replaces Ctrl+J):
document.querySelector('[data-testid="hatch-nav-chat"]').click();
```
`cmd_sidechat_main` already falls back to this click when the "Main chat"
element lookup fails. Sibling nav buttons in the same rail:
`hatch-nav-search` (Search), plus `div[role="button"]` items for Feed, Ideas,
Goals, Library, and `hatch-dock-more` (Settings).
---
## 5. Loading / empty states
| State | How to detect |
|-------|---------------|
| New empty thread (`/thread/new`) | URL is `/thread/new`, `document.title === "Muse"`, zero `[data-message-id]` elements, composer present |
| Main chat loaded | URL is `/`, title is `Chat — `, `[data-message-id]` elements present |
| Assistant generating | `[data-testid="hatch-chat-typing-indicator"]` exists; send button replaced by `[data-testid="hatch-composer-stop-button"]` |
| Idle, ready to send | typing indicator absent, stop button absent, `textarea[aria-label="Message"]` visible and enabled |
| Composer empty | `[data-testid="hatch-composer-placeholder-overlay"]` visible with text `Message` |
| Side panel open (threads visible) | `[data-testid="hatch-chat-compose"]` ("New side chat") present |
### Page-title signals
| Title | Meaning |
|-------|---------|
| `Chat — ` | logged in, chat UI loaded |
| `Muse` | partial load or `/thread/new` stripped state — treat as not-ready, navigate to `/` first |
---
## Quick-reference selector list
```js
// Composer
'textarea[aria-label="Message"]'
'[data-testid="hatch-composer-placeholder-overlay"]' // empty-state ghost text
// Send / stop
'button[aria-label="Send"]' // via aria-label includes 'send'
'[data-testid="hatch-composer-stop-button"]' // while generating
// Messages
'#hatch-chat-scroll' // scroll container
'div[role="log"][aria-label="Chat messages"]' // list
'[data-message-id]' // each message
'[data-message-id^="assistant-msg"]' // assistant messages only
// Chat activation
'[data-testid="hatch-nav-chat"]' // dock-rail Chat link
'[data-testid="hatch-chat-typing-indicator"]' // generating indicator
```
## Caveats
- Selectors observed on the `opm` account; class names are Tailwind-ish and
may shift with deploys — prefer `data-testid`, `aria-label`, `role`, and
`id` selectors over classes.
- `aria-label` on `hatch-nav-chat` embeds a live notification count; match
with `startsWith('Chat')` or the testid, never exact equality.
- During this capture another agent was driving the same browser; page URL
flipped between `/` and `/thread/new` between probes. Element identities
above were each verified at least once against the live DOM.