11 KiB
DOM reference: notifications & activity (muse.ai)
Box is the main surface. All operator work goes through Box (box.muse-dev.online). The web UI,
boxCLI, 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:
- Toast live region —
section[aria-label="Notifications alt+T"](transient toasts, aria-live). - Activity tab —
button[aria-label="Activity"]in the status-panel toolbar (activity feed; tabs: Activity / Approvals / Upcoming / Identity). - Chat nav unread —
a[data-testid="hatch-nav-chat"]whosearia-labelbecomes"Chat, N notification(s)"when chats are unread (observed live on pip:"Chat, 1 notification", later cleared to"Chat"). - Unread dot badges — transient blue-dot
SPANs (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.
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
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:
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)
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:
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:
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:
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-labelmatches 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 SPANs (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:
// 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-badgewas 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-indicatorsits 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:
- the
hatch-nav-chataria-label suffix ("Chat, N notification(s)"), - the transient dot badges above (
data-testidpresence), - (presumably) unread styling on individual
[data-testid="hatch-thread-row"]rows in the chat panel — seeDOM-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
// 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-badgevs the aria-label suffix (observed independently transient on pip/muse — may be separate signals).