Verify + extend DOM-SETTINGS/SEARCH/NOTIFICATIONS (2026-10-04, 3 nodes)

Session: sidechat/dom-settings
This commit is contained in:
dom-inspector-4
2026-10-04 04:31:11 +00:00
parent 7d0ccba64c
commit ef453ccb26
3 changed files with 164 additions and 54 deletions
+63 -13
View File
@@ -1,23 +1,30 @@
# DOM reference: notifications & activity (muse.ai) # DOM reference: notifications & activity (muse.ai)
Inspected live via CDP on the opm browser (warp-opm netns, port 9440), Inspected live via CDP on the opm browser (warp-opm netns, port 9440),
2026-10-04. Companion docs: `DOM-CHAT-PANEL.md`, `DOM-MESSAGES.md`, 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`. `DOM-PAGE-STRUCTURE.md`.
## TL;DR ## TL;DR
muse.ai has **no bell icon and no notification center/dropdown**. The muse.ai has **no bell icon and no notification center/dropdown**. The
notification surface is three separate things: notification surface is four separate things:
1. **Toast live region** — `section[aria-label="Notifications alt+T"]` 1. **Toast live region** — `section[aria-label="Notifications alt+T"]`
(transient toasts, aria-live). (transient toasts, aria-live).
2. **Activity tab** — `button[aria-label="Activity"]` in the status-panel 2. **Activity tab** — `button[aria-label="Activity"]` in the status-panel
toolbar (activity feed; tabs: Activity / Approvals / Upcoming / Identity). toolbar (activity feed; tabs: Activity / Approvals / Upcoming / Identity).
3. **Chat nav unread** — `a[data-testid="hatch-nav-chat"]` whose `aria-label` 3. **Chat nav unread** — `a[data-testid="hatch-nav-chat"]` whose `aria-label`
becomes `"Chat, N notification(s)"` when chats are unread. 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 are no badge/dot/pill unread elements in the DOM, and no element with There is no bell icon. The one `data-testid` containing `notif` is
`notif` in its `data-testid`. `hatch-dock-chat-notification-badge` (see §5).
--- ---
@@ -191,20 +198,55 @@ Do not match the element itself by aria-label — the count suffix changes.
## 5. What does NOT exist ## 5. What does NOT exist
Verified absent (three DOM sweeps, 2026-10-04): Verified absent (sweeps on all three nodes, 2026-10-04):
- No bell icon element. - No bell icon element (zero `aria-label` matches for /bell/i on every node).
- No `data-testid` containing `notif` (full inventory checked).
- No badge / dot / pill / count / unread elements anywhere in the DOM
(regex sweep over testids, class names, and aria-labels — zero hits).
- No notification dropdown, menu, or dialog container. - No notification dropdown, menu, or dialog container.
- No `[data-radix-portal]` / portal roots hosting hidden dropdowns. - No `[data-radix-portal]` / portal roots hosting hidden dropdowns.
- Clicking the Activity tab revealed no additional notification list UI - Clicking the Activity tab revealed no additional notification list UI
(the status panel was already open on the Activity feed). (the status panel was already open on the Activity feed).
Unread state is therefore conveyed **only** through: ## 5a. Unread dot badges — CORRECTION 2026-10-04
1. the `hatch-nav-chat` aria-label suffix, and
2. (presumably) unread styling on individual `[data-testid="hatch-thread-row"]` 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 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; during this inspection so row-level unread markers were not sampled;
re-probe with the panel open if row-level detection is needed. re-probe with the panel open if row-level detection is needed.
@@ -222,6 +264,12 @@ document.querySelector('section[aria-label="Notifications alt+T"]')
/notification/.test( /notification/.test(
document.querySelector('[data-testid="hatch-nav-chat"]').getAttribute('aria-label')); 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): // Open the Activity feed (select tab):
document.querySelector('button[aria-label="Activity"]').click(); document.querySelector('button[aria-label="Activity"]').click();
// Which status tab is selected? // Which status tab is selected?
@@ -240,3 +288,5 @@ document.querySelector('button[data-testid="hatch-status-panel-close"]').click()
- Whether tab buttons ever carry counts/badges when items are pending - Whether tab buttons ever carry counts/badges when items are pending
(Approvals tab is the likely candidate — re-probe with a pending approval). (Approvals tab is the likely candidate — re-probe with a pending approval).
- Row-level unread markers inside `hatch-thread-row` (needs panel-open probe). - 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).
+36 -5
View File
@@ -3,6 +3,7 @@
Muse.ai global search (quick-search / command palette). Inspected live on bl via CDP Muse.ai global search (quick-search / command palette). Inspected live on bl via CDP
against the opm browser (warp-opm netns, CDP 127.0.0.1:9440) on 2026-10-04. against the opm browser (warp-opm netns, CDP 127.0.0.1:9440) on 2026-10-04.
All selectors verified against the live DOM. All selectors verified against the live DOM.
Re-verified end-to-end 2026-10-04 (open → type → results → empty state → Escape).
Related: `docs/DOM-CHAT-PANEL.md` (chat panel), `docs/DOM-MESSAGES.md`, Related: `docs/DOM-CHAT-PANEL.md` (chat panel), `docs/DOM-MESSAGES.md`,
`docs/DOM-PAGE-STRUCTURE.md`. `docs/DOM-PAGE-STRUCTURE.md`.
@@ -153,14 +154,40 @@ document.querySelectorAll('[data-testid="hatch-quick-search-frame"] [data-testid
|---|---|---| |---|---|---|
| `chat:main` | Main chat | `chat:main` | | `chat:main` | Main chat | `chat:main` |
| `chat:thread:<uuid>` | Sidechat thread — **the thread UUID is embedded** | `chat:thread:2e2c90a1-4b17-4590-b8ba-fb17d8018d36` | | `chat:thread:<uuid>` | Sidechat thread — **the thread UUID is embedded** | `chat:thread:2e2c90a1-4b17-4590-b8ba-fb17d8018d36` |
| `chat:message:<msg-id>:<n>` | Individual message match inside a thread | `chat:message:assistant-msg-b2a4e27d-4970-8b1e-8863-a96ed0aa1401:20420` | | `chat:message:assistant-msg-<uuid>:<n>` | Individual assistant message match inside a thread | `chat:message:assistant-msg-b2a4e27d-4970-8b1e-8863-a96ed0aa1401:20420` |
| `chat:message:<uuid>:<n>` | Individual message match (non-assistant id form) | `chat:message:08a44cdd-91a7-408e-9073-743e7e15d50f:20890` |
Search matches **both threads and individual messages**. A query like `"heartbeat"` Search matches **both threads and individual messages**. A query like `"heartbeat"`
returned 12 results mixing `chat:thread:*` and `chat:message:*` values. returned 12 results on 2026-10-04 re-verify: 6 `chat:thread:*` + 6 `chat:message:*`,
mixing both message id forms.
This is a ready-made thread-lookup mechanism: search for a sidechat name, read This is a ready-made thread-lookup mechanism: search for a sidechat name, read
`data-value`, extract the UUID — no title-matching or URL polling needed. `data-value`, extract the UUID — no title-matching or URL polling needed.
### Full result-item markup (verified 2026-10-04)
```html
<div data-slot="command-item"
data-testid="command-palette-item"
id="radix-_r_1e9_" <!-- random per item per render — do not match -->
cmdk-item=""
role="option"
aria-disabled="false" aria-selected="true"
data-disabled="false" data-selected="true"
data-value="chat:thread:778fbbfa-3bf8-4163-bfe6-92c0f88657e8"
class="... h-12 items-center gap-2 p-1.5 ...">
<span class="bg-fill-secondary flex size-9 ...rounded-full">…svg icon…</span>
<span class="flex min-w-0">…title + snippet + relative time…</span>
</div>
```
Notes:
- The **first result is auto-highlighted** on render (`aria-selected="true"` /
`data-selected="true"`); the input's `aria-activedescendant` points at it.
Enter would activate it (navigation not tested).
- `data-value` is the stable automation hook; `id` is random per render.
--- ---
## 5. Groups ## 5. Groups
@@ -247,9 +274,13 @@ Detection: `querySelector('[cmdk-empty]')` non-null, or item count `=== 0`.
// CDP: Input.dispatchMouseEvent mousePressed + mouseReleased at (r.x + r.width/2, r.y + r.height/2) // CDP: Input.dispatchMouseEvent mousePressed + mouseReleased at (r.x + r.width/2, r.y + r.height/2)
``` ```
JS `.click()` on the trigger is unreliable (Radix wants trusted events). JS `.click()` on the trigger is unreliable (Radix wants trusted events).
2. **Never JS-touch the input.** `focus()`, `click()`, or the native value setter 2. **Type with CDP `Input.insertText`** (verified 2026-10-04 — simplest reliable
on `command-palette-search-input` closes the dialog. The input auto-focuses on path). The raw `rawKeyDown` → `char` → `keyUp` sequence also works but is
open — just send trusted key events (`rawKeyDown` → `char` → `keyUp`). unnecessary. **Never JS-touch the input** (`focus()`, `click()`, native value
setter): synthetic interaction on the input **closes the dialog** (verified
twice). One exception observed once: `input.select()` on the already-open
dialog did *not* close it — usable for clearing, but Escape-and-reopen
remains the safest clear.
3. **Closed === absent.** After Escape the dialog node is gone; check 3. **Closed === absent.** After Escape the dialog node is gone; check
`querySelector(...) === null`, with optional chaining (`?.offsetParent`) — `querySelector(...) === null`, with optional chaining (`?.offsetParent`) —
without `?.` a missing node throws and a truthy error string can false-positive without `?.` a missing node throws and a truthy error string can false-positive
+65 -36
View File
@@ -1,6 +1,7 @@
# DOM Reference: Settings, Profile & Dock Rail # DOM Reference: Settings, Profile & Dock Rail
Inspected live via CDP on the opm browser (warp-opm netns, CDP 9440), 2026-10-04. Inspected live via CDP on the opm browser (warp-opm netns, CDP 9440), 2026-10-04.
Re-verified 2026-10-04 across all three fleet nodes (muse 9410, pip 9420, opm 9440).
Structure only — no credential, token, or personal values are recorded here. Structure only — no credential, token, or personal values are recorded here.
Related: `DOM-CHAT-PANEL.md` (chat panel), `DOM-MESSAGES.md` (messages), Related: `DOM-CHAT-PANEL.md` (chat panel), `DOM-MESSAGES.md` (messages),
@@ -18,12 +19,24 @@ The left vertical nav rail. Container:
### Rail items (top to bottom) ### Rail items (top to bottom)
| # | Selector | Tag | `aria-label` | Notes | | # | Selector | Tag | `aria-label` | `data-pel-click` | Notes |
|---|----------|-----|--------------|-------| |---|----------|-----|--------------|------------------|-------|
| 1 | `[data-testid="hatch-nav-chat"]` | `<a href="/">` | `Chat` | The chat/message button from the user's screenshot. Navigates `/thread/new` → `https://muse.ai/`. See "chat activation" below. | | 1 | `[data-testid="hatch-nav-chat"]` | `<a href="/">` | `Chat` (unstable — see notifications doc) | `chat_tab_click` | The chat/message button from the user's screenshot. Navigates `/thread/new` → `https://muse.ai/`. See "chat activation" below. |
| 2 | `[data-testid="hatch-nav-search"]` | `<button>` | `Search` | Opens quick-search dialog `[data-testid="hatch-quick-search-frame"]` (heading "Search Muse"). | | 2 | `[data-testid="hatch-nav-search"]` | `<button>` | `Search` | `quick_search_nav_click` | Opens quick-search dialog `[data-testid="hatch-quick-search-frame"]` (heading "Search Muse"). |
| 3 | *(none)* | `<button>` | `Get the Muse app` | App download upsell. No `data-testid`. | | 3 | *(none)* | `<button>` | `Get the Muse app` | `mobile_app_upsell_click` | App download upsell. No `data-testid`. |
| 4 | `[data-testid="hatch-dock-more"]` | `<button>` | `Settings` | Opens the settings menu (radix menu). Bottom of rail (`div.mt-auto`). | | 4 | `[data-testid="hatch-dock-more"]` | `<button>` | `Settings` | `settings_menu_open_click` | Opens the settings menu (radix menu). Bottom of rail (`div.mt-auto`). |
Mid-rail system items (verified 2026-10-04, all three nodes) — `DIV`s, not
buttons, between the search button and the app upsell:
| `data-testid` | `aria-label` |
|---|---|
| `hatch-nav-system-item-66656564` | `Feed` |
| `hatch-nav-system-item-6964656173` | `Ideas` |
| `hatch-nav-system-item-676f616c73` | `Goals` |
| `hatch-nav-system-item-6c696272617279` | `Library` |
(The hex suffixes decode to ascii: `feed`, `ideas`, `goals`, `library`.)
Parent chain of the settings button (4 levels): Parent chain of the settings button (4 levels):
@@ -41,14 +54,16 @@ Key attributes on the button:
aria-label="Settings" aria-label="Settings"
data-testid="hatch-dock-more" data-testid="hatch-dock-more"
aria-haspopup="menu" aria-haspopup="menu"
aria-expanded="false" <!-- unreliable: stays "false" even when menu is open --> aria-expanded="false" <!-- RELIABLE after 2026-10-04 re-verify: "true" when menu open -->
data-state="closed" <!-- unreliable: stays "closed" even when menu is open --> data-state="closed" <!-- unreliable: stays "closed" even when menu is open -->
data-pel-click="settings_menu_open_click"> data-pel-click="settings_menu_open_click">
``` ```
> **Automation note:** `data-state` / `aria-expanded` do NOT reflect the real > **Automation note (updated 2026-10-04):** `data-state` does NOT reflect the
> open state. Detect the open menu by counting `[role="menuitem"]` elements > real open state (stays `"closed"`). `aria-expanded`, however, **does**:
> (`> 0` means open). Synthetic `.click()` does not open the radix menu; > verified `"true"` on opm with the menu open (earlier claim that it stays
> `"false"` was wrong — possibly a timing artifact). Belt and braces: detect
> the open menu by counting `[role="menuitem"]` elements (`> 0` means open). Synthetic `.click()` does not open the radix menu;
> use real mouse events via CDP `Input.dispatchMouseEvent` > use real mouse events via CDP `Input.dispatchMouseEvent`
> (`mousePressed` + `mouseReleased` at the button center). The button toggles, > (`mousePressed` + `mouseReleased` at the button center). The button toggles,
> so click only when `[role="menuitem"]` count is 0. > so click only when `[role="menuitem"]` count is 0.
@@ -66,11 +81,12 @@ always match on the `data-testid`.
## 2. Settings menu (dock "more" menu) ## 2. Settings menu (dock "more" menu)
Radix menu rendered in a popper. Items (`role="menuitem"`, no `data-testid`): Radix menu rendered in a popper. Items are **`DIV`s with `role="menuitem"`**
(verified 2026-10-04 — not buttons), no `data-testid`:
| Text | `data-pel-click` | Action (observed) | | Text | `data-pel-click` | Action (observed) |
|------|------------------|-------------------| |------|------------------|-------------------|
| Keyboard shortcuts | `keyboard_shortcuts_nav_click` | No dialog rendered under automation (see §5) | | Keyboard shortcuts | `keyboard_shortcuts_nav_click` | No dialog under automation (see §5) |
| Download apps | `mobile_app_upsell_menu_click` | — (not opened) | | Download apps | `mobile_app_upsell_menu_click` | — (not opened) |
| Report an issue | `bug_report_nav_click` | — (not opened) | | Report an issue | `bug_report_nav_click` | — (not opened) |
| Settings | `settings_nav_click` | Opens the Settings dialog (§3) | | Settings | `settings_nav_click` | Opens the Settings dialog (§3) |
@@ -124,38 +140,49 @@ Close
### 3b. General section ### 3b. General section
Headings (all `h2`): `General`, `Usage`, `Appearance`. Headings (all `h2`, in order): `General`, `Meta Account`, `Usage`, `Appearance`.
**Usage** — one button: **Meta Account** — row labeled `Password, security, personal details`
(structure only; not opened).
| Control | Label | Notes | **Usage** — plan and quota rows (structure only, values not recorded):
|---------|-------|-------|
| `<button>` | `Upgrade` | Plan upgrade upsell | | Row | Notes |
|-----|-------|
| Plan name + weekly reset line | e.g. plan tier + "Weekly limit resets on <date>" |
| Usage bar(s) with percentage | at least two bars observed (weekly + additional) |
| `Upgrade` button | `data-pel-click="settings_general_upgrade_click"` |
| `Language` | control follows the usage block |
**Appearance** — theme + accent color: **Appearance** — theme + accent color:
| Control | Label | | Control | Label source | Selection state |
|---------|-------| |---------|--------------|-----------------|
| `<button>` | `Light` | | `<button aria-label="Light">` | `aria-label` (icon-only, no text) | `aria-pressed` |
| `<button>` | `Dark` | | `<button aria-label="Dark">` | `aria-label` | `aria-pressed` |
| `<button>` | `System` | | `<button aria-label="System">` | `aria-label` | `aria-pressed="true"` on opm (verified) |
| `<input type="radio">` | `Match my avatar` | | 9× `<input type="radio">` | **`aria-label`**, not innerText | `checked` |
| `<input type="radio">` | `Default` |
| `<input type="radio">` | `Blue` | Radio `aria-label`s in order: `Match my avatar` (checked on opm), `Default`,
| `<input type="radio">` | `Purple` | `Blue`, `Purple`, `Pink`, `Orange`, `Green`, `Beige`, `Monochrome`.
| `<input type="radio">` | `Pink` | All share one radio group (`name` is random per render, e.g. `_r_184_`).
| `<input type="radio">` | `Orange` |
| `<input type="radio">` | `Green` |
| `<input type="radio">` | `Beige` |
| `<input type="radio">` | `Monochrome` |
### 3c. Profile / avatar ### 3c. Profile / avatar
- Avatar image: `<img>` with empty `alt`, `src` present (value not recorded). - Avatar image: `<img>` with empty `alt`, `src` present (value not recorded).
No `data-testid` on the img. No `data-testid` on the img.
- `Edit avatar and name` button (`aria-label="Edit avatar and name"`, - `Edit avatar and name` button (`aria-label="Edit avatar and name"`,
`<button>`, no `data-testid`) appears in the profile area of the settings `<button>`, no `data-testid`, id is a random `radix-_R_…` value).
dialog. Chain (3 levels):
> **Correction 2026-10-04:** the button is **not a descendant of
> `div[role="dialog"]`**. While the settings dialog is open it renders as a
> sibling overlay inside `[data-testid="hatch-status-panel-sliding-surface"]`
> (nearest testid ancestor at depth 10; button rect ≈ (622,141), 32×32 on a
> 780px-wide viewport). It appears only while the dialog is open and is gone
> after Escape. **Match it by `aria-label`, never by dialog containment.**
> The 3-level parent chain below is still accurate:
```
``` ```
div.pointer-events-auto.absolute.z-20 div.pointer-events-auto.absolute.z-20
@@ -195,9 +222,11 @@ Menu item exists:
Clicking it via automation (menuitem `.click()` after opening the dock menu Clicking it via automation (menuitem `.click()` after opening the dock menu
with real mouse events) produced **no `[role="dialog"]`** within 3s and no with real mouse events) produced **no `[role="dialog"]`** within 3s and no
URL change. The modal likely requires a trusted user gesture deeper in the URL change. Re-tested 2026-10-04 with **real mouse events**
radix event chain, or renders on a longer delay. **Status: selector documented, (`Input.dispatchMouseEvent` press+release on the menuitem): still no dialog
modal content not captured** — needs a headed/manual pass or longer wait. within 3s; the menu simply closes. The menuitem carries no kbd hint in its
markup (icon SVG + text "Keyboard shortcuts" only).
**Status: selector documented, modal content not captured** — needs a headed/manual pass.
--- ---