Red commit: PENDING (will update)
Fixes#1173.
Replaces the `#liveDot` WebSocket-connected indicator with a
packet-driven node-pulse animation on the brand logo's two inner
circles.
## Behavior (locked per issue spec)
- **Animation curve:** `ease-out` (default per open-question 1).
- **Rate cap:** 15/sec (66ms gap; default per open-question 2). Excess
triggers are dropped, never queued.
- **Direction:** alternates A→B / B→A across messages (aesthetic, not
semantic).
- **Idle ≥10s:** logo at full brightness, no animation.
- **Disconnected:** `.logo-disconnected` applies `filter: grayscale(0.6)
opacity(0.7)`.
- **`prefers-reduced-motion: reduce`:** single-step `.logo-pulse-blip`
on destination only.
## Implementation
- WS handler hook lives in `public/app.js` `connectWS()` (`ws.onmessage`
triggers `Logo.pulse()`; `ws.onopen`/`ws.onclose` toggle
`Logo.setConnected()`).
- `Logo` is a small IIFE in `app.js` that exposes
`window.__corescopeLogo` for E2E injection.
- All animation is pure CSS; JS only toggles `.logo-pulse-active` /
`.logo-pulse-blip` / `.logo-disconnected`. Colors come exclusively from
`--logo-accent` / `--logo-accent-hi` tokens.
- Two new classes (`.logo-node-a`, `.logo-node-b`) attached to inner
circles in both `.brand-logo` and `.brand-mark-only` SVGs so the mobile
mark animates too.
## `#liveDot` removal proof
```
$ grep -rn liveDot public/
(no output)
```
## E2E
- E2E assertion added: `test-logo-pulse-1173-e2e.js:54` and follows.
- Wired into the Playwright matrix in `.github/workflows/deploy.yml`
(mirrors PR #1168 pattern from commit `5442652`).
- Test injects synthetic pings via `window.__corescopeLogo.pulse({
synthetic: true })`; matches the existing harness style (no new WS-mock
pattern invented).
Red→green discipline preserved: the test commit lands first and CI fails
on assertion; the implementation commit follows.
---------
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot>
Co-authored-by: corescope-bot <bot@corescope.local>
Red commit: 61fcc8c19b (CI run: pending —
see Checks tab on this PR)
Fixes#1178Fixes#1179
## Summary
Live page layout polish — both issues touch `public/live.css` + a
small `public/live.js` slice, so they ship as one PR per AGENTS rule
34.
### #1178 — Header compactness + narrow-viewport collapse
- `.live-header` total height ≤ 40px at desktop widths (smaller
padding, gap, title font, and pill sizing; `max-height: 40px` as a
belt-and-suspenders gate).
- Body wrapped in `.live-header-body` so it can collapse cleanly.
- New 32×32 toggle button `[data-live-header-toggle]`, hidden at
wide viewports, visible at `≤768px`.
### #1179 — Controls pinned bottom-right + narrow-viewport collapse
- New `.live-controls` cluster around the toggles list and audio
controls, `position: fixed; right: 12px;` and
`bottom: calc(78px + var(--bottom-nav-height, 56px) +
env(safe-area-inset-bottom, 0px))`.
- That bottom calc reserves space for the VCR bar **and** the bottom
nav (#1061, currently in PR #1174). When the bottom-nav exposes
`--bottom-nav-height` the cluster tracks it; otherwise the 56px
fallback keeps it clear regardless of merge order.
- `z-index: 1000` keeps it above map markers but below modals.
- New 32×32 toggle button `[data-live-controls-toggle]`, hidden at
wide viewports, visible at `≤768px`.
### Breakpoint + selectors
- Narrow = `max-width: 768px` (matches #1061 bottom-nav activation).
- Stable selectors for E2E: `[data-live-header-toggle]`,
`[data-live-header-body]`, `[data-live-controls-toggle]`,
`[data-live-controls-body]`. No DOM-order dependence.
### Bottom-nav coexistence
The expanded narrow-viewport controls panel uses
`max-height: 50vh; overflow-y: auto` on its toggles list, and the
cluster's `bottom` reservation guarantees the panel's bottom edge
sits above the (possibly absent) bottom-nav region. The E2E test
asserts exactly this with `expandedRect.bottom + 8 < innerHeight −
navH`,
defaulting `navH` to 56 if `.bottom-nav` is not in the DOM yet.
### Theming
All new colors via existing CSS tokens (`--surface-1`, `--text`,
`--text-muted`, `--border`, `--accent`). check-css-vars passes.
### TDD
- Red commit: `61fcc8c` — assertions only (no impl), wired into
`.github/workflows/deploy.yml` Playwright matrix.
- Green commit: `7d591be` — DOM split + CSS + collapse JS.
- E2E assertion added: `test-live-layout-1178-1179-e2e.js:55`
(desktop header height) through `:170` (narrow controls
bottom-nav coexistence).
### Local verification
```
./corescope-server -port 13581 -db test-fixtures/e2e-fixture.db &
CHROMIUM_PATH=/usr/bin/chromium BASE_URL=http://localhost:13581 \
node test-live-layout-1178-1179-e2e.js
# → 8/8 passed
```
---------
Co-authored-by: meshcore-bot <bot@meshcore.local>
Co-authored-by: openclaw-bot <bot@openclaw.local>
Red commit: 0f29da3 (CI is pending — will be linked once dispatched)
Fixes#1058
This PR is in **red phase**. The new E2E asserts the desired
fluid + auto-stacking behavior; with `master`'s code it FAILS at
≥768px (cards don't stack). Green commit follows.
E2E assertion added: `test-charts-fluid-1058-e2e.js:99`.
---------
Co-authored-by: OpenClaw Bot <bot@openclaw.local>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot>
Red commit: e964ec9c46 (CI run: pending —
workflow only triggers on PR open)
Partial fix for #1120 — finishes the four follow-up items left open
after PR #1123 (cancelled writes, ingestor I/O, threshold-flag tests,
docs).
## What's done
- **`cancelledWriteBytesPerSec`** — server `/proc/self/io` parser
handles `cancelled_write_bytes`; `/api/perf/io` exposes the per-second
rate; Perf page renders it next to Read/Write with ⚠️ when sustained >1
MB/s.
- **Ingestor `/proc/<pid>/io`** — `cmd/ingestor/stats_file.go` samples
its own `/proc/self/io` each tick and includes `procIO` in the snapshot.
The server's `/api/perf/io` reads it and surfaces `.ingestor`. Frontend
renders an `Ingestor process` Disk I/O block alongside the existing
`server process` block (issue mockup: "Both ingestor and server").
- **Threshold + anomaly tests** — `test-perf-disk-io-1120.js` now
asserts ⚠️ fires/suppresses on WAL>100MB, cache_hit<90%, and the
backfill-rate-vs-tx-rate guard with the `tx_inserted >= 100` baseline
floor. Drops the tautological `|| ... === false` short-circuits flagged
in MINOR m4.
- **Docs (m8)** — `config.example.json` adds `_comment_ingestorStats`
(env var, default path, shared-tmp security note);
`cmd/ingestor/README.md` adds `CORESCOPE_INGESTOR_STATS` to the env-var
table plus a `Stats file` section.
## What's NOT done (deferred)
m1 sync.Map → map+RWMutex, m2 perfIOMu rate caching, m3 negative
cacheSize translation, m5 deterministic-write test, m7 ctx-aware
shutdown — pure polish; will file a follow-up issue if the operator
wants them tracked.
## TDD
- Red: `e964ec9` — adds failing tests + stub field/handler shape
(cancelled missing from struct, ingestor stub returns nil, ingestor
procIO absent).
- Green: `1240703` — wires up the parser case, ingestor sampler,
frontend rendering, docs.
E2E assertion added: test-perf-disk-io-1120.js:108
---------
Co-authored-by: clawbot <clawbot@users.noreply.github.com>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot>
Red commit: 8ac568bac3 (CI run: pending)
## Summary
Implements AC #4 of #1056: row-detail **slide-over panel** at narrow
viewports for the Packets, Nodes, and Observers tables.
ACs #1–#3, #5 already shipped in #1099; this PR closes the remaining
criterion.
## Approach
- Shared `window.SlideOver` helper (`packets.js`, top of file next to
`TableResponsive`) — singleton overlay (`.slide-over-backdrop` +
`.slide-over-panel`) injected into `<body>`. Close affordances: X button
(`.slide-over-close`), backdrop click, Escape key. `aria-modal="true"`,
focus moved to close button on open.
- Breakpoint: `window.innerWidth <= 1023` (matches the
`data-priority="3"` threshold reused by `TableResponsive`). At `>=1024`
the existing right-side panel / full-screen behavior is preserved — no
regression.
- Each page (`packets.js`, `nodes.js`, `observers.js`) checks the
breakpoint at row-click time and routes the same detail content into
`SlideOver.open(node)` instead of the side panel / full-screen
navigation.
- Reuses the existing `slideInRight` keyframe in `style.css`.
- CSS additions live in the table section of `style.css` only.
## E2E
`test-slideover-1056-e2e.js` — at 800x800 clicks the first row of each
of the three tables, asserts `.slide-over-panel` +
`.slide-over-backdrop` are visible and the close X exists; verifies
Escape, backdrop click, and X click all dismiss; verifies that at 1440
the slide-over does NOT appear.
E2E assertion added: `test-slideover-1056-e2e.js:71`
## TDD
- Red commit: `8ac568b` — E2E asserts on `.slide-over-panel` which does
not exist yet.
- Green commit: forthcoming in this PR.
Fixes#1056
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local>
Co-authored-by: corescope-bot <bot@corescope.local>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot>
# Fix#1109 — mobile hamburger dropdown clipped invisible by `.top-nav {
overflow:hidden }`
Red commit: `5429b0f` (failing E2E, asserts pixel-level visibility).
## Symptom
On <768px viewports, tapping `#hamburger` toggles `.nav-links.open` and
`body.nav-open` correctly — DOM state is right, `aria-expanded="true"`,
computed `display:flex` — but **nothing appears below the navbar**. The
dropdown is laid out at `y=52..626` but visually clipped.
## Root cause
`.top-nav` is `position:sticky; height:52px; overflow:hidden` (added in
#1066 fluid scaffolding at `417b460` to guard against horizontal
overflow during the Priority+ measurement pass). At <768px the dropdown
becomes `position:absolute; top:52px`, so its containing block is
`.top-nav` — and `.top-nav`'s `overflow:hidden` clips everything below
`y=52`. Result: the dropdown renders inside a 52px box and the user sees
nothing.
Full RCA + screenshots:
https://github.com/Kpa-clawbot/CoreScope/issues/1109#issuecomment-4398900387
## Fix
In `public/style.css`, inside `@media (max-width: 767px)`, change
`.nav-links` from `position:absolute` to `position:fixed`.
`position:fixed` escapes any `overflow:hidden` ancestor (its containing
block becomes the viewport), so the dropdown is no longer clipped. All
other rules (display/flex/background/padding/z-index) keep working.
This deliberately does **not** relax `overflow:hidden` on `.top-nav` —
that would reopen the #1066 horizontal-overflow regression on desktop.
## Why prior tests missed this
Existing nav E2Es asserted `.classList.contains('open')` /
`getComputedStyle().display === 'flex'` — pure DOM state. Those passed
even while the dropdown was clipped invisibly. The new test in this PR
asserts **pixel-level visibility**:
`document.elementFromPoint(viewportWidth/2, 100)` must land on something
inside `.nav-links` (not `<body>`), and the first `.nav-link`'s bounding
rect must satisfy `bottom > 60` and have non-zero area. A state-only fix
can never satisfy this.
E2E assertion added:
`test-issue-1109-hamburger-dropdown-visible-e2e.js:113` (the
`hitInsideNavLinks` check).
## Files changed
- `public/style.css` — one line in the mobile media query: `position:
absolute` → `position: fixed`
- `test-issue-1109-hamburger-dropdown-visible-e2e.js` — new E2E
- `.github/workflows/deploy.yml` — wire the new E2E into the suite
Fixes#1109
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
Fixes#1151
## Problem
The side-panel "Heard By" row template in `public/nodes.js` (line 1337)
built its stats suffix with inline ternaries:
```js
${o.packetCount} pkts · ${o.avgSnr != null ? '...' : ''}${o.avgRssi != null ? ' · RSSI ...' : ''}
```
When `avgSnr` and/or `avgRssi` were `null` (very common in prod —
many CJS observers have both null), this produced orphan separators:
- both null → `"110 pkts · "` (trailing dot)
- snr null only → `"55 pkts · · RSSI -50"` (double dot)
## Fix
Build a filtered parts array, then `.join(' · ')`. Only present fields
contribute, so the separator can never appear next to nothing.
```js
const stats = [`${o.packetCount} pkts`];
if (o.avgSnr != null) stats.push('SNR ' + Number(o.avgSnr).toFixed(1) + 'dB');
if (o.avgRssi != null) stats.push('RSSI ' + Number(o.avgRssi).toFixed(0));
// → stats.join(' · ')
```
Full-page table (line 1337's neighbor) was already null-safe (separate
`<td>` cells), so only the side-panel template needed the change.
## TDD
Red commit: `1c02ff9a7889aadd16f87f4e673287f9742d4ad0` — adds
`test-issue-1151-orphan-separators-e2e.js` to the deploy.yml E2E job.
The test stubs `/api/nodes/:pubkey/health` via Playwright `page.route()`
with four observer permutations (both null, snr-only-null,
rssi-only-null,
both set), opens the side panel, and asserts no `.observer-row` stat
suffix matches `· ·`, leading `·`, or trailing `·`.
E2E assertion added: `test-issue-1151-orphan-separators-e2e.js:96`
## Preflight
All hard gates pass — see preflight output in the implementation log.
---------
Co-authored-by: CoreScope Bot <bot@corescope>
## Summary
Restores sage/teal as default logo colors while preserving customizer
theming. Closes the gap from #1157 (closed) and the user's complaint
about lost two-tone.
Out-of-the-box, the navbar + hero CORE/SCOPE wordmarks now render the
brand-identity duotone — `#cfd9c9` (sage / fog) and `#2c8c8c` (teal /
water). When an operator picks a theme via the customizer (or sets a
custom accent color), the wordmark recolors to follow.
## Approach (Option C — decoupled defaults + customizer mirror)
- **`public/style.css` `:root`** — set `--logo-accent: #cfd9c9` and
`--logo-accent-hi: #2c8c8c` as literal defaults. Removes the previous
`var(--accent)` cascade so blue-by-default no longer leaks into the
brand mark.
- **`public/customize-v2.js`** — `applyTheme()`, the early-apply path,
and the live color-picker `input` handler now mirror
`themeSection.accent` → `--logo-accent` and `themeSection.accentHover` →
`--logo-accent-hi`.
- **`public/customize.js`** (legacy) — same mirroring in
`applyThemePreview()` and the early localStorage replay.
- **`.github/workflows/deploy.yml`** — adds the new e2e to the Chromium
batch.
This preserves `--accent` as the canonical app-wide accent token (no
other UI changes) while giving the logo its own brand-defaulted tokens
that the customizer still drives.
## Tests
Red → green commit pair on the branch.
- **NEW: `test-logo-default-sage-teal-e2e.js`** — gates both halves of
the contract:
1. Clean localStorage → navbar + hero CORE = `rgb(207, 217, 201)`, SCOPE
= `rgb(44, 140, 140)`.
2. Seeded `cs-theme-overrides` with red accent → navbar + hero recolor
to red.
- **UPDATED: `test-logo-theme-e2e.js`** — replaces the old "must NOT be
sage" sentinel (sage was a regression marker; it's now the brand
default) with a theme-reactivity probe that overrides `--logo-accent` /
`--logo-accent-hi` directly and asserts the wordmark fill changes.
Duotone, mobile-fit, and clip checks are unchanged.
## Verification
- Default load: sage CORE + teal SCOPE in navbar AND hero ✔ (asserted by
step 1 of the new e2e).
- Customizer override: wordmark follows `accent` / `accentHover` ✔
(asserted by step 2 of the new e2e + the theme-reactivity probe in
`test-logo-theme-e2e.js`).
- Preflight: all hard gates green (PII, branch scope, red commit,
CSS-var defined, CSS self-fallback, LIKE-on-JSON, sync migration); all
warnings green.
## Browser verified
E2E assertion added: `test-logo-default-sage-teal-e2e.js:73` (default
sage), `test-logo-default-sage-teal-e2e.js:124` (customizer override).
CI runs both via `deploy.yml:243`.
Browser verified: covered by Chromium e2e against
`http://localhost:13581` in CI; staging URL TBD on merge.
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
Partial fix for #1139 — closes Bug B (desktop More menu degenerate). Bug
A (mobile hamburger) blocked on user device info; left for separate PR.
## What this changes
`public/app.js` `applyNavPriority()` (the >1100px measurement branch):
add a "minimum More menu size" floor. After the greedy `fits()` loop
terminates, if exactly one link ended up in `is-overflow`, promote one
more from the overflow queue so the dropdown contains ≥2 items.
```diff
let i = 0;
while (!fits() && i < overflowQueue.length) {
overflowQueue[i].classList.add('is-overflow');
i++;
}
+ // #1139 Bug B: floor the More menu at >=2 items.
+ var overflowedCount = allLinks.filter(a => a.classList.contains('is-overflow')).length;
+ if (overflowedCount === 1 && i < overflowQueue.length) {
+ overflowQueue[i].classList.add('is-overflow');
+ i++;
+ }
rebuildMoreMenu();
```
The ≤1100px Priority+ design contract (5 high-priority + More) is
unchanged; the floor only applies on the measurement branch.
## Why
Above 1100px the measurement loop greedily fills inline links until
something overflows. If exactly one non-priority link is wider than the
remaining slack, the loop pushes only it into overflow and stops —
producing a one-item "More ▾" dropdown. With the fixture stats this
reproduces deterministically at 1600px (overflow=`["🎵 Lab"]`); the
prod report on 1101–1278px is the same root cause with realistic
`#navStats` width consuming most of the remaining slack.
## TDD
- Red: `test-nav-more-floor-1139-e2e.js` sweeps 1101, 1150, 1200,
1240, 1278, 1280, 1340, 1500, 1600, 1700px and asserts
`#navMoreMenu.children.length` is 0 or ≥2 — never 1. On master it
fails at 1600px (`items=1, overflow=[#/audio-lab]`).
- Green: with the floor in place all 10 viewports pass.
- Existing `test-nav-priority-1102-e2e.js` and
`test-nav-fluid-1055-e2e.js` still pass (5/5 and 20/20).
- Wired into CI alongside the other nav E2E tests.
## Out of scope (Bug A)
The mobile hamburger inert-button report needs a console snapshot from
the affected device (pasted in the issue body) to pin the root cause.
Left open for a follow-up PR. This PR uses "Partial fix" intentionally
and does NOT include `Fixes #1139` so the issue stays open.
---------
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local>
Fixes#1147
## What
Re-orders the node-detail sections in **both** the side panel and the
full
node detail page. New sequence matches operator mental order
(identity → what this node SAID → who heard it → relay topology → meta):
1. Identity (name, role, badges)
2. Map + QR (full page) / Public key (side panel)
3. Overview (Last Heard, First Seen, Total Packets, etc.)
4. **Recent Packets** ← lifted from bottom
5. Heard By (observers)
6. Neighbors
7. Paths Through This Node
8. Clock Skew (hidden until populated)
## Why
"What did this node originate?" is the most-asked operator question at
the
node-detail surface. Previously Recent Packets was the LAST section in
both
views — operators had to scroll past Clock Skew, Heard By, Neighbors,
and
Paths just to see the node's own activity. Section B4 of the
node-analytics review flagged this as P1.
## Changes
- `public/nodes.js`: pure template re-order in two render paths
(full-page `loadFullNode`, side-panel `renderDetail`). No data,
styling, or behavior changes — same DOM ids, same CSS classes,
same content per section.
- `test-issue-1147-section-order-e2e.js`: new Playwright test that
loads a node detail page (and the side panel) against the fixture
DB and asserts `Recent Packets` index in DOM order is **before**
`Paths Through This Node`, `Heard By`, and `Neighbors` for both
surfaces.
- `.github/workflows/deploy.yml`: wired the new E2E into the
existing `e2e-test` job.
## TDD trail
- Red commit: `c0829fd` — adds failing E2E (Recent Packets is last).
- Green commit: `29cdb22` — re-orders the templates, test passes.
## Browser verified
E2E assertion added: `test-issue-1147-section-order-e2e.js:84` (full
page) and `:115` (side panel). Local Chromium can't run on this host
(libc reloc), so verification is via CI; server-side `grep` of rendered
`/nodes.js` confirms the new section order in both code paths.
## Preflight
All hard gates pass (PII, branch scope, red commit, CSS vars,
self-fallback, LIKE-on-JSON, sync migration). All warning gates pass.
---------
Co-authored-by: kpaclawbot <bot@kpaclawbot.local>