Files
box/docs/DOM-NOTIFICATIONS.md
T

11 KiB
Raw Blame History

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 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-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 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-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

// 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).