Files
box/docs/DOM-NOTIFICATIONS.md

295 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DOM reference: notifications & activity (muse.ai)
> **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.
Inspected live via CDP on the opm browser (warp-opm netns, port 9440),
2026-10-04. Re-verified 2026-10-04 across all three fleet nodes
(muse 9410, pip 9420, opm 9440).
Companion docs: `DOM-CHAT-PANEL.md`, `DOM-MESSAGES.md`,
`DOM-PAGE-STRUCTURE.md`.
## TL;DR
muse.ai has **no bell icon and no notification center/dropdown**. The
notification surface is four separate things:
1. **Toast live region** — `section[aria-label="Notifications alt+T"]`
(transient toasts, aria-live).
2. **Activity tab** — `button[aria-label="Activity"]` in the status-panel
toolbar (activity feed; tabs: Activity / Approvals / Upcoming / Identity).
3. **Chat nav unread** — `a[data-testid="hatch-nav-chat"]` whose `aria-label`
becomes `"Chat, N notification(s)"` when chats are unread (observed live on
pip: `"Chat, 1 notification"`, later cleared to `"Chat"`).
4. **Unread dot badges** — transient blue-dot `SPAN`s (see §5). **Correction
2026-10-04:** these DO exist; the earlier "no badge elements" claim was
wrong — the first sweep ran on opm while it was clean, and the badges only
render while there is unread activity.
There is no bell icon. The one `data-testid` containing `notif` is
`hatch-dock-chat-notification-badge` (see §5).
---
## 1. Toast live region
The only element carrying the word "notification" in an aria-label.
```js
document.querySelector('section[aria-label="Notifications alt+T"]')
```
| Property | Value |
|---|---|
| Tag | `<section>` |
| `aria-label` | `Notifications alt+T` (stable — safe to match on) |
| `tabindex` | `-1` |
| `aria-live` | `polite` |
| `aria-relevant` | `additions text` |
| `aria-atomic` | `false` |
| `data-testid` | none |
| Parent chain | direct child of `<body>` (no intermediate wrappers) |
| Content when idle | empty — `childCount: 0`, `innerHTML === ""` |
Semantics: an aria-live toast container. Toasts are injected as children and
announced politely. `Alt+T` presumably moves focus to it (unverified —
shortcut named in the label).
**Visibility condition:** always in the DOM (even when empty). Presence of
children = a toast is/was showing.
**Automation note:** to detect a toast, poll
`document.querySelector('section[aria-label="Notifications alt+T"]').childElementCount > 0`
or read `innerText`.
---
## 2. Activity tab button
```js
document.querySelector('button[aria-label="Activity"]')
```
| Property | Value |
|---|---|
| Tag | `<button type="button">` |
| `aria-label` | `Activity` (stable — safe to match on) |
| `aria-pressed` | `"true"` / `"false"` — reflects selected tab |
| `data-state` | `"closed"` (tooltip/popover state, not panel state) |
| `data-slot` | `tooltip-trigger` |
| `data-testid` | none |
| Children | single `<span aria-hidden="true">` wrapping an SVG icon (no text) |
Parent chain (bottom-up, 8 levels, coords at 780×493 viewport):
```
div @(420,0)
div[data-testid="hatch-status-panel-sliding-surface"] @(420,0)
div @(420,0)
div @(421,0)
div @(421,274)
div @(437,274)
div @(437,274)
button[aria-label="Activity"] @(441,278)
```
The button lives in the **status-panel toolbar**, not the dock rail. It is a
tab toggle: clicking selects the Activity feed inside the status panel.
### 2a. Sibling tabs (same toolbar)
All icon-only `<button>`s, same structure (span + SVG, no text):
| aria-label | Observed state |
|---|---|
| `Activity` | `aria-pressed="true"` (selected during inspection) |
| `Approvals` | `aria-pressed="false"` |
| `Upcoming` | present (state not sampled) |
| `Identity` | present (state not sampled) |
Selector pattern for any tab:
```js
document.querySelector('button[aria-label="Approvals"]') // etc.
```
**Automation note:** no badge/count was observed on any tab (nothing pending
at inspection time). If counts ever appear they would likely be SVG-adjacent
spans or aria-label suffixes — re-probe when approvals are pending.
---
## 3. Status panel (sliding surface)
```js
document.querySelector('[data-testid="hatch-status-panel-sliding-surface"]')
```
| Property | Value |
|---|---|
| Geometry | 360px wide, full height, at x≈420 (right of the chat column) |
| Visibility | always in DOM; `visible: true` when the panel is open |
Internal structure:
```
[data-testid="hatch-status-panel-sliding-surface"]
├── div
│ ├── div[data-testid="hatch-status-panel-close-drag-handle"]
│ └── span[data-testid="hatch-status-panel-close-drag-handle-divider-line"]
├── div ← tab content ("operator-main", "Today" when Activity selected)
│ ├── div[data-testid="hatch-status-panel-close-toolbar"]
│ │ └── span
│ │ └── button[data-testid="hatch-status-panel-close"][aria-label="Close panel"]
```
Close button:
```js
document.querySelector('button[data-testid="hatch-status-panel-close"]')
// aria-label="Close panel"
```
**Automation note:** the tab toolbar (Activity/Approvals/Upcoming/Identity)
sits between the drag handle and the content area. Tab selection is readable
via `aria-pressed`.
---
## 4. Chat nav unread indicator
The dock-rail chat link doubles as the unread-chats indicator:
```js
document.querySelector('[data-testid="hatch-nav-chat"]')
```
| Property | Value |
|---|---|
| Tag | `<a href="/">` (a link, not a button) |
| `data-testid` | `hatch-nav-chat` (stable — **match on this, not aria-label**) |
| `aria-label` | **unstable**: `"Chat"` when clean, `"Chat, 1 notification"` (etc.) when chats are unread |
| `aria-keyshortcuts` | `Control+J` |
| `aria-current` | `"page"` when the chat view is active |
| `data-pel-click` | `chat_tab_click` |
| `data-hatch-shell-navigation` | `"true"` |
Dock rail context (`[data-testid="hatch-dock-rail"]` children):
```
a[data-testid="hatch-nav-chat"][aria-label="Chat"] ← unread shows here
button[data-testid="hatch-nav-search"][aria-label="Search"]
button[aria-label="Get the Muse app"]
span[data-testid="hatch-dock-rail-divider"]
button[data-testid="hatch-dock-more"][aria-label="Settings"]
```
**Visibility condition:** always in the DOM on chat pages (also present in the
stripped `/thread/new` state — it is the recovery click target there).
**Automation note — detecting unread chats:**
```js
const label = document.querySelector('[data-testid="hatch-nav-chat"]')
.getAttribute('aria-label');
const unread = /notification/.test(label); // "Chat, N notification(s)"
```
Do not match the element itself by aria-label — the count suffix changes.
---
## 5. What does NOT exist
Verified absent (sweeps on all three nodes, 2026-10-04):
- No bell icon element (zero `aria-label` matches for /bell/i on every node).
- No notification dropdown, menu, or dialog container.
- No `[data-radix-portal]` / portal roots hosting hidden dropdowns.
- Clicking the Activity tab revealed no additional notification list UI
(the status panel was already open on the Activity feed).
## 5a. Unread dot badges — CORRECTION 2026-10-04
The earlier claim ("no badge / dot / pill elements anywhere") is **wrong**.
Three badge testids exist; they render only while there is unread activity and
are absent when clean (which is why the first sweep on opm missed them):
| `data-testid` | Observed on | Markup |
|---|---|---|
| `hatch-dock-chat-notification-badge` | pip (while `"Chat, 1 notification"`) | blue dot |
| `hatch-chat-switcher-unread-indicator` | muse | `<span class="bg-fill-blue absolute -end-0.5 size-2 rounded-full">` — 8×8 blue dot, no text, no `aria-label` |
| `hatch-invite-activity-badge` | muse, pip | `<span class="bg-fill-blue absolute -end-0.5 -top-0.5 size-2 rounded-full">` — 8×8 blue dot |
All are empty `SPAN`s (`innerText === ""`, no `aria-label`, no `role`) —
pure CSS dots (`bg-fill-blue`, `rounded-full`, `size-2`). **They are transient**:
pip's `hatch-dock-chat-notification-badge` was present during one sweep and gone
minutes later after the unread cleared. Never match on class names (Tailwind);
match on `data-testid` presence:
```js
// Any unread-dot badges currently rendered?
const unreadDots = [
'hatch-dock-chat-notification-badge',
'hatch-chat-switcher-unread-indicator',
'hatch-invite-activity-badge',
].filter(tid => document.querySelector(`[data-testid="${tid}"]`));
```
Notes:
- `hatch-invite-activity-badge` was present on muse and pip even when the chat
nav label was clean (`"Chat"`), so it likely signals invites/activity rather
than chat unread — treat it as its own signal, not as "unread chats".
- `hatch-chat-switcher-unread-indicator` sits on the chat switcher (rect ≈
(110,20), 8×8 on muse) — useful when the panel is open and the nav label
is out of view.
Unread state is therefore conveyed through:
1. the `hatch-nav-chat` aria-label suffix (`"Chat, N notification(s)"`),
2. the transient dot badges above (`data-testid` presence),
3. (presumably) unread styling on individual `[data-testid="hatch-thread-row"]`
rows in the chat panel — see `DOM-CHAT-PANEL.md`. The panel was closed
during this inspection so row-level unread markers were not sampled;
re-probe with the panel open if row-level detection is needed.
---
## 6. Automation cheat sheet
```js
// Is there a toast showing?
document.querySelector('section[aria-label="Notifications alt+T"]')
.childElementCount > 0;
// Any unread chats?
/notification/.test(
document.querySelector('[data-testid="hatch-nav-chat"]').getAttribute('aria-label'));
// Any unread dots currently rendered? (transient — absent when clean)
['hatch-dock-chat-notification-badge',
'hatch-chat-switcher-unread-indicator',
'hatch-invite-activity-badge'
].filter(tid => document.querySelector(`[data-testid="${tid}"]`));
// Open the Activity feed (select tab):
document.querySelector('button[aria-label="Activity"]').click();
// Which status tab is selected?
document.querySelector('button[aria-label="Activity"]').getAttribute('aria-pressed'); // "true"/"false"
// Close the status panel:
document.querySelector('button[data-testid="hatch-status-panel-close"]').click();
// Focus the toast region (keyboard):
// Alt+T (named in the section's aria-label; behavior unverified)
```
## Open questions
- Toast content structure (no toast was firing during inspection).
- Whether tab buttons ever carry counts/badges when items are pending
(Approvals tab is the likely candidate — re-probe with a pending approval).
- Row-level unread markers inside `hatch-thread-row` (needs panel-open probe).
- What clears `hatch-dock-chat-notification-badge` vs the aria-label suffix
(observed independently transient on pip/muse — may be separate signals).