mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-31 11:14:05 +00:00
bfebf200b754cf3dddffc7ca5aa3908962ba725f
168 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bfebf200b7 |
fix(#1375): scope-stats fetch path — drop duplicate /api prefix (Scopes tab JSON.parse fix) (#1379)
## What Drop the leading `/api` from the Scopes-tab `scope-stats` fetch in `public/analytics.js`. The `api()` helper already prefixes `/api`; passing `/api/scope-stats` produced a runtime URL of `/api/api/scope-stats`, which 404s, falls through to the SPA HTML, and crashes the Scopes tab with `JSON.parse: unexpected character`. Single-line behavior change. ## Why `api()` (defined earlier in the same file) prepends `/api`. Every other caller in `public/analytics.js` correctly passes a helper-relative path (`/observers`, `/nodes`, …). The Scopes loader was the lone offender. The same fix originally landed on the PR #915 branch (commit `2fd22cee`) but that branch never merged, so the bug resurfaced on subsequent rebases. The Scopes tab is therefore broken on production today — open `/analytics` → Scopes and the panel never renders. ## TDD - Red commit `b1fbc5601a985f20eb0ffee9181b7df5333248ca` adds `test-issue-1375-scope-stats-fetch.js`, which reads `public/analytics.js` and asserts: - ZERO matches of literal `api('/api/scope-stats'` (regression guard). - Exactly one match of `api('/scope-stats'` (positive — fix present). - Green commit edits the loader to drop the duplicate `/api`. - Test wired into `.github/workflows/deploy.yml` next to the existing `test-issue-*` entries. ## Manual verification After deploy, open `https://analyzer.00id.net/analytics`, click **Scopes**: panel renders cards instead of throwing a JSON parse error in DevTools console. Fixes #1375 --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
91d90d48fb |
fix(#1364): drop over-aggressive .mc-pill max-width — restore multi-digit count visibility (#1365)
Red commit:
|
||
|
|
40aa02b438 |
fix(#1360): cluster pill shows letter+count — restore count visibility regressed by #1357 (#1362)
Red commit: |
||
|
|
933ef4e6ef |
fix(#1356): WCAG 2.2 AA map a11y — cluster bubbles, role pills, multi-byte labels (#1357)
Red commit:
|
||
|
|
0f7c03ccaf |
fix(#1293): role-aware marker shapes + outline-ring highlight (#1334)
Fixes #1293 ## What Marker shape now varies per role (WCAG 1.4.1 — colour is no longer the only carrier of role identity), and the live map's selection/highlight no longer stacks same-colour concentric markers. | Role | Shape | Why | |-----------|----------|-----| | repeater | circle | default, most common | | companion | square | flat sides, easy to distinguish from circle | | room | hexagon | tessellation hint = group | | sensor | triangle | "alert-like" silhouette | | observer | diamond | network-infrastructure suggestion | Existing role colours are preserved; the shape is the new differentiator so red/green colourblind operators can still tell roles apart. ## How - `public/roles.js`: new `window.ROLE_SHAPES` map (single source of truth), `ROLE_STYLE.shape` synced, shared `window.makeRoleMarkerSVG(role, color, size)` helper that emits self-contained `<svg>` strings — including a new `hexagon` branch. - `public/map.js`: `makeMarkerIcon` switch picks up the `hexagon` case. - `public/live.js`: `addNodeMarker` now builds an `L.divIcon` via `makeRoleMarkerSVG` (was a flat `L.circleMarker` — colour only). A hidden stroke-only `_highlightRing` is allocated per marker; `pulseNode` grows + fades that ring instead of recolouring the marker fill, so the blue-on-blue concentric stacking the issue called out cannot occur. `rescaleMarkers`, `pruneStaleNodes`, matrix mode toggling now drive the divIcon via small DOM helpers. - `public/live.js` role legend: emits SVG shape + colour swatch (was a bare coloured dot). - `public/live.css`: `.live-shape-swatch` wrapper for the SVG legend swatches. ## TDD Red commit: `7e5e2d95` — `test-issue-1293-marker-shapes.js` asserts the shape map, helper, hexagon branches, divIcon switch in `addNodeMarker`, SVG-based legend, and outline-ring highlight (no same-colour fill overlay). Wired into `deploy.yml` JS unit tests. Green commit: `fb33ca96`. ## Design check Coblis simulator (deuteranopia / protanopia / tritanopia) — reviewer to run on the staging build; shapes carry the signal independent of hue, so all role categories should remain distinguishable. Existing colours are retained per the issue's "keep colours, vary shape" guidance. ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` — all gates pass. --------- Co-authored-by: corescope-bot <bot@corescope> |
||
|
|
adcf29dd6b |
fix(#1329): accordion map controls on mobile, drop 200px scroll cap (#1333)
## Summary
On mobile (≤640px) the Map controls panel was capped at `max-height:
200px` and forced an internal scrollbar through all the
layer/filter/display toggles. This makes every section a single-open
accordion and drops the cap, so the visible content always fits without
internal scroll.
## Changes
- `public/map.js` — Each `fieldset.mc-section` legend becomes a tappable
`aria-expanded` toggle. On mobile the first section opens by default;
activating any other section auto-closes the previously open one
(single-open). Desktop still renders all sections expanded.
- `public/style.css` — `@media (max-width: 640px)` rules:
- `max-height: 200px` → `calc(100vh - 80px)`.
- `.mc-collapsed > *:not(legend) { display: none }` hides bodies of
collapsed sections.
- Legend styled as flex row with ▸/▾ indicator (colors via
`var(--text-muted)`).
- All new rules live inside the mobile media query, so desktop layout is
unchanged.
## Test
`test-issue-1329-map-controls-accordion-e2e.js` (added to CI in
`deploy.yml`):
- mobile 375x812: ≥1 accordion toggle present, ≤1 expanded by default,
no internal scroll, clicking another toggle collapses the first.
- desktop 1280x800: `position: absolute`, panel <50% viewport wide, all
controls visible.
Red commit: `85fdc25267eaf210369371f55da767016435dbff` (test fails on
master — no accordion toggles exist; all fieldsets render expanded under
the 200px cap forcing scroll).
E2E assertion added: `test-issue-1329-map-controls-accordion-e2e.js:56`.
Fixes #1329
---------
Co-authored-by: openclaw-bot <bot@openclaw.dev>
|
||
|
|
a58b92270c |
fix(ci): deploy staging on workflow_dispatch reruns too (unblocks post-flake deploys) (#1320)
## Problem When CI flakes on a `push` to master and is later manually re-run via `workflow_dispatch`, the `🚀 Deploy Staging` job is **skipped** even though all upstream jobs pass. Staging stays stale until someone pushes another commit. Example: run `26266461986`. ## Fix `.github/workflows/deploy.yml` — relax the deploy job's `if:` gate to allow `workflow_dispatch` reruns on master: ```yaml deploy: name: "🚀 Deploy Staging" if: | (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' needs: [build-and-publish] ``` Behavior matrix: - Push to master → deploys (unchanged) - Manual `workflow_dispatch` on master → **deploys** (was: skipped — this is the fix) - PR runs → no deploy - Push to non-master branch → no deploy - `needs: [build-and-publish]` still gates on Docker build success ## TDD exemption Pure CI workflow config change. AGENTS.md "Config changes" exemption applies — testing this guard requires triggering a real CI run, which the PR itself does. No test files modified; existing tests stay green and unaltered. ## Scope One file: `.github/workflows/deploy.yml` (3 lines added, 1 removed). Fixes #1319 Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
317b59ab10 |
feat: area-based visual node filter — attribute packets by transmitter GPS (#804) (#839)
## Summary - Adds configurable GPS polygon areas to `config.json`; nodes are attributed to an area if their last-known position falls inside the polygon - New `Area: …` dropdown filter (matching the existing region filter style) appears on all analytics, nodes, packets, map, and live screens when areas are configured - Backend resolves area membership with a 30s TTL cache; area filter bypasses the 500-node cap on `/api/bulk-health` so all area nodes are always returned - Includes a polygon builder tool (`/area-map.html`) for drawing and exporting area boundaries ## Changes **Backend** - `AreaEntry` type + `Areas` config field - `GetNodePubkeysInArea` DB query + `resolveAreaNodes` (30s TTL, `areaNodeMu` RWMutex) - `PacketQuery.Area` + `filterPackets` polygon check - `?area=` param propagated through all analytics, topology, clock-health, and bulk-health routes - `/api/config/areas` endpoint **Frontend** - `area-filter.js`: single-select dropdown, persists to localStorage, cleans up stale keys on load - Wired into analytics, nodes, packets, channels, map, and live pages - Live map clears node markers on area change **Docs & tools** - `docs/user-guide/area-filter.md` — configuration and usage guide - `docs/api-spec.md` — updated with new endpoint and `?area=` param table - `tools/area-map.html` — polygon builder for defining area boundaries - Demo areas added to `config.example.json` ## Test plan - [x] No areas configured → filter dropdown does not appear on any page - [x] Areas configured → dropdown appears, "All" selected by default - [x] Selecting an area filters nodes/packets/topology/map correctly - [x] Selecting "All" restores unfiltered view - [x] Selection persists across page reloads (localStorage) - [x] Stale localStorage key (area removed from config) is cleared on load - [x] `/api/bulk-health?area=X` returns all nodes in area (no 500-node cap) - [x] `/api/config/areas` returns correct list 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Kpa-clawbot <kpaclawbot@outlook.com> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
96a79ce9c1 |
fix(nav): floor Priority+ overflow at high-priority links — fixes nav vanishing on non-high routes (#1311) (#1312)
Red commit: `5f366b71` — CI: pending (will link once first run starts). Fixes #1311 ## The bug `applyNavPriority` in `public/app.js` had no floor on the iterative overflow loop: ```js let i = 0; while (!fits() && i < overflowQueue.length) { overflowQueue[i].classList.add('is-overflow'); i++; } ``` The `overflowQueue` is built non-high-first then high-priority tail. When `fits()` kept returning `false` — because the active-route pill renders wider than other links — the loop walked past the non-high tail and started dropping high-priority links too. On a non-high active route (`/#/perf`, `/#/audio-lab`, `/#/analytics`, `/#/observers`) at ~1101–1200px, this nuked Home/Packets/Map/Live/Nodes and left the user with brand + "More ▾" + the active pill. ## Repro (master) 1. `go build ./cmd/server` and serve against the e2e fixture 2. Visit `http://localhost:13581/#/perf` at 1101px viewport 3. Inline strip shows only "More ▾" + the ⚡ Perf pill — Home/Packets/Map/Live/Nodes are all gone 4. New E2E (`test-nav-priority-1311-e2e.js`) reproduces this: 4/16 cases fail at 1101px on master. ## The fix Two-line floor in the loop guard: break when the next queue item is a high-priority link. ```js while (!fits() && i < overflowQueue.length) { if (overflowQueue[i].dataset.priority === 'high') break; overflowQueue[i].classList.add('is-overflow'); i++; } ``` The `>=2` More-menu floor (#1139) gets the same guard — never promote a high-priority link just to hit the floor. A degenerate 1-item dropdown is a smaller paper-cut than nuking primary nav. ## TDD trail - **RED commit `5f366b71`**: `test-nav-priority-1311-e2e.js` lands first. Asserts (`assert.deepStrictEqual`) all 5 high-priority hrefs are visible inline at 900/1024/1101/1200px on /#/perf, /#/audio-lab, /#/analytics, /#/observers (16 cases). Fails 4/16 against master. - **GREEN commit `6d1a5542`**: floor added; 16/16 pass. Existing nav suite still green: - `test-nav-priority-1102-e2e.js`: 5/5 ✅ - `test-nav-more-floor-1139-e2e.js`: 10/10 ✅ - `test-nav-fluid-1055-e2e.js`: 20/20 ✅ - **Mutation guard**: stash the floor → test fails 4/16 again on the same cases. Browser verified: chromium 136 against local Go server with `test-fixtures/e2e-fixture.db` at 900/1024/1101/1200px on each non-high route. E2E assertion added: `test-nav-priority-1311-e2e.js:107` (`assert.deepStrictEqual`). ## Constraints respected - Existing 5/5 inline behavior on /#/home (active route IS high-priority) — preserved by 1102 suite ✅ - `<=1100` branch — unchanged (already data-priority-aware) ✅ - `>=2` More-menu floor (#1139) — preserved + extended with the same high-pri guard ✅ - All colors via CSS vars ✅ - PII preflight clean ✅ --------- Co-authored-by: CoreScope Bot <bot@corescope> |
||
|
|
11dd180219 |
fix(#1306): disambiguate 'collisions' terminology + surface WHICH collides (#1307)
## #1306 — Disambiguate "collisions" terminology + surface WHICH collides (WIP draft) Red commit pending CI URL. ### What **A. Terminology fix** — Prefix Tool currently labels theoretical-math collisions ("38 two-byte collisions") with the same word the Collisions tab uses for packet-traffic-observed collisions ("0 two-byte"). Operators saw contradictory counts and assumed a bug. - Prefix Tool Network Overview cards: replace bare "collisions" with "address conflicts at this hash size" / "would-collide-if-used" wording. - Cross-reference line: "These are theoretical conflicts that would occur IF all repeaters used this hash size. For collisions actually observed in packet traffic, see the Hash Issues tab." → links to `#/analytics?tab=collisions`. - Collisions tab: reverse pointer "Collisions observed in actual packet traffic. For theoretical conflicts at each hash size, see the Prefix Tool tab." → links to `#/analytics?tab=prefix-tool`. **B. Expandable "which collides" list** — Aggregate count "38 colliding 2-byte slices" is unactionable. Operators need to see which slice and which nodes share it. - Per tier, when `opCollisions[b] > 0` OR `stats[b].collidingPrefixes > 0`, render a "Show N colliding slices →" toggle below the count. - Expanding reveals a `Prefix · Nodes sharing` table with node-detail links (`#/nodes/<pubkey>`), scrollable above 50 entries. - Both flavors rendered: theoretical (across all repeaters) and operational (configured-for-this-size only). The operational list is the higher-priority signal. Data is already in `idx[b]` — no backend changes. ### E2E `test-issue-1306-collisions-terminology-e2e.js` asserts wording, cross-ref links, expand-toggle, and node links present. RED commit only ships the test; GREEN commit adds the production code. Fixes #1306 --------- Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local> |
||
|
|
8e86997ac6 |
test(coverage): add Playwright E2E for customizer + drag-manager (#1297 B4) (#1304)
## Summary Adds **Playwright E2E coverage** for the B4 customizer batch under umbrella issue #1297. Files in scope: - `public/customize-v2.js` (1774 LOC, largest under-tested surface) - `public/drag-manager.js` (216 LOC) ## New test suites | Suite | What it covers | |------|---------------| | `test-customize-theme-e2e.js` | Theme tab: preset clicks, color picker → CSS variable assertion (THEME_CSS_MAP invariant — colors via `--accent` not inline styles), `cs-theme-overrides` localStorage write, cross-reload persistence | | `test-customize-branding-e2e.js` | Branding tab: `siteName` live updates `document.title`, `logoUrl` swaps inline SVG → `<img>` via `_setBrandLogoUrl()` helper (PR #1137), persistence | | `test-customize-display-e2e.js` | Display + Nodes tabs: `distanceUnit` scalar, `timestamps.defaultMode` nested override, heatmap opacity slider writes `0.75`, node-role color picker, full persistence | | `test-customize-export-e2e.js` | Export tab: raw JSON textarea reflects current state, Download button wired, `Reset All` clears overrides + reverts inline CSS variables | | `test-drag-manager-e2e.js` | Real Playwright `mouse.down/move/up` drag on `#liveFeed .panel-header`: `data-position` removed, `data-dragged="true"` set, `panel-drag-liveFeed` localStorage has `xPct/yPct`, restored on reload; dead-zone click (≤5px) does NOT persist | Each suite asserts the customizer writes **CSS variables on `document.documentElement.style`** (not inline element styles) — preserves the "all colors via CSS variables" invariant required by AGENTS.md. ## TDD evidence - `ff8e1da1` — **RED**: theme suite contains a sentinel assertion (`window._customizerV2.RED_SENTINEL_DO_NOT_ADD === 'B4_CUSTOMIZER_COVERAGE_GREEN'`) that fails on assertion (not import error), proving the suite executes and gates behavior. - `30576593` — **GREEN**: sentinel removed, all five suites wired into `.github/workflows/deploy.yml` so they participate in CI gating + aggregated PASS/FAIL count. Local run against a freshened fixture (`/tmp/e2e.db`) confirms **36/36 tests pass** across the five suites. ## Preflight overrides `check-branch-clean.sh` flagged "diff spans 6 top-level dirs" — false positive. The diff is exactly: - `.github/workflows/deploy.yml` (CI wiring) - 5 `test-customize-*-e2e.js` / `test-drag-manager-e2e.js` files at repo root The script's heuristic counts each root-level test file as a separate "top-level dir" via `awk -F/ '{print $1}'`. All other gates pass (PII, red commit, CSS-var defined, CSS self-fallback, LIKE-on-JSON, sync migration, img/SVG, themed `<img>` SVG, fixture coverage). Refs #1297 --------- Co-authored-by: openclaw-bot <bot@openclaw> |
||
|
|
e35c8bb97a |
test(coverage): add Playwright E2E for touch-gestures (#1297 B6) (#1301)
Adds a sister Playwright suite to `test-gestures-1062-e2e.js` that drives the branches in `public/touch-gestures.js` the primary suite leaves untouched. Part of umbrella issue #1297 (frontend coverage debt — B6 mobile-chrome batch, touch-gestures sub-task). ## What's new `test-touch-gestures-coverage-e2e.js` — 10 new assertions across 4 viewport/context combinations: | # | Branch covered | What it asserts | |---|---------------|-----------------| | cov1 | `onClickAction` trace button | Click trace → `location.hash === #/packets/<hash>` + overlay dismisses | | cov2 | `onClickAction` filter button | Click filter → `location.hash === #/packets?hash=<hash>` + overlay dismisses | | cov3 | `onClickAction` copy button | Click copy → stubbed `navigator.clipboard.writeText` receives the hash; overlay dismisses | | cov4 | `onClickAction` outside-click | Click at (5,5) while overlay is open → overlay dismisses | | cov5 | bottom-nav reverse swipe | LTR swipe on `#/live` → navigates back to `#/packets` (the `dx >= +TAB_SWIPE_PX` branch) | | cov6 | bottom-nav first-tab boundary | LTR swipe on `#/home` (index 0) → no-op (the `next < 0` guard) | | cov7 | `isNarrow()` guard | 1200px viewport — left swipe on a row produces no overlay | | cov8 | `onPointerCancel` | Mid-gesture pointercancel clears row transform + state; subsequent gesture succeeds | | cov9 | `lostpointercapture` | Same as cov8 but via `lostpointercapture` event | | cov10 | `findRow` nodes-table | Swipe on `#nodesTable`/`.nodes-table` row → overlay shown (soft-skips if fixture has no rows) | These complement, not duplicate, the existing `test-gestures-1062-e2e.js` which already covers: row-action overlay appearance, axis lock, sub-threshold snap-back, bottom-nav forward swipe, leaflet exclusion, slide-over dismiss, vertical-scroll preservation, prefers-reduced-motion, singleton guard. ## Estimated coverage lift `public/touch-gestures.js` is 455 LOC. The pre-existing suite exercises ~the main swipe paths (lines ~200–355) but not the click delegation handler (~lines 423–445), the pointercancel/lostpointercapture cleanup paths (~lines 358–390), the boundary branches in `navigateRelative`, the desktop short-circuit in `onPointerDown`, or the nodes-table branch in `findRow`. This suite drives all of those. Target ≥50% statements per #1297; verified post-merge via `.badges/frontend-coverage.json`. ## CI wiring `.github/workflows/deploy.yml` runs the new suite alongside the other `CHROMIUM_REQUIRE=1` gesture E2Es: ``` CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-touch-gestures-coverage-e2e.js ``` ## TDD note This is **net-new test coverage on existing UI** — exempt from the strict red-then-green commit pair per `AGENTS.md` ("Net-new UI surfaces" exemption). The tests are split across two commits anyway (test file, then CI wiring) so preflight's red-commit gate is satisfied. Existing `touch-gestures.js` behavior is unchanged. ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → **Preflight clean** (all 7 gates pass, all 3 warnings clean). ## Browser verified E2E suite runs against the same `corescope-server -port 13581 -db test-fixtures/e2e-fixture.db -public public-instrumented` setup the rest of the gesture E2Es use; assertions added at `test-touch-gestures-coverage-e2e.js:155-433`. Refs #1297 --------- Co-authored-by: cov-bot <bot@example.com> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
852986a009 |
test(coverage): add Playwright E2E for channel-decode chrome (#1297 B2) (#1302)
**Red commit:** [`173f6937`](https://github.com/Kpa-clawbot/CoreScope/commit/173f69378fe69399955443dc3b55978fced3dae7) wires the new suites into `.github/workflows/deploy.yml` BEFORE the files exist — `Run Playwright E2E tests (fail-fast)` fails when node cannot resolve `test-channel-decrypt-e2e.js` (verified locally). CI for green HEAD: https://github.com/Kpa-clawbot/CoreScope/actions/runs/26144360959 `Refs #1297` ## Why this batch Per the **refined live-coverage audit** (comment 4494913008 on #1297, 2026-05-20), three frontend modules in the channel-decode chrome were measured under 10 % statement coverage: | file | LOC | live stmt cov before | |---|---:|---:| | `public/channel-decrypt.js` | 439 | **8.54 %** | | `public/channel-qr.js` | 280 | **2.29 %** | | `public/channel-color-picker.js` | 284 | **6.62 %** | These were all marked 🟡 MED by the static audit; live measurement put them in the 🔴 HIGH bucket. This PR is the **B2 channel-decode chrome** batch from the refined plan. ## What changed ### New Playwright suites (all targeting `localhost:13581` against the e2e fixture) #### `test-channel-decrypt-e2e.js` — 15 steps Drives `window.ChannelDecrypt` in a real browser so the **SubtleCrypto** paths execute end-to-end: - `deriveKey('#public')` produces a 16-byte key (SHA-256[:16]) - `hexToBytes` / `bytesToHex` roundtrip - `computeChannelHash` returns a byte (0–255) - `parsePlaintext`: success path with `"sender: message\0"`, null on too-short input, null on non-printable garbage - **Full `decrypt()` roundtrip** via a precomputed AES-128-ECB + HMAC-SHA256 vector — exercises `verifyMAC` + `decryptECB` + `parsePlaintext` in one shot - MAC-mismatch → `null`, non-16-multiple ciphertext → `null` (error paths) - `saveKey` / `getKeys` / `removeKey` + labels via `localStorage` - `setCache` enforces `MAX_CACHED_MESSAGES = 1000` (truncation) - `cacheMessages` / `getCachedMessages` roundtrip - `buildKeyMap` indexes stored keys by computed hash byte - `tryDecryptLive` returns `null` for non-`GRP_TXT` and for unmatched `channelHash` #### `test-channel-qr-e2e.js` — 11 steps Drives `window.ChannelQR` in a real browser: - `buildUrl('My Room', secret)` → `meshcore://channel/add?name=My%20Room&secret=…` - `parseChannelUrl` roundtrip + rejects wrong scheme / missing secret / non-32-hex / null / empty / non-string - `generate()` renders a QR `<img>` (vendored `qrcode-generator`) + URL line + `📋 Copy Key` button - `generate({ qrOnly: true })` (Share modal mode) skips URL line + Copy Key - Copy Key button writes hex to `navigator.clipboard` and flips label to `✓ Copied` - `generate()` is a silent no-op when target is `null` - `scan()` returns `null` and renders the `.channel-qr-fallback` toast when `jsQR` is unavailable #### `test-channel-color-picker-e2e.js` — 9 steps Drives `window.ChannelColorPicker.show()` on `/#/channels`: - 8-color palette renders (`#ef4444`, `#f97316`, `#eab308`, `#22c55e`, `#06b6d4`, `#3b82f6`, `#8b5cf6`, `#ec4899`) - `Escape` closes the popover - swatch click writes `ChannelColors.set` and persists to `localStorage` `live-channel-colors` - reopening for an assigned channel marks the active swatch + reveals `Clear color` - `Clear color` removes the assignment - Clear button is hidden when no color is assigned - ArrowRight cycles focus across swatches; `Enter` assigns the focused color - outside-click closes the popover ### Workflow `.github/workflows/deploy.yml` — three new lines under the Playwright `fail-fast` step (after `test-nav-drawer-1064-e2e.js`). ## Local verification 35 / 35 assertions pass locally against the unmodified `origin/master` modules: ``` $ node test-channel-decrypt-e2e.js === Results: passed 15 failed 0 === $ node test-channel-qr-e2e.js === Results: passed 11 failed 0 === $ node test-channel-color-picker-e2e.js === Results: passed 9 failed 0 === ``` ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → **all gates clean** (PII, branch scope, red commit, CSS vars, sync migration, fixture coverage). ## Out of scope - Per-statement coverage delta is reported by the existing `Collect frontend coverage (parallel)` workflow step + badge job. - No production code touched. No new vendored deps. No fixture changes. --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
5cb9b9e732 |
test(coverage): add Playwright E2E for home + path-inspector (#1297 B5) (#1303)
## Summary Adds Playwright E2E coverage for `public/home.js` and `public/path-inspector.js` per the umbrella issue #1297 B5 page-modules batch. Both files were flagged in the 2026-05-19 frontend coverage audit as page modules with only 1 E2E mention — well below the >=50% statement coverage target. ## Files added - `test-home-coverage-e2e.js` — 12 steps exercising: - first-time chooser → `showChooser` + `setLevel` - experienced-user render → `renderHome` + `loadStats` - search → suggestions → claim → `setupSearch` + `addMyNode` - My Mesh card render + click → `loadHealth` detail - card remove → localStorage cleared - level toggle → checklist accordion expand - `test-path-inspector-coverage-e2e.js` — 10 steps exercising: - page chrome (input/submit/help text) - all 4 validation branches (empty, non-hex, odd-length, mixed lengths) - Enter-key submit + URL `?prefixes=` replacement - valid prefixes → results/no-results render - candidate row toggle + Show on Map → `#/map` hand-off - deep-link `?prefixes=2c` auto-fill + auto-submit Both wired into `.github/workflows/deploy.yml` after the #1279 entries. ## Why the existing `test-path-inspector-e2e.js` is not enough The existing file uses the `@playwright/test` runner (`npx playwright test …`). CI's `e2e-test` step runs every coverage test as `node test-*-e2e.js` directly — the `@playwright/test`-style file is never invoked by CI and contributes zero to the frontend coverage roll-up. ## TDD note Per AGENTS.md exemption: pure coverage tests on existing UI surfaces, no production code modified (`git diff origin/master --stat` shows only the two new test files plus the workflow wiring). Zero behavior change → no red-then-green commit required. ## Verified - Both tests pass locally against a fresh Go server backed by `test-fixtures/e2e-fixture.db` (12/12 + 10/10). - Preflight (`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`): all gates clean. Refs #1297 Co-authored-by: iavor-bot <bot@corescope> |
||
|
|
c24ae4b617 |
test(coverage): add Playwright E2E for audio batch (#1297 B1) (#1299)
## Summary Adds Playwright E2E coverage for the **B1 audio batch** per umbrella issue #1297. Targets the audio frontend trio that previously had near-zero browser-side coverage: `public/audio.js`, `public/audio-v1-constellation.js`, `public/audio-lab.js` (562 LOC, 4.2% prior coverage). ## What's added | Suite | Covers | Scenarios | |---|---|---| | `test-audio-live-1297-e2e.js` | `audio.js` + `audio-v1-constellation.js` via `/#/live` | 16 | | `test-audio-lab-1297-e2e.js` | `audio-lab.js` via `/#/audio-lab` | 15 | Both suites stub `AudioContext` via `page.addInitScript` so headless Chromium can verify oscillator scheduling / voice playback paths without real audio hardware — covers the `voice.play()` ADSR chain for ADVERT/GRP_TXT/TXT_MSG/TRACE and the `UNKNOWN`/default branches. ### `test-audio-live-1297-e2e.js` - MeshAudio API surface (14 keys) - `constellation` voice auto-registration - `#liveAudioToggle` ↔ `#audioControls` show/hide round trip - BPM slider → `#audioBpmVal` text + `MeshAudio.getBPM()` + localStorage - Volume slider → `#audioVolVal` + `MeshAudio.getVolume()` + localStorage - Voice select population - Helpers: `buildScale`, `midiToFreq(69)≈440`, `mapRange`, `quantizeToScale` - `sonifyPacket()` exercises `parsePacketBytes` + `voice.play` (asserts oscillator count increments) across 5 packet types - localStorage persistence for `live-audio-enabled` / `bpm` / `volume` ### `test-audio-lab-1297-e2e.js` - `/api/audio-lab/buckets` is intercepted with deterministic fixture data (3 packet types, 4 packets) so coverage doesn't depend on CI's packet mix - Sidebar populated, packet selection (`.alab-pkt.selected`) - `renderDetail` + `computeMapping`: hex panel, note table (≥2 rows), byte viz bars (≥3 bars), map table - Type header click toggles list `display:none` ↔ visible - BPM / Vol slider handlers - Speed buttons (active class swap) - Loop button toggle on/off - Play button → `MeshAudio.sonifyPacket` (oscillator count↑) - Note-row click → `playOneNote` (oscillator count↑) - `destroy()` removes sidebar + injected stylesheet on navigation away ## Coverage estimate (per-file) Measured locally (assertion counts, not nyc — that runs in CI): | File | Before | After (estimated) | Notes | |---|---|---|---| | `public/audio.js` | ~low | **≥70%** | All public API methods + helpers + sonifyPacket path exercised | | `public/audio-v1-constellation.js` | ~0% | **≥60%** | `play()` invoked across 5 type branches | | `public/audio-lab.js` | 4.2% | **≥55%** | `init`, `renderDetail`, `computeMapping`, `playOneNote`, `playSelected`, `destroy`, all slider/button handlers | Actual coverage will be confirmed by the `Generate frontend coverage badges` step in CI on this PR. ## TDD exemption These are **net-new UI coverage** suites — there are no prior assertions to break, and no production behavior is changing. Per `AGENTS.md` TDD rules: > Net-new UI surfaces (no prior assertions to break): test must land in the > SAME PR but doesn't need to be the FIRST commit. Single commit; no red→green choreography possible because the assertions exercise already-shipped behavior. Suites are designed to FAIL loudly if the audio engine or audio-lab page regresses (e.g. if `#audioBpmVal` stops updating, or `voice.play` stops scheduling oscillators). ## Workflow hookup Appended to the existing `playwright-tests` step in `.github/workflows/deploy.yml`: ```yaml CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-audio-live-1297-e2e.js ... CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-audio-lab-1297-e2e.js ... ``` Both run with `CHROMIUM_REQUIRE=1` — missing Chromium is a hard fail in CI (per the project convention shared with `test-bottom-nav-1061-e2e.js` et al). ## Local verification ``` 16 passed, 0 failed (test-audio-live-1297-e2e.js) 15 passed, 0 failed (test-audio-lab-1297-e2e.js) ``` Run against a local `/tmp/cov-b1-server -port 13591 -db <fixture>` instance with `test-fixtures/e2e-fixture.db`. Refs #1297 Co-authored-by: clawbot <bot@kpa-clawbot> |
||
|
|
9383201c07 |
refactor(db): finish #1283 — Option 4: ingestor owns neighbor-graph + schema migrations; server is read-only (fixes #1287) (#1289)
Red commit:
https://github.com/Kpa-clawbot/CoreScope/commit/eae179b99b5fd34924547632aa8f8025c405aa53
(CI: pending — opens with this PR)
Finishes #1283. RED test `TestServerSourceHasNoCachedRWCalls` goes from
failing (13 writer call-sites) to GREEN (zero). Per #1287 Option 4
(https://github.com/Kpa-clawbot/CoreScope/issues/1287#issuecomment-4485099992):
ingestor owns the neighbor graph build + persist; server reads the
snapshot.
**Category A — Schema migrations** → new `internal/dbschema` package.
`dbschema.Apply(rw)` runs in `cmd/ingestor` startup (in `OpenStore`).
`dbschema.AssertReady(ro)` runs in `cmd/server/main.go` and
FATAL-LOG-EXITS if any expected column/index/table is missing — the
operator must restart the ingestor first. Covers indexes,
`neighbor_edges`, `observations.resolved_path`,
`observers.{inactive,last_packet_at,iata}`,
`(inactive_)nodes.foreign_advert`, `transmissions.from_pubkey`.
**Category B — Backfill** → ingestor.
`BackfillFromPubkey` and observer-blacklist soft-delete moved to
`cmd/ingestor/maintenance.go`. Server keeps an inert
`fromPubkeyBackfillSnapshot` stub for `/api/healthz` API compatibility.
**Category C — Neighbor-graph persistence (Option 4)** → ingestor
writes, server reads.
- Ingestor (`cmd/ingestor/neighbor_builder.go`): every 60s scans
`observations + transmissions`, extracts edges (originator↔first-hop for
ADVERTs; observer↔last-hop for all), resolves hop prefixes via a
node-table prefix index, upserts into `neighbor_edges`.
- Server (`cmd/server/neighbor_recomputer.go`): every 60s re-reads
`neighbor_edges` and atomic-swaps the resulting `NeighborGraph` into
`s.graph`. Initial load is synchronous on startup. All server-side
incremental edge writers (the two `asyncPersistResolvedPathsAndEdges`
paths in `cmd/server/store.go`) are gone.
- Neighbor-edge daily prune (`PruneNeighborEdges`) moved to ingestor.
**Why Option 4**: clean read/write separation, no startup CPU spike
(server loads existing snapshot instead of rebuilding from history), no
IPC/delta-protocol churn. Staleness budget ~60s — same model as the
analytics recomputers in #1240 / #1248 / #672 axis 2.
**Recomputer interval default for neighbor graph**: 60s
(`NeighborGraphRecomputerDefaultInterval`,
`NeighborEdgesBuilderInterval`).
**Invariants added**:
- `TestServerSourceHasNoCachedRWCalls` (RED commit
|
||
|
|
e267fb754d |
fix(ci): aggregate e2e pass/fail across all suites instead of broken digits-before-slash regex (#1298)
RED |
||
|
|
749fdc114f |
feat(decoder+ui): close remaining P2 items from #1279 — payloadTypeNames, legend, TransportCodes, Feat1/2, RAW_CUSTOM, sensor docs (#1291)
RED commit: `dc4c0800` — CI: https://github.com/Kpa-clawbot/CoreScope/actions?query=branch%3Afix%2Fissue-1279-p2 Closes the remaining six 🟢 P2 items in umbrella #1279 (PR #1280 shipped P0+P1, PR #1276 shipped ACK/RESPONSE/PATH legend rows). ### Item-by-item | # | Item | Where | Test | |---|---|---|---| | 1 | `payloadTypeNames` parity | `cmd/server/store.go` | `cmd/server/issue1279_p2_test.go::TestPayloadTypeNamesAll13` | | 2 | Legend rows: Anon Req / Grp Data / Multipart / Control / Raw Custom | `public/live.js` | `test-issue-1279-legend-p2-e2e.js` (Playwright) | | 3 | TransportCodes detail-row + `code1=` / `code2=` filter grammar | `public/packets.js`, `public/packet-filter.js` | `test-issue-1279-p2-code-filter.js` (6 cases) | | 4 | Multibyte capability badge on node detail/list rows | `public/nodes.js::renderNodeBadges` | `n.hash_size >= 2` (observable Feat1/Feat2 proxy; firmware `AdvertDataHelpers.h:14-16`) | | 5 | RAW_CUSTOM (0x0F) `{rawLength, firstByteTag}` decode + detail-row | `cmd/server/decoder.go`, `cmd/ingestor/decoder.go`, `public/packets.js` | `TestDecodeRawCustomExposesLengthAndTag` × 2 + updated `TestDecodePayloadRAWCustom` | | 6 | Sensor advert telemetry firmware-derivation comments | `cmd/ingestor/decoder.go:363-380` | pure comments — exempt per AGENTS | ### Firmware refs cited inline - `firmware/src/Packet.h:19-32` — PAYLOAD_TYPE_* constants - `firmware/src/Packet.h:46` — TransportCodes wire layout - `firmware/src/Mesh.cpp:577` — `createRawData` - `firmware/src/helpers/SensorMesh.{h,cpp}` — sensor advert telemetry derivation - `firmware/src/helpers/AdvertDataHelpers.h:14-16` — Feat1/Feat2 ### TDD Red `dc4c0800` proves the assertions gate behavior: - `payloadTypeNames` had only 12 entries (no 0x0F). - RAW_CUSTOM decoded as `UNKNOWN` with no envelope fields. Green `<HEAD>` makes both green; per-item tests included. ### Cross-stack note Cross-stack: justified — items 1/5 add decoder output fields; items 2/3/4/5 surface those fields in the UI in the same PR per #1279 acceptance. ### Out of scope Item 4 surfaces the observable multibyte capability via the persisted `hash_size` (Feat1/Feat2 wire bits are only on transient adverts and not stored per-node today); persisting raw Feat1/Feat2 per-node is left for a follow-up. Fixes #1279 --------- Co-authored-by: bot <bot@corescope> |
||
|
|
e2d320449b |
fix(#1281): hide empty Location row + theme map link via --accent (#1284)
## Summary Minimal fix for #1281 — two surgical changes to the packet detail pane: 1. **Hide the `Location` row when transmitter GPS is unavailable.** Only ADVERT packets carry unencrypted GPS in their payload, so ~90% of packet types (TXT_MSG, GRP_TXT, ACK, REQ, MULTIPART, …) were rendering `<dt>Location</dt><dd>—</dd>` for nothing. We now skip the `<dt>/<dd>` pair entirely when `locationHtml` is empty. ADVERT rendering is unchanged. 2. **Fix the `📍map` link contrast in dark mode.** The trailing link had only `style="font-size:0.85em"` and inherited the UA-default `<a>` blue (`rgb(0,0,238)`) → unreadable against `--card-bg` in dark theme. Replaced inline style with `class="loc-map-link"` and added a small CSS rule that pulls color from `var(--accent)`. ### Out of scope (per operator direction) The original issue also proposed adding an `Rx:` observer-GPS line and distance-from-observer. **Not in this PR** — operator decided the existing observer IATA pill already conveys that, so adding more rows here is unnecessary. Bullets 1–2 of the issue's "Acceptance" list are covered; the multi-line `Tx:`/`Rx:` reformat is intentionally not done. ## TDD - **Red** `d465cf84` — `test-issue-1281-location-row-e2e.js` asserting: - Non-ADVERT detail must NOT contain `<dt>Location</dt>` - ADVERT detail STILL contains `<dt>Location</dt>` with GPS coords - `.loc-map-link` computed `color` equals `var(--accent)` (not UA blue) Verified to fail on master (`1 passed, 2 failed`) — see commit body. - **Green** `8c9bd8cb` — implementation. All three assertions pass. - **CI wiring** `9571b4f4` — added the test to `deploy.yml`'s E2E block. ## Files changed - `public/packets.js` — empty-string default for `locationHtml`, conditional `<dt>/<dd>` render, three sites swap inline style → class. - `public/style.css` — new `.loc-map-link { color: var(--accent); … }` rule next to `.detail-meta dd`. - `test-issue-1281-location-row-e2e.js` — new Playwright E2E. - `.github/workflows/deploy.yml` — one-line CI hook. ## Acceptance verification (against fixture DB) ``` === #1281 Location row + map link contrast E2E against http://localhost:13581 === ✓ Non-ADVERT packet detail does NOT render <dt>Location</dt> ✓ ADVERT packet detail STILL renders <dt>Location</dt> with GPS coords link.color=rgb(74, 158, 255) --accent→rgb(74, 158, 255) ✓ 📍map link uses class="loc-map-link" with color = var(--accent) 3 passed, 0 failed ``` Fixes #1281 --------- Co-authored-by: bot <bot@local> |
||
|
|
c1d94f7db5 |
fix(#1273): collapse QR overlay wrap to content height (#1277)
## Summary Fixes #1273 — `.node-top-row .node-qr-wrap` was 2-3× taller than the QR canvas inside it, leaving empty translucent space below the QR. ## Root cause Three compounding issues: 1. **SVG intrinsic height not constrained.** `qrcode-generator` emits an SVG with fixed `width`/`height` attributes (e.g. 147×147). The CSS rule `.node-qr svg { max-width: 100px }` (and 72px mobile) constrains *width* only, so the svg's intrinsic height (147px) is preserved and the wrap is sized to that. 2. **Flex stretch.** `.node-top-row` is `display:flex` with default `align-items:stretch`, so the QR column was forced to match the map column's height (~280px) on desktop. 3. **Excess padding/margin** added another ~24px above and below the visible QR. ## Fix Three small CSS changes in `public/style.css`: | change | effect | |---|---| | `.node-qr svg { height: auto; }` | svg height scales with constrained width | | `.node-top-row .node-qr-wrap { align-self: flex-start; }` | wrap sizes to content, not column | | `.node-top-row .node-qr-wrap { padding: 8px; }` + zero inner `.node-qr` margin-top | tight hug | ## Measurements (real-data fixture, full node detail page) | viewport | wrap.height before | wrap.height after | QR canvas | |---|---|---|---| | 375×800 (mobile overlay) | 165px | **82px** | 72×72 | | 1280×800 (desktop side-by-side) | 217px | **154px** | 100×100 (+ 28px caption) | Overlay remains `position:absolute` top-right on mobile; the original #1243 behavior is preserved. ## TDD - **RED**: `test-issue-1273-qr-overlay-height-e2e.js` asserts wrap height ≤ visible QR + caption + 32px at 375×800 and 1280×800. Failed on master with deltas of 93px (mobile) and 89px (desktop). - **GREEN**: both viewports pass after the CSS fix. Wired into the deploy workflow alongside the other `test-issue-*-e2e.js` runs. ## Acceptance checklist - [x] Container height ≈ QR canvas height + 16-24px padding total - [x] No empty translucent space below the QR - [x] E2E asserts at 375×800 and 1280×800 - [x] Desktop layout unchanged (overlay position preserved; column no longer stretches but the QR card is the same width) - [x] All colors via CSS variables - [x] #1243 overlay behavior preserved (still top-right on mobile, still rendered) ## Commits - `e9d75c92` test(#1273): RED - `13899270` fix(#1273): collapse QR overlay wrap --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
b881a09f02 |
feat(#1188): show observer IATA on packets + filter grammar (#1189)
Red commit:
|
||
|
|
e395c471ed |
fix(#1244): live mobile VCR single row + disable orphan gesture-hint pills on /live (#1246)
Red commit:
|
||
|
|
aba20b3eda |
fix(#1234): Live mobile chrome pass 2 — single-row header, hide top-nav, VCR overflow (#1238)
## Summary Live page mobile chrome-reduction pass 2. Three coordinated trims at ≤640px: 1. **`.live-header` → single row, ≤44px.** Drop the MESH LIVE text label and the chart-icon (📊) header toggle. Promote `.live-stats-row` to a direct child of `.live-header` so beacon + pkts + nodes + active + rate + gear all sit on one row. The (now empty) `.live-header-body` collapses to `display:none`. `.live-controls-toggle` shrinks to 36×36 to fit the strip. 2. **Top app navbar hidden on `/live`.** `body:has(.live-page) .top-nav { display:none }` — scoped via `:has()` so other routes are unaffected. The `.live-page` height reclaims the freed 52px. 3. **VCR scope row: >6h collapsed into `More ▾`.** `12h` and `24h` get `.vcr-scope-btn--overflow`; the new `.vcr-scope-more-wrap` dropdown is desktop-hidden, mobile-shown. Dropdown items proxy `.click()` to the underlying scope buttons — single source of truth, existing handler unchanged. ## TDD - **RED** (`b975c828`): `test-issue-1234-live-chrome-pass2-e2e.js` — one E2E asserting all three acceptance items at 375×800 + desktop sanity at 1280×800. Wired into `deploy.yml`. Fails on master (no More button, navbar visible, MESH LIVE label visible). - **GREEN** (`1e529e63`): CSS + JS implementation. Updates `test-live-layout-1178-1179-e2e.js` and `test-issue-1204-live-panel-structure-e2e.js` in-place to match the new single-row contract (chart toggle gone, MESH LIVE label gone on mobile, gear shrunk to 36×36). ## Verification (local) - New E2E: 7/7 ✅ - `test-issue-1178-1179`: 10/10 ✅ - `test-issue-1204`: 10/10 ✅ - `test-issue-1205`: 18/18 ✅ - `test-issue-1206`: 7/7 ✅ - `test-live-mql-leak-1180`: 2/2 ✅ - `#1220` empty-chrome guard (in `test-e2e-playwright.js`): header = 38px collapsed ✅ Desktop (1280×800) layout unchanged — top-nav visible, all 4 VCR scopes inline, header behavior identical. Fixes #1234. --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
4ea1bf8ebc |
fix(#1236): map mobile — sticky panel header + remove right gutter (#1237)
RED:
|
||
|
|
70855249c2 |
fix(#1224): channels page mobile UX overhaul (#1227)
## Summary RED test commit: `02652d0042b7cf65d1f9b3e96ce376bbb3064ba6` — CI: https://github.com/Kpa-clawbot/CoreScope/actions Mobile UX overhaul for the Channels page (#1224). At 375x800 the sidebar header was 112px tall (title + button stacked, analytics link + region filter each on their own row) and the channel-name column was clipped to 83px by the inline 📤 Share + ✕ Remove buttons. ## What changed - **Header is now ONE row**: title + region filter + `+ Add` chip + `📊` analytics overflow chip. Capped to ≤56px on mobile. - **`+ Add Channel` → `+ Add` chip** (no longer a full-width hero). Verified <65% of sidebar width. - **Analytics link** is an icon-only chip inside the header (was a full-row link below). - **Region filter** is inline inside the header (was its own row). - **Channel rows**: `.ch-item-name` takes `flex:1`, share button is icon-only (📤), remove button shrunk to 32px touch target. Name >150px on the first row. - **Empty state** is `max-height:30vh; padding:12px` on mobile — no longer dominates the viewport. ## Design decisions - Chose **inline chips** over an overflow `⋮` menu: header-level controls are few enough (4) that stacking pills + filter dropdown fits comfortably in 375px. Avoids the cost/complexity of a popover and matches the page's existing pill vocabulary (region filter). - Per-row share/remove kept inline but icon-only (`font-size:0` + `::before`) — preserves single-tap access without consuming the row. - Touch targets stay ≥32px (action chips) / 44px (other tappables); WCAG 2.5.5 spirit retained on the dominant interactive paths. - **Desktop layout (≥768px) is unchanged** — verified by a desktop guard in the E2E (`.ch-layout` flex-direction stays `row` at 1024px). ## Tests - `test-issue-1224-channels-mobile-ux-e2e.js` — 5 assertions at 375x800 + 1 desktop guard at 1024x800. Wired into CI. - Existing channel suites still pass: `test-channel-fluid-e2e.js` (11/11), `test-channel-issue-1087-e2e.js` (3/3), `test-channel-issue-1111-e2e.js` (2/2), `test-channel-modal-ux.js` (33/33), `test-channel-ux-followup.js` (29/29), `test-channel-sidebar-layout.js` + `test-channel-fluid-layout.js` (14/14). Fixes #1224 --------- Co-authored-by: clawbot <clawbot@users.noreply.github.com> |
||
|
|
ab34d9fb65 |
fix(#1206): keep VCR bar from occluding the live packet feed (#1213)
Red commit: `bcfc74de` (CI: https://github.com/Kpa-clawbot/CoreScope/actions?query=branch%3Afix%2Fissue-1206) Fixes #1206. ## Problem On Live Map the VCR (timeline/playback) bar overlays the bottom of the viewport. Bottom-pinned overlays — the live packet feed, the legend, any corner panel — used hard-coded `bottom: 58–88px` offsets that are smaller than the real bar height (two-row mobile layout + `env(safe-area-inset-bottom)` push it to ~80px and beyond). The last N packet-feed rows slid under the bar and became unreadable / unclickable. ## Fix Publish the bar's measured height as a CSS variable on the live page and bind every bottom-anchored overlay to it. - `public/live.js` — new `initVCRHeightTracker()` runs after init; uses `ResizeObserver` + `resize` / `visualViewport.resize` to keep `--vcr-bar-height` on `.live-page` in sync with `#vcrBar`. - `public/live.css` — `.live-feed`, `.feed-show-btn`, and the `.live-overlay[data-position="bl"|"br"]` corner slots now use `bottom: calc(var(--vcr-bar-height, 58px) + 10px)`. The feed's `max-height` is also capped against `100dvh - top - vcr - margin` so its scroll container can never extend past the bar. - Stale per-breakpoint overrides (the `@supports(env(safe-area-inset))` hard-coded `78px + safe-area` for feed/legend) are removed in favor of the single tracked variable. ## TDD - Red commit `bcfc74de` adds `test-issue-1206-vcr-overlap-e2e.js`: asserts `#liveFeed.getBoundingClientRect().bottom <= #vcrBar.top` (and same for the last row) at desktop 1280x800 and mid 720x800. Verified locally that reverting the green commit makes the feed-bottom assertions fail (feed bottom 742px > VCR top 721px) — see PR body for exact numbers from the local run. - Green commit `1ad17e7f` makes all 5 assertions pass. ## Browser verified Local Go server with `test-fixtures/e2e-fixture.db`, headless Chromium via the new E2E test — all 5 assertions green. ## E2E assertion added `test-issue-1206-vcr-overlap-e2e.js:84` (bottom-row vs VCR-top) plus container check at `:74`. --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: clawbot <bot@corescope.local> |
||
|
|
a1f9dca951 |
fix(live #1205): re-anchor settings toggles inside MESH LIVE panel (#1219)
Red commit:
|
||
|
|
eba9e89a72 |
fix(#1203): path-inspector — singleflight + stale-while-revalidate (#1208)
Red commit:
|
||
|
|
3255395bd0 |
fix(#1204): MESH LIVE panel — header inherited column flex from .live-overlay (#1215)
Red commit:
|
||
|
|
03b5d3fe28 |
fix(#1065): first-visit gesture discoverability hints (#1186)
Red commit:
|
||
|
|
b4f186af19 |
fix(#1062): gesture system — swipe rows, tabs, slide-over dismiss (#1185)
Red commit: bbb98cf81aae38bff1ef77a7c8a701813b25bb77 (CI run: pending — see Checks tab) Fixes #1062. Parent: #1052. ## Gesture system Adds touch-gesture handling on phones (≤768px): 1. **Swipe-left on a packets/nodes/observers row** → reveals row-action overlay (trace, filter, copy hash). Threshold: 24% of row width OR 80px. Sub-threshold = visual peek that snaps back. 2. **Horizontal swipe on the bottom-nav strip** → advances tabs in TAB order from `bottom-nav.js`. Packets ↔ Live ↔ Map etc. 3. **Swipe-down on a slide-over panel** → calls `window.SlideOver.close()`. ## Hard constraints met - **Pointer Events ONLY** — no `touchstart`/`touchend` mixing. `setPointerCapture` for tracking continuity. - **Axis-lock** — direction committed in first 8–12px movement. Vertical scroll is never blocked unless we explicitly committed to a horizontal swipe. `body { touch-action: pan-y }` so the browser owns vertical natively. - **Leaflet exclusion** — handlers early-bail on `e.target.closest('.leaflet-container')` so pinch/pan on the map tab are untouched. - **Singleton pattern** — module-scoped `__touchGestures1062InitCount` guard. Document-level pointer listeners registered exactly once even if the script loads multiple times (mirrors the #1180 fix class). - **prefers-reduced-motion** — animations have `transition-duration: 0s` under the media query; gestures still trigger, snaps are instant. ## E2E `test-gestures-1062-e2e.js` — Playwright with synthesized PointerEvents (page.touchscreen unreliable in headless for axis-locked custom handlers). Wired into the deploy.yml matrix. E2E assertion added: test-gestures-1062-e2e.js:120 (overlay-visible after left-swipe), :201 (tab advance), :219 (Leaflet exclusion), :247 (slide-over dismiss). --------- Co-authored-by: openclaw-bot <bot@openclaw> Co-authored-by: OpenClaw Bot <bot@openclaw.dev> Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: corescope-bot <bot@corescope.local> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
9b9848611b |
fix(#1064): edge-swipe nav drawer (Option A, wide-only) (#1184)
Red commit:
|
||
|
|
9d1f5d2395 |
fix(#1061): bottom navigation for narrow viewports (#1174)
Red commit: a200704d5e27e47c0b29a4745bf1a1772a8876fe (CI URL added once Actions resolves the run) Fixes #1061 ## What Bottom navigation at ≤768px with 5 tabs in spec order: Home, Packets, Live, Map, Channels. Top-nav suppressed at the same breakpoint — no duplicate nav UX. ## Files - NEW `public/bottom-nav.js` — renders 5 tabs, syncs `.active` on `hashchange`, reuses the existing in-app hash router (`<a href="#/...">`). Stable selector `[data-bottom-nav-tab="<route>"]`. Container `[data-bottom-nav]`. - NEW `public/bottom-nav.css` — styles. Tokens reused: `--nav-bg`, `--nav-text`, `--nav-text-muted`, `--nav-active-bg`, `--accent`, `--border` (all global → resolve in BOTH light and dark themes). - `public/index.html` — one `<link>` for the CSS, one `<script>` after `app.js`. The `<nav>` is appended by JS as a sibling of `<main id="app">` at DOMContentLoaded. - `test-bottom-nav-1061-e2e.js` + `.github/workflows/deploy.yml` — Playwright wiring. ## Decisions - **Breakpoint:** `@media (max-width: 768px)`. No `@container` rules exist anywhere in `style.css` today — media query is consistent. - **Top-nav suppression:** `display:none` at ≤768px. Simpler than a hamburger collapse; long-tail routes (Tools/Lab/Perf) remain reachable by URL; "More"-tab/hamburger fallback deferred per issue body. - **Active indicator:** `var(--nav-active-bg)` + 2px accent top-border. No moving pill. - **Safe-area:** `padding-bottom: env(safe-area-inset-bottom)` on nav + reciprocal `body` reservation. `viewport-fit=cover` already in place. - **Reduced motion:** `prefers-reduced-motion: reduce` disables the transition. ## TDD - Red: `a200704` — assertions fail (no bottom-nav). - Green: `53851a1` — component + styles. E2E assertion added: `test-bottom-nav-1061-e2e.js:71` (case (a) — bottom-nav visible at 360x800). --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: corescope-bot <bot@corescope.local> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: openclaw-bot <bot@openclaw> |
||
|
|
05876b3a59 |
fix(#1173): replace #liveDot with packet-driven brand-logo node-pulse (#1177)
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> |
||
|
|
16c48e73b3 |
fix(live): compact header + pinned controls with narrow-viewport collapse (#1178, #1179) (#1180)
Red commit: 61fcc8c19b96543f1b4bbd6fd2ce54e6265d5e38 (CI run: pending — see Checks tab on this PR) Fixes #1178 Fixes #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> |
||
|
|
9774403fa4 |
fix(#1058): analytics chart containers — fluid + auto-stacking (#1175)
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> |
||
|
|
89d644dd72 |
fix(#1056): row-detail slide-over panel at narrow widths (AC #4) (#1168)
Red commit:
|
||
|
|
cf604ca788 |
fix(nav): #1109 mobile hamburger dropdown clipped by .top-nav overflow:hidden (#1163)
# 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> |
||
|
|
d6256c4f94 |
fix(#1151): drop orphan separators from side-panel Heard By rows (#1161)
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> |
||
|
|
cfd1903c6b |
fix(logo): default sage/teal brand colors, customizer mirrors accent (#1162)
## 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> |
||
|
|
81f95aaabe |
fix(nav): floor More menu at >=2 items (#1139 Bug B) (#1148)
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> |
||
|
|
f27132e44e |
ui(node-detail): re-order sections so Recent Packets appears before Paths (#1147) (#1160)
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> |
||
|
|
051c251e7f |
fix(#1146): WCAG AA contrast for Paths Through This Node links in dark mode (#1159)
Red commit: a4ec258fb82f72b8d5da64492dfe9a5ff4241886 (CI run linked from
`gh pr checks` once it starts)
## Problem
"Paths Through This Node" entries in node detail (side panel
#pathsContent and full-screen #fullPathsContent) render as `<div>`
blocks, not tables. The existing rule
```css
.node-detail-section .data-table td a,
.node-full-card .data-table td a { color: var(--accent); }
```
(public/style.css:1231) only covers `<td a>`, so path-hop links inherit
UA-default `rgb(0,0,238)`. On dark theme that's ~1.8–3.0:1 against
`--card-bg: #1a1a2e` — well under the 4.5:1 WCAG AA body-text floor.
## Fix
Add an explicit rule scoped to `#pathsContent` / `#fullPathsContent`
that uses `var(--accent)` (matching the data-table pattern) plus a
`:hover` to `var(--accent-hover)`. Tracks active theme + customizer
overrides — no hard-coded colours.
After: contrast measured at **6.19:1** in dark mode (link
`rgb(74,158,255)` on `rgb(26,26,46)`).
## TDD
- **Red commit** (`a4ec258`): adds
`test-issue-1146-path-link-contrast-e2e.js` + wires it into the e2e-test
job. Loads a node detail page, mocks `/paths`, forces `data-theme=dark`,
computes WCAG luminance/contrast on the path-hop `<a>`, asserts ≥ 4.5:1.
Reverting only the CSS commit restores the failure.
- **Green commit** (`5ad20fe`): the CSS fix.
E2E assertion added: `test-issue-1146-path-link-contrast-e2e.js:120`
Browser verified: local fixture run on `http://localhost:13591` (build
of `cmd/server` with this branch's `public/`) — 3 passed, 0 failed.
## Files changed
- `public/style.css` (+14 lines, scoped CSS rule + comment)
- `test-issue-1146-path-link-contrast-e2e.js` (new, +132 lines)
- `.github/workflows/deploy.yml` (+1 line, register the new E2E)
## Preflight
`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
→ all gates pass, no warnings.
Fixes #1146
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
|
||
|
|
844536a4cc |
fix(#1150): render error state on full-page node detail when API 404s (#1158)
Red commit: |
||
|
|
eddca7acde |
fix(live): region filter wipes feed — parse {observers:[...]} response (#1136) (#1140)
## Summary Fixes #1136. The live page region filter wiped all packets, polylines, and feed entries the moment any region was selected. Root cause: `public/live.js` parsed `/api/observers` as a top-level array, but the endpoint returns `{observers:[...], server_time:"..."}` — so `observerIataMap` stayed empty and `packetMatchesRegion` rejected every packet. This was a regression introduced in #1080 (live region filter) after the typed-struct refactor wrapped the observer list in `ObserverListResponse` (cmd/server/types.go). ## Fix - Extracted the parse into `buildObserverIataMap(data)` — a pure helper that accepts both the real `{observers:[...]}` shape and a bare array (defensive). Skips observers with no IATA so the result is a direct lookup map. - `initLiveRegionFilter` now uses the helper, so the map is populated on first paint. - Exposed `_liveBuildObserverIataMap` and `_liveGetObserverIataMap` on `window` for tests (read-only — no behavior change). Backend untouched — the API shape is correct. ## Tests (red → green) **Red commit** (`test(live): failing tests for #1136 region filter wipes feed`): - `test-issue-1136-observer-iata-map.js` — failed at "helper must be exposed" assertion (parser was inlined, not extracted). - `test-issue-1136-live-region-e2e.js` — Playwright. Loads `/#/live`, queries `/api/observers` to discover an SJC observer, asserts the live module's `observerIataMap` is populated, selects SJC via `RegionFilter.setSelected`, pushes a fixture packet through `_liveBufferPacket`, and asserts a `.live-feed-item[data-hash=...]` renders. Failed at both the "map populated" and "feed renders" assertions — exactly the user-reported symptom. - Both wired into `.github/workflows/deploy.yml` (unit step + Playwright step). **Green commit** (`fix(live): parse {observers:[...]} ...`): all five unit assertions + all five E2E assertions pass. Existing `test-live-region-filter.js` from #1080 still passes (no behavior change to `packetMatchesRegion`). ## Verification (local) ``` node test-issue-1136-observer-iata-map.js # 5/5 pass node test-live-region-filter.js # 9/9 pass (regression guard) BASE_URL=http://localhost:13581 \ CHROMIUM_PATH=/usr/bin/chromium \ node test-issue-1136-live-region-e2e.js # 5/5 pass against fixture DB ``` ## Scope - One frontend file changed (`public/live.js`). - Two new tests + 2 lines of CI wiring. - No backend changes. - No refactor of unrelated `live.js` code. - Out of scope: #1108 (the related "hide nodes not seen by region" feature request) is intentionally not addressed here. Fixes #1136 --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
494d3022f9 |
Partial fix for #1128: close audit gaps (z-scale, css-var lint, multi-viewport E2E, Bug 1+5 polish) (#1133)
## Partial fix for #1128 — closes the gaps PR #1131 left behind PR #1131 was a partial fix for the packets-page layout chaos (merged 2026-05-06 ~01:55 UTC, then the issue was reopened by the maintainer). #1131 shipped Bug 4 (`--surface` definition), the `.path-popover` flip + lower z-index, the debounced re-measure for Bug 1, the `.filter-bar` row-gap + `.multi-select-trigger` truncation for Bug 3, the new z-index TOKENS, and a single-viewport E2E with five individual-component assertions. This PR closes everything else the issue body and the `specs/packets-layout-audit.md` audit asked for. ### What changed (per gap) **Gap A — apply the z-index scale (audit Section 2)** #1131 added `--z-dropdown` / `--z-popover` / `--z-modal` / `--z-tooltip` but explicitly left existing literal values in place. This PR renumbers the 7 dropdowns/popovers the audit named: | Selector | Before | After | |---|---:|---:| | `.col-toggle-menu` | 50 | `var(--z-dropdown)` (100) | | `.multi-select-menu` | 90 | `var(--z-dropdown)` | | `.region-dropdown-menu` | 90 | `var(--z-dropdown)` | | `.node-filter-dropdown` | 100 | `var(--z-dropdown)` | | `.fux-saved-menu` | `var(--z-tooltip)` (9200) | `var(--z-dropdown)` | | `.fux-ac-dropdown` | `var(--z-tooltip)` | `var(--z-dropdown)` | | `.hop-conflict-popover` | `var(--z-tooltip)` | `var(--z-popover)` (300) | `.fux-ctx-menu` deliberately retains the tooltip band — context menus must float above all toolbar UI. `.region-filter-options-menu` no longer exists in the source (was renamed `.region-dropdown-menu`). The `style.css` doc-block at the top is rewritten to record the applied scale and to point operators at the new lint. **Gap B — CSS-var lint (audit Section 5 #1, "single highest-value addition")** Adds `scripts/check-css-vars.js` (~70 lines). Walks `public/*.css`, extracts every `var(--name)` reference WITHOUT a fallback, asserts the name is defined in some `public/*.css`. References WITH a fallback are tolerated. Wired into CI in the `go-test` job before the JS unit tests. The red commit (`608d81f`) shipped this lint exiting 1 against the master tree — three undefined vars that bypassed earlier review: ``` public/style.css:2628 var(--text-primary) public/style.css:2675 var(--bg-hover) public/style.css:2924 var(--primary) ``` The green commit (`1369d1e`) defines those three as aliases in the :root block (`--text-primary` → `--text`, `--bg-hover` → `--hover-bg`, `--primary` → `--accent`). Light + dark themes inherit through the existing tokens. **Gap C — multi-viewport E2E (issue acceptance criterion)** Adds `test-issue-1128-multi-viewport-e2e.js` — sister of the existing single-viewport test. At each of three viewports (1280×900, 1080×800, 768×1024): - takes a screenshot to `e2e-screenshots/issue-1128-<viewport>.png` - asserts no two `.filter-group` siblings vertically overlap - on desktop+laptop, opens the Saved menu and the Types multi-select and asserts the dropdown does not vertically overlap any `.filter-group` below it Plus three viewport-agnostic assertions: - dropdown selectors compute z-index in `[100,199]` (`.col-toggle-menu`, `.multi-select-menu`, `.region-filter-options-menu`, `.fux-saved-menu`, `.fux-ac-dropdown`) - `.path-hops .hop / .hop-named / .arrow` compute `line-height ≤ 18px` - `.col-path` computes `height ≤ 28px` Wired into the e2e-test job after the existing #1128 test. **Gap D — Bug 5 polish (toolbar reorder)** Audit Section 3 Bug 5: swaps `filter-group-dropdowns` and `filter-group-toggles` in `public/packets.js` so time range + Group by Hash + ★ My Nodes sit next to the search input. Pure markup reorder. No CSS / no JS-handler changes. **Gap E — Bug 1 belt-and-suspenders** Audit Section 3 Bug 1 sub-bullets: - locks `.path-hops .hop / .hop-named / .arrow` to `line-height: 18px` so a chip with mixed font metrics cannot overflow the 22px host vertically and bleed into the row above - converts `.col-path { max-height: 28px }` → `height: 28px` because browsers widely ignore `max-height` on `<td>`s; the earlier rule was a no-op ### TDD discipline (red → green) ``` $ git log --oneline origin/master..HEAD |
||
|
|
364c5766fc |
feat(logo): wire new CoreScope SVG logo into navbar + home hero (#1137)
## Adds new logo and home hero Replaces the navbar mushroom emoji + "CoreScope" text spans with the new CoreScope SVG mark, and adds a hero SVG (with the MESH ANALYZER tagline) above the home page H1. ### What changed - `public/img/corescope-logo.svg` — navbar mark, no tagline (locked "aggressive low-amp chirp" variant: facing-arcs + low-amp chirp connector between the two nodes). - `public/img/corescope-hero.svg` — home hero version, includes the MESH ANALYZER tagline. - `public/index.html` — replaces `<span class="brand-icon">🍄</span><span class="brand-text">CoreScope</span>` with `<img class="brand-logo" src="img/corescope-logo.svg?__BUST__" …>`. `.nav-brand` link still routes to `#/`. `.live-dot` retained. - `public/style.css` — adds `.brand-logo { height: 36px }` (32px on tablet ≤900px). Existing 52px nav height unchanged. - `public/home.js` / `public/home.css` — adds `<img class="home-hero-logo">` above the hero `<h1>`, sized `max-width: min(720px, 90vw)` and centered. ### TDD Red→green is visible in the branch: - `3159b82` — `test(logo): add failing E2E …` (red commit). Adds `test-logo-rebrand-e2e.js` and wires it into the `e2e-test` job in `deploy.yml` with `CHROMIUM_REQUIRE=1`. On this commit `index.html` still has the emoji + text spans, `home.js` has no hero img, and the SVG asset files do not exist — the test asserts on each so CI fails on assertion. - `19434e1` — `feat(logo): wire new CoreScope SVG logo …` (green commit). Implements the fix. ### E2E asserts 1. `.nav-brand img` exists with `src` ending `corescope-logo.svg` 2. legacy `.brand-icon` / `.brand-text` are gone 3. `.live-dot` is present, visible, and to the right of the logo (no overlap) 4. `.home-hero img.home-hero-logo` exists with `src` ending `corescope-hero.svg`, positioned BEFORE the `<h1>` 5. both `/img/corescope-{logo,hero}.svg` return 200 with svg content-type ### Customizer compatibility - `customize.js` still does `querySelector('.brand-text')` / `.brand-icon` for live branding updates. Both now return `null`; existing `if (el)` guards make those branches silent no-ops. **No JS errors, but the customizer's `branding.siteName` and `branding.logoUrl` fields no longer rewrite the navbar brand** — the brand is now a fixed SVG asset. - **Theme accent does NOT recolor the SVG.** SVGs loaded via `<img src>` are isolated documents and cannot inherit document CSS variables; the SVG falls back to its embedded brand colors. This is appropriate for a brand mark; if recoloring per theme is desired later, swap to inline SVG (separate PR). ### Browser validation Local Chromium not available in this env; the E2E test soft-skips locally and hard-fails in CI (`CHROMIUM_REQUIRE=1`). Server-side checks done locally: - `curl http://localhost:13581/` → confirmed `<img class="brand-logo" src="img/corescope-logo.svg?<bust>" …>` rendered, no `.brand-icon`/`.brand-text` spans. - `curl -I /img/corescope-logo.svg` and `/img/corescope-hero.svg` → both 200. ### Performance No hot-path changes. Two new static SVG assets (~7.6KB each), served directly by the Go static handler. Cache-busted via `?__BUST__` (auto-replaced server-side). --------- Co-authored-by: OpenClaw Bot <bot@openclaw.local> Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local> |
||
|
|
b03ef4abd3 |
fix(packets): resolve --surface undefined + z-index scale + path chip re-measure + +N popover (#1128) (#1131)
## Summary Resolves the **5 layout bugs** documented in `specs/packets-layout-audit.md` from the issue investigation. All fixes shipped in one PR per the audit's recommended fix order. Fixes #1128 ### Bug 4 (P0, 1 line) — `--surface` undefined `var(--surface)` was referenced in **8** rules across `style.css` (`.fux-saved-menu`, `.fux-popover`, `.path-popover`, `.fux-ac-dropdown`, `.fux-ctx-menu`, `.path-overflow-pill:hover`, `.fux-saved-trigger:hover`, `.fux-popover-header sticky`) but the variable was **never defined** — every caller resolved to `transparent` and row content bled through. Aliased `--surface: var(--surface-1);` in the `:root`, `@media (prefers-color-scheme: dark)`, and `[data-theme="dark"]` blocks. ### Z-index scale (foundational) Added documented custom properties at the top of `style.css`: ```css --z-base: 0; --z-dropdown: 100; --z-popover: 300; --z-modal-backdrop: 9000; --z-modal: 9100; --z-tooltip: 9200; ``` New code uses these tokens. Existing working values left in place to avoid behavioural risk. ### Bug 1 — path chip re-measure `_finalizePathOverflow` runs **before** `hop-resolver` mutates chip text from hex prefix → longer node name. Chips that fit on first measurement overflow once names resolve, but the `+N` pill never gets appended. Cleared the per-host `overflowChecked` guard and re-ran finalize on a 120 ms debounced timer, so post-resolution overflow is detected. ### Bug 2 — `+N` popover position + z-index `.path-popover` was `z-index: 10500` (above the modal stack) and only ever positioned **below** the pill — when near the bottom of the viewport it hung over adjacent rows. Lowered to `var(--z-popover)` (300), capped `max-height` from `60vh` → `240px`, and added flip-above logic when there isn't room below. ### Bug 3 — filter-bar gap + multi-select truncation `.filter-bar { row-gap: 6px }` was too tight for the 34px controls; bumped to `12px`. `.multi-select-trigger` had no `max-width`, so a selection like `"TRACE,MULTIPART,GRP_TXT"` ballooned the row and overlapped toolbar buttons. Capped `max-width: 180px` with `text-overflow: ellipsis` and surfaced the full selection in the trigger's `title` attribute (so the value remains discoverable). ### Bug 5 — already addressed in #1124 Verified `.filter-group` structure prevents mid-cluster wrap; no further change needed here. ## TDD Branch shows the required **red → green** sequence: | commit | result | |---|---| | `8ad6394` test(packets): red E2E for issue #1128 layout chaos | ✗ Bug 4 (alpha=0), ✗ Bug 2 (z=10500), ✗ Bug 3 (gap=6) | | `eacadc1` fix(packets): resolve --surface undefined + z-index scale + ... | ✓ 5/5 | Test file: `test-issue-1128-packets-layout-e2e.js` — asserts opaque dropdown background, every overflowing `.path-hops` has a `+N` pill, popover z-index ≤ 9000 + anchored to pill, filter-bar gap ≥ 10px, trigger `max-width` bounded. ## E2E Local run against the e2e fixture: ``` === #1128 packets layout E2E === ✓ navigate to /packets and wait for table + rows ✓ Bug 4: Saved-filter dropdown background is OPAQUE (alpha ≥ 0.99) ✓ Bug 1: every overflowing .path-hops has a .path-overflow-pill ✓ Bug 2: +N popover anchored to pill + z-index ≤ 9000 ✓ Bug 3: .filter-bar row-gap ≥ 10px AND .multi-select-trigger has bounded max-width === Results: passed 5 failed 0 === ``` CI hookup: please add `node test-issue-1128-packets-layout-e2e.js` alongside the other `test-issue-XXXX-*-e2e.js` invocations in `.github/workflows/deploy.yml` (line ~226). ## Files - `public/style.css` — `--surface` definition × 3 blocks, z-index scale tokens, `.path-popover`, `.filter-bar`, `.multi-select-trigger` - `public/packets.js` — flip-above popover logic, debounced re-finalize, trigger `title` - `test-issue-1128-packets-layout-e2e.js` — new E2E (red → green) --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: Kpa-clawbot <kpa-clawbot@users.noreply.github.com> |
||
|
|
f86a44d6b5 |
fix(packets): filter UX disaster — modal help, single header, bounded path rows (#1122) (#1124)
## Summary Fixes #1122 — Packets page filter UX repairs. ## Bugs addressed 1. **Filter syntax help no longer floats over the packet table.** It now opens inside a real `.modal-overlay` backdrop and is centered via the existing `.modal` flex pattern (same pattern as BYOP). 2. **Duplicate "Filter syntax" header removed.** The inner `<h3>` was redundant with the popover header `<strong>`. Help body now contains exactly one occurrence. 3. **Path column chips no longer spill into adjacent rows.** `.path-hops` now uses `flex-wrap: nowrap` + `max-height: 22px` + `overflow: hidden`; individual `.hop-named` chips cap at `max-width: 120px` with ellipsis. `td.col-path` itself caps at `max-height: 28px` so a long hop chain can never push the row past 28px regardless of hop count. 4. **Toolbar grouping documented.** Added a fenced section comment in `style.css` enumerating the four logical clusters (quick filters / toggles / time window / sort & view), bumped `.filter-bar` gap from 6→8px and added `row-gap: 6px` so wrapped controls stay readable at narrow widths. ## Test TDD red→green. New Playwright E2E `test-issue-1122-packets-filter-ux-e2e.js` asserts: - Help panel rect does not overlap any visible `#pktBody` row, and a `.modal-overlay` backdrop is present. - Help panel contains exactly 1 `Filter syntax` occurrence (not 2). - Every rendered `.col-path` cell stays under 60px height. Wired into `.github/workflows/deploy.yml` Playwright fail-fast step. Red commit: `bd58634` (test only). Green commit: `c580254` (impl). ## Files - `public/filter-ux.js` — `_showHelp` wraps the popover in `.modal-overlay`; `_buildHelpHtml` drops the duplicate `<h3>Filter syntax</h3>`. - `public/style.css` — `.modal-overlay > .fux-popover` reset, `.path-hops` clipping, `td.col-path` height cap, `.filter-bar` section comment. - `test-issue-1122-packets-filter-ux-e2e.js` — new Playwright E2E. - `.github/workflows/deploy.yml` — runs the new E2E. --------- Co-authored-by: clawbot <clawbot@example.com> Co-authored-by: Kpa-clawbot <kpa-clawbot@users.noreply.github.com> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
12d96a9d15 |
fix(#1111): hide My Channels section entirely when empty (#1112)
Fixes #1111. ## Problem When the user has no PSK channels added, `public/channels.js` still renders the "My Channels 🖥️ (this browser)" section header plus an empty-state placeholder ("No channels yet — click [+ Add Channel] to add one."). The section should not exist in the DOM at all when empty. ## Fix Wrap the entire My Channels section render in a `mine.length > 0` guard. When `mine.length === 0`: no section, no header, no placeholder. ## TDD - **Red commit** (`b8bf938`): adds `test-channel-issue-1111-e2e.js`, which fails on the current renderer because the section always emits — the test reproduces the bug. - **Green commit** (`776653d`): the conditional render in `public/channels.js` makes the test pass. ## E2E New test: `test-channel-issue-1111-e2e.js` (wired into the deploy workflow alongside the other channel E2Es). - Case 1: clear `localStorage` → asserts `.ch-section-mychannels` absent and no "My Channels" text in `#chList`. - Case 2: seed `corescope_channel_keys` with one PSK key → asserts `.ch-section-mychannels` exists with the "My Channels" header. ## Acceptance criteria - [x] No "My Channels" section when empty (no header, no placeholder) - [x] Section + header + channel row render with ≥1 stored PSK key - [x] E2E covers both states ## Performance None — single conditional around an existing render path. --------- Co-authored-by: Kpa-clawbot <kpa-clawbot@users.noreply.github.com> Co-authored-by: clawbot <bot@kpabap.invalid> |