mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-07-17 14:01:53 +00:00
498a2dcbb5a104f304a51dbec9bde046daa104bb
191
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
878d162b71 |
fix(live): persist nav-pin state across refresh (#1510) (#1515)
## What was broken
The nav-pin button state was not persisted across page loads. Every
refresh reset the nav to unpinned regardless of what the user had set,
forcing them to re-pin on every visit.
## What was added
- On init: reads `localStorage.getItem('live-nav-pinned')` and restores
the pinned state into `_navCleanup.pinned` before the button is created;
if pinned, the button gets the `pinned` class, `aria-pressed="true"`,
and `nav-autohide` is removed from the nav.
- On click: after toggling, writes
`localStorage.setItem('live-nav-pinned', _navCleanup.pinned)` inside a
`try/catch` (quota guard, consistent with other live.js localStorage
writes).
localStorage key: `live-nav-pinned`
Closes #1510
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
c9b98cb15f |
fix(#1498): preserve WS-pushed messages across REST replacements (#1513)
## Summary Fixes #1498. Roots out the actual WS-vs-REST race that has made `test-channels-ws-batch-e2e.js` flaky on master for ~2 weeks. ## Root cause `selectChannel()` and `refreshMessages()` unconditionally replace the in-memory `messages` array with the REST response. Any WebSocket-pushed messages appended between `selectedHash` assignment (when the chat view opens) and the REST resolution were silently stomped. The flaky test was a real-world manifestation: when the synthetic `processWSBatch` injection happened to land BEFORE the in-flight `/channels/<hash>/messages` fetch resolved, the (effectively empty) fixture REST response wiped it out. This is a production bug too — real users would lose any live message that arrived during channel load. ## Why the three prior PRs missed it - **#1499** — added a 500ms `waitForTimeout` before injection. Often enough to let the REST fetch resolve first, but not under any added load. - **#1502** — skipped the test instead of diagnosing. - **#1511** — re-enabled with a "wait by hash, not index" predicate. That fixed the symptom of `messages[length-1]` being some unrelated packet, but did nothing for the underlying race where the WS-pushed message gets wiped entirely by the REST replacement. None of the three PRs reproduced the failure locally. The hypothesis "closure over stale messages" in the test comment was never substantiated. ## Fix Stamp WS-pushed messages with `_fromWS=true` and add a `mergeWsAppendedIntoRest()` helper that preserves WS-pushed messages whose `packetHash` isn't already present in the REST response. Applied to all three REST replacement sites: - `selectChannel()` REST path - `decryptAndRender()` (encrypted channel path) - `refreshMessages()` (background poll) ## Tests Added `test-channels-ws-race-1498-e2e.js`. Deterministically forces the race by stubbing `fetch` to delay the `/channels/<hash>/messages` response 800ms, injects a WS message during the delay, asserts it survives the late REST resolution. - Red commit (`9dfc4b08`): test added against unfixed master HEAD → fails with `WS message stomped by REST fetch — messages after fetch: {"present":false,"count":0,"hashes":[]}`. - Green commit (`8f336591`): applies the fix → passes. Verified the red commit actually fails when the production change is reverted (TDD discipline check). ## Local repro stats Used the instrumented frontend (`public-instrumented/`) which exposes the race more reliably than the raw `public/` build (slower JS load widens the WS-vs-REST window). - Before fix: 29/30 pass (1 reproduced "injected message not found" failure — identical to CI). The new race test: 0/50 pass. - After fix: original `test-channels-ws-batch-e2e.js` — **50/50 pass**. New `test-channels-ws-race-1498-e2e.js` — **50/50 pass**. ## CI Wired the new race test into `.github/workflows/deploy.yml` right after the existing `test-channels-ws-batch-e2e.js` invocation. ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → all gates pass (PII, branch scope, red commit, CSS vars, LIKE-on-JSON, sync migration, all warnings). Browser verified: the fix was validated end-to-end against the local fixture server (`http://localhost:13581`) using the headless Chromium the CI uses. E2E assertion added: `test-channels-ws-race-1498-e2e.js` (deterministic race regression). --------- Co-authored-by: bot <bot@local> Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
7fcb226cd8 |
fix(#1486): collapse chevron no longer reopens closed detail panel (#1492)
## Summary Fixes #1486 — clicking the collapse chevron on a grouped packet row in the packets table no longer reopens the detail panel that the operator just closed. ## Root cause In the `#pktBody` row click handler the `toggle-select` action ran **both** `pktToggleGroup(value)` and `pktSelectHash(value)` on every chevron click. `pktToggleGroup()` already opens the detail panel itself (via `selectPacket()`) when it expands a row, so the trailing `pktSelectHash()` was: - redundant on **expand** (the panel was already opening), and - harmful on **collapse** — after the operator closed the detail panel via the ✕ in `#pktRight`, clicking the same chevron a second time to collapse the tree re-fetched `/packets/<hash>` and re-populated the panel with the same packet, exactly the behavior the issue describes. ## Fix Drop the unconditional `pktSelectHash(value)` call inside the `toggle-select` branch. `pktToggleGroup()` already handles the expand-side panel open; the collapse branch does no panel work, so a closed panel stays closed. ```js else if (action === 'toggle-select') { // #1486: pktToggleGroup() already opens the detail panel on EXPAND // (via selectPacket()), and must NOT open it on COLLAPSE. pktToggleGroup(value); } ``` ## Tests - New Playwright E2E `test-issue-1486-collapse-reopens-detail-e2e.js` walks the operator-visible repro: expand → assert panel open → click ✕ → assert panel empty → click chevron again → assert row collapsed AND panel STILL empty. - Committed red-first: the test was added in its own commit and FAILS on the unpatched code (3 passed / 1 failed), then GREEN on the fix commit (4 passed / 0 failed). - CI workflow seeds two extra observations onto the newest fixture transmission so a grouped (`toggle-select`) row exists; without this the fixture renders only flat rows and the chevron can't be exercised. ## Reproduction (manual, against staging or local) 1. Open `/#/packets` on desktop. 2. Click a grouped row's `▶` chevron — the tree expands and the detail panel opens on the right. 3. Click the `✕` in the top-right of the detail panel — panel goes back to "Select a packet to view details". 4. Click the same chevron (now `▼`) again — **before:** detail panel reopens with the same packet. **After:** the row collapses and the panel stays empty. --------- Co-authored-by: mc-bot <bot@meshcore.local> |
||
|
|
c841dbccdd |
fix(#1487): BYOP modal — bounded header, no body occlusion (#1493)
## Fixes #1487 Reporter (@EldoonNemar): "The dialog text can't be seen due to the title bar being massive." ### Root cause `.byop-header` swelled to ~73px on mobile because: 1. `position: sticky` + `margin: -24px -24px 12px` assumed `.modal` desktop padding (24px) — but `.modal` switches to 16px padding at mobile, so the sibling-margin pushed the description paragraph UP into the sticky-pinned header band, occluding it. 2. `.btn-icon` close button floors at 48×48 (touch target) → forced header height ≥48px+padding. 3. H3 inherited a default emoji line-height that added more height on platforms with tall emoji ascent metrics. ### Fix (`public/style.css`) - Drop full-bleed negative-margin gymnastics — header uses normal in-flow padding (`4px 0`); `.modal` padding handles inset. - `max-height: 48px` on header so emoji ascent / btn-icon floor can't blow it past safe range. - Bound H3 explicitly (`font-size: 1rem; line-height: 1.3`). - Override `.byop-x` to compact 32px visual size; preserve ≥44px effective tap target via invisible `::before` pad (a11y safe). ### Verification Hot-swapped onto staging, CDP-measured both viewports: | viewport | hdrH | descTop ≥ hdrBottom | result | |---|---|---|---| | 390×844 mobile | 41px (was 73) | 341 ≥ 329 ✅ | clean | | 1280×800 desktop | 41px | 318 ≥ 306 ✅ | clean | ### TDD - **Red commit**: |
||
|
|
d837166158 |
test(coverage): add Playwright E2E for channels page (#1297 B3) (#1300)
## #1297 B3 — Playwright E2E coverage for `public/channels.js` Pure-coverage PR. Adds five Playwright suites targeting the largest under-tested branches of `public/channels.js` (1950 LOC, was **19.9% statements** per the live coverage refinement in #1297 — the single biggest delta opportunity in the umbrella). No production code changes. ### Coverage exemption Per repo `AGENTS.md` TDD rule: this is the **net-new test coverage** case — there is no production change to gate, so a failing-then-passing red commit isn't applicable. All five suites exercise existing channels init() code paths that ship today. ### New test files | File | Scenarios exercised | | --- | --- | | `test-channels-list-render-e2e.js` | Sectioned sidebar (My Channels / Network / Encrypted) headers, encrypted collapse toggle + localStorage persistence, row badges + previews, color dot + color clear control, sidebar resize handle width persist | | `test-channels-selection-flow-e2e.js` | `selectChannel()` header update + URL replaceState, message row rendering (avatars, sender colors, packet links), node detail panel open via mouse + keyboard + close-with-focus-restore, deep-link route restoration, scroll button initial state | | `test-channels-add-modal-e2e.js` | Generate PSK Channel (key + QR + status banner + localStorage persist), Add PSK invalid hex error path, Add PSK valid hex success + close + My Channels row, Monitor Hashtag with and without leading `#`, empty-hashtag no-op, Scan QR unavailable fallback, Escape close, Remove ✕ flow | | `test-channels-share-color-e2e.js` | Share modal normal mode (dedicated `#chShareModal` with QR + Hex Key + Copy success label), Share modal error mode (`openShareModalError` when no stored key — field groups hidden), Escape close, `ChannelColorPicker.show` invocation on color-dot click, keyboard Enter on a `[data-share-channel]` span | | `test-channels-ws-batch-e2e.js` | `processWSBatch` via `_channelsProcessWSBatchForTest`: explicit-sender append, `"Sender: text"` parsing branch, packetHash dedup + observer accumulation, new-channel append (channel previously unseen), scroll-button branch when user not at bottom, region-filter exclusion code path | All five tests wired into `.github/workflows/deploy.yml` after the existing `test-channel-fluid-e2e.js` step. ### Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → exit 0, all gates pass (PII, CSS vars, branch scope, etc.). Refs #1297 --------- Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com> Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: mc-bot <bot@meshcore.local> |
||
|
|
8151185ede |
fix(ci): Dockerfile COPY invariant check — prevent missing internal/<pkg> Docker failures (#1316) (#1432)
## Summary - Adds `scripts/check-dockerfile-internal-pkgs.sh`: reads `replace => ../../internal/<pkg>` directives from `cmd/server/go.mod` and `cmd/ingestor/go.mod`, then verifies each referenced package has the correct number of `COPY internal/<pkg>/` lines in `Dockerfile` (one per builder section that needs it) - Wired into CI as a step in the `go-test` job, before CSS lint — runs on every PR, adds ~0.1s - Prevents the recurring failure pattern (#1316): new `internal/<pkg>` added to go.mod but COPY line forgotten in Dockerfile; non-Docker CI passes, Docker build fails after merge with a cryptic module error Key details: - Counts COPY occurrences per package: if a pkg is referenced in both go.mods (both binaries need it), it must appear in at least 2 builder sections - Anchored regex: only matches actual `replace` directives (not comments) - Anchored grep: skips commented-out `COPY internal/...` lines Closes #1316. ## Test plan - [ ] Run `bash scripts/check-dockerfile-internal-pkgs.sh` locally — exits 0 on current Dockerfile - [ ] Manually remove a `COPY internal/perfio/` line from Dockerfile → script exits 1 with a clear error - [ ] CI step visible in the `go-test` job on this PR 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
cc37f9f689 |
fix(ci): stop cancelling master runs — only cancel stale PR builds (#1426)
## Summary - `cancel-in-progress: true` was silently killing staging deploys whenever a new commit landed on master during an active CI run - During burst-merge sessions (7 cancelled runs documented in #1395), staging drifted hours behind master with no failure signal (cancelled = grey, not red) - Fix: evaluate to `true` only for `pull_request` events, so PR branches still drop stale runs but master runs always complete ## Test plan - [ ] Verify expression evaluates correctly: PRs → `true` (cancel stale), master push → `false` (never cancel), `workflow_dispatch` → `false` (let manual runs complete) - [ ] Manually trigger: merge 3 PRs in quick succession, confirm all 3 staging deploys complete - [ ] Confirm no master CI run shows `cancelled` status after the fix Closes #1395 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
94f004909c |
fix(#1438): migrate marker fills to CSS vars + write --mc-role-* in customizer (#1439)
## Summary Fixes #1438. Map + Live node markers and customizer per-role overrides did not honor CB-preset switches because: - SVG markers baked `ROLE_COLORS[role]` hex into `fill=` attribute at marker creation. Existing markers were stale until full page reload after `MeshCorePresets.applyPreset(...)`. - `setRoleColorOverride` only mutated the JS `_roleOverrides` map; the `--mc-role-{role}` CSS var (source of truth for cluster pills, route lines, all CSS-var-driven surfaces) was never updated, so operator picks were invisible to those surfaces. ## Fix shape Empirically verified in headless chromium: CSS-var-on-SVG-fill **does** repaint mounted elements when the variable value changes. Pure CSS-var migration is sufficient — no `cb-preset-changed` listener needed on the marker layers. - **`public/roles.js makeRoleMarkerSVG`** — default fill is now `var(--mc-role-{role})`; callers passing an explicit colour (matrix mode, stale dim) still win. - **`public/map.js makeMarkerIcon` + observer star overlay** — same migration to `var(--mc-role-{role})` / `var(--mc-role-observer)`. - **`public/live.js addNodeMarker`** — passes `null` to `makeRoleMarkerSVG` so the var path is used; inline fallback SVG also uses the var. - **`public/roles.js setRoleColorOverride`** — now writes `--mc-role-{role}` on `documentElement.style`. On clear, restores the preset value captured at first-override time, preserving #1412's contract ("clearing override reverts to active preset"). ## TDD Red commit: `test-issue-1438-marker-css-vars.js` asserts the CSS-var contract across all four files. Failed 5 assertions on `master`: - `makeRoleMarkerSVG emits var(--mc-role-X) in default fill path` - `makeMarkerIcon body references var(--mc-role-*)` - `observer star overlay uses var(--mc-role-observer)` - `addNodeMarker body references var(--mc-role-*)` - `setRoleColorOverride body writes --mc-role-{role} CSS var` Green commit: code fix → all 13 assertions pass. ## Verification - `test-issue-1438-marker-css-vars.js` (new) — 13/13 pass - `test-issue-1407-cb-preset-propagation.js` — 61/61 pass (no regression) - `test-issue-1412-customizer-no-override.js` — 13/13 pass (clear-override-restores-preset contract preserved by `_presetCssSnapshot`) - `test-marker-outline-weight.js` — 6/6 pass - Full `test-all.sh` — same pre-existing pass/fail count (no new failures introduced) Browser verified: CSS-var-on-SVG-fill repaint behavior confirmed live in headless chromium (about:blank test svg, `setProperty('--test-color', '#0000ff')` flips a mounted `<rect fill="var(--test-color)">` from red to blue without re-mount). Staging hot-deploy + CDP verification will happen post-merge (per fix-issue playbook). ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` — all gates clean. --------- Co-authored-by: OpenClaw Bot <bot@openclaw> |
||
|
|
777f77a451 |
feat(#1420): dark-tile provider picker in customizer (4 variants) (#1430)
# feat(#1420): dark-tile provider picker in customizer (4 variants) Closes #1420. ## What Operator pick: don't force a single dark-tile choice on everyone. Wire 4 candidates into the customizer + server config so users can choose which dark basemap they want, with per-browser persistence. ## Providers shipped | ID | Source | Filter | |---|---|---| | `carto-dark` (default) | `https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png` | none | | `esri-darkgray-labels` | Esri Dark Gray Base + Reference (two stacked layers) | none | | `voyager-inverted` | Carto Voyager + CSS `invert(1) hue-rotate(180deg) brightness(0.9) contrast(1.05)` on `.leaflet-tile-pane` | applied in dark, cleared in light | | `positron-inverted` | Carto Positron + same CSS invert | applied in dark, cleared in light | No new dependencies — all providers are URL-only. ## Architecture - **`public/map-tile-providers.js`** — registry + 5 public helpers (`MC_TILE_PROVIDERS`, `MC_setDarkTileProvider`, `MC_getDarkTileProvider`, `MC_setServerDefaultTileProvider`, `MC_applyTileFilter`). Persists to `localStorage['mc-dark-tile-provider']`. Dispatches `mc-tile-provider-changed` on user pick. - **`public/map.js` / `public/live.js`** — resolve the active dark provider via the registry, manage the Esri labels overlay lifecycle (add when needed, remove cleanly so we don't leak layers on repeated theme toggles), and apply/clear the CSS filter on `.leaflet-tile-pane`. Listen for both `data-theme` mutations AND `mc-tile-provider-changed`. - **`public/customize-v2.js`** — new "Dark Map Tiles" dropdown in the Display tab. On change, calls `MC_setDarkTileProvider(id)`; the maps re-render live without reload. - **`public/roles.js`** — hydrates the server default via `MC_setServerDefaultTileProvider` from `/api/config/client`. - **Server (`cmd/server/`)** — new `mapDarkTileProvider` string on `Config` + surfaced in `ClientConfigResponse`. Default empty → client uses `carto-dark`. - **`config.example.json`** — documents the new field with all allowed values. ## Behavior guarantees (from the acceptance criteria) - ✅ Light mode is **completely unchanged** — `_resolveTileUrl(false)` short-circuits to `TILE_LIGHT` with no filter and no overlay logic. - ✅ Switching dark→light always clears the CSS filter, even if an inverted provider remains selected (`MC_applyTileFilter` is called on every theme change and early-returns to `style.filter = ''` when not dark). - ✅ Switching light→dark with an inverted provider re-applies the filter. - ✅ Attribution is updated per provider (Esri credit for Esri, CartoDB credit for the others); the Leaflet attribution control is refreshed. - ✅ Esri uses two stacked layers (base + reference labels). The reference layer is added/removed cleanly so repeat toggles do not leak. - ✅ Customizer change → immediate re-render, no reload. Uses the same "live setting + persist + dispatch event" pattern as cb-presets (#1361). ## TDD - Red commit: `148b71c3` — `test(#1420): add failing tests for dark-tile provider registry (red)` — 6/7 assertions fail (stub only returns nulls). - Green commit: `49ffb230` — `feat(#1420): dark-tile provider picker — 4 variants wired into customizer` — 7/7 pass. ## Tests `test-issue-1420-tile-providers.js` (wired into `test-all.sh` and `.github/workflows/deploy.yml` JS-unit step): ``` ── #1420 Dark-tile provider registry ── ✅ MC_TILE_PROVIDERS has all 4 IDs with url + attribution ✅ Inverted providers have non-null invertFilter; non-inverted have null ✅ MC_setDarkTileProvider persists to localStorage and dispatches mc-tile-provider-changed ✅ MC_setDarkTileProvider rejects unknown IDs (no persistence, no dispatch) ✅ MC_getDarkTileProvider falls back to server default, then carto-dark ✅ Apply filter for inverted provider in dark mode; clear when switching to non-inverted ✅ Light mode always clears the CSS filter even if inverted provider is selected 7 passed, 0 failed ``` `cd cmd/server && go build ./... && go vet ./...` — clean. ## CDP verification Not run in this PR — the sandbox does not have a Chrome CDP endpoint reachable, and staging cannot exercise this code path until this branch is deployed. The issue body's "CDP-verified candidate set" table covers prior provider-URL validation; the new code path (registry lookup + filter swap + Esri overlay lifecycle) is covered by the unit tests above. **Recommend operator run a quick manual verification on staging post-deploy:** dark mode → open customizer → cycle through all 4 providers, confirm tiles render and the CSS filter is applied for `voyager-inverted` / `positron-inverted` (verify via `getComputedStyle(document.querySelector('.leaflet-tile-pane')).filter`). ## Files touched - `public/map-tile-providers.js` (new) - `public/map.js`, `public/live.js`, `public/customize-v2.js`, `public/roles.js`, `public/index.html` - `cmd/server/config.go`, `cmd/server/routes.go`, `cmd/server/types.go` - `config.example.json` - `test-issue-1420-tile-providers.js` (new), `test-all.sh`, `.github/workflows/deploy.yml` - `.eslintrc.json` (register new `MC_*` globals) --------- Co-authored-by: openclaw <bot@openclaw.local> |
||
|
|
77d1925f30 |
Route view v2 — Tufte redesign (packet context, multi-path picker, mobile bottom-sheet, CB-preset live colors) (#1423)
# Route view v2 redesign Fixes #1418, Fixes #1419, Fixes #1422 This is the route-view redesign that came out of a long iterative QA cycle. The first commit (`a3c39636`) landed the v1 sidebar timeline + multi-path baseline; this PR's second commit (`0e2e913f`) is the v2 polish covering packet context, multi-path picker, mobile bottom-sheet, CB-preset live colors, and dozens of operator-driven UX fixes. ## The journey, in one line > "The data is a sequence. Geography is annotation. The packet is the cargo, the route is the road — show both." ## New surfaces ### 1. Packet context block (sidebar header) Above the multi-path chip, a per-type fact list explaining **what** is traveling. Operator was tired of "the route view shows the road but not the cargo." | Type | Chip | Facts | |-------------|-----------------|---------------------------------------------------------| | ADVERT | 📡 ADVERT | name · role · sig ✓ · self-reported GPS · pubkey prefix | | TXT_MSG | ✉ DM | src → dst · 🔒 encrypted | | REQ/RESPONSE| 🔒/🔓 REQUEST/…| src → dst · 🔒 encrypted | | GRP_TXT | # CHANNEL MSG | #channel · 🔓 decrypted · "…content preview…" · sender | | TRACE | ⌖ TRACE | Official: N hops · Observed: M | | PATH | 🔀 PATH | src → dst (with "from payload" chip on SRC/DST rows) | Sources merge `pkt.decoded_json` + `obs.decoded_json` (channel data often lives at packet level) and fall back to byte-level `raw_hex` parsing for encrypted DMs and unkeyed channel msgs. ### 2. Multi-path picker The header lists every unique observer-path with `<count>/<total>` chip + hex hop string. Click a path → full-clear and redraw that path only (Tufte v6's "replace + retain subpath weights"). "All" → edge-deduplicated UNION view (each unique edge drawn once, stroke = observer count, single accent color, no seq numbers because there's no single ordering). ### 3. Deep-link URLs `#/map?packet=<hash>&obs=<id>` — bookmarkable, shareable, the single source of truth. sessionStorage flow removed. "Back to packet" preserves the obs id. ### 4. Hop resolution Priority: server `resolved_path` → shared `window.HopResolver` (same resolver as packets page, observer-IATA-aware) → raw prefix. Eliminates a whole class of "route view named hops differently than packet detail" bugs. ### 5. Markers (v5/v6/v7) - All markers same 22 px filled circle, seq number rendered **inside** - SRC + DST get a 2 px hollow endpoint ring - SRC = DST loop → **double concentric ring** (ring grammar extended, no new glyph) - Spider-fan within 14 px collisions (16 px arc, dashed hairline), re-runs on `zoomend` only, debounced ### 6. CB preset live colors - Each preset gets a `routeRamp` (5 stops): default/trit = viridis, deut/prot = plasma, achromat = pure luminance - `cb-presets.js` writes `--mc-rt-ramp-0..4` CSS vars; route reads them via `getComputedStyle` - `cb-preset-changed` + `theme-changed` listeners hot-recolor without re-render ### 7. Desktop chrome - **Resize handle** on right edge of sidebar (drag, persisted to `localStorage["mc-rt-sidebar-width"]`) - **Collapse button** = round chevron **centered on the right edge** (Material/Drive style — not in the top-right corner, doesn't collide with the close X) - Collapsed = 36 px strip with rotated "ROUTE" label, expand on click ### 8. Mobile (bottom sheet) - Anchored above bottom-nav (`bottom: 56px + safe-area-inset`) - Collapsed = thin summary line `TYPE · N hops · X km · M obs` + hex preview, tap chevron to expand to ~75 vh - Drag-grip removed (conflicted with browser pull-to-refresh + CoreScope's own pull-to-reconnect) - Desktop collapse / resize affordances hidden on mobile (sheet is the mobile collapse affordance) - Map controls toggle floats top-right, panel collapses on route entry, reachable via toggle click - All three mobile detail panels (`pktRight`, `.slide-over-panel`, `#mobileDetailSheet`) explicitly closed when entering route view ### 9. Map fit / centering - Manual layer-children walk because `L.LayerGroup.getBounds()` doesn't aggregate (only `FeatureGroup` does) - Mobile padding: `paddingTopLeft: [30, 70]`, `paddingBottomRight: [30, 190]` to clear top-nav + sheet+nav stack - Re-fits on: initial render, isolate, All, `window.resize` (iOS URL-bar collapse) - Staggered timers 0/200/600/1400 ms (and 2800 ms on initial render) to survive layout settles ### 10. Hop drill-in refinements - SNR sparkline suppresses connecting polyline when n < 3 (two points implies a trend across time it can't represent — dots only) - "Node details" link properly chip-styled with aria-label including node name + route count ## Edge weight scales | View | Range | |---------------------------------|----------------| | Single-path | 5 px flat | | Multi-path interior | 3..9 | | Origin→hop1 / last-hop→dest | proxy via max adjacent edge count | | Union overlay | 2..8 | Boundary edges (SRC→first hop, last hop→DST) used to render thin because `edgeCounts` only tracks `path_json` transitions. Now they take the strongest adjacent edge count as proxy (every observer who saw the packet implicitly transited that boundary edge). ## Files - **NEW** `public/route-tufte.js` (~1700 lines) — the route renderer + sidebar - **NEW** `public/route-tufte.css` (~750 lines) — all styling - **MOD** `public/map.js` — async draw functions, deep-link loader, `__mc_nodes` exposure, raw_hex extraction - **MOD** `public/packets.js` — View Route → deep-link URL only, closes all mobile panels - **MOD** `public/cb-presets.js` — `routeRamp` per preset + CSS var write - **MOD** `public/index.html` — script + stylesheet tags ## Testing Manually CDP-validated across desktop and mobile-emulator viewports for every major change. Fixtures cover: - ADVERT (4 hops, single-obs) - DM (TXT_MSG, raw_hex parse) - GRP_TXT (#test channel, decrypted text) - PATH (operator's bug case) - TRACE (3-hop) - 1-hop edge case - Multi-path (75-observer 4-hop with 47 unique paths) - 32-hop stress - Loop (SRC = DST) - Bay Area dense cluster (spider-fan) Per AGENTS.md net-new-UI exemption, no failing-test-first; existing tests stay green. **TODO**: Playwright E2E follow-up PR. ## What's deferred to v2.1 / follow-ups - **Glyph overlay on SRC marker** for packet type (e.g. 📡 corner glyph on ADVERT marker, ⌖ on TRACE) - **Per-hop SNR sparkline for TRACE packets** (their payload contains real per-hop SNR contributions, distinct from observer-derived SNR) - **GRP_TXT full content preview** (currently truncated at 80 chars; could expand inline) - **Playwright E2E test** covering the deep-link → isolate → All flow ## Screenshots (would be useful here — CDP screenshots captured during dev show: desktop with sidebar + multi-path picker, mobile with bottom sheet + overlay toggle, isolated-path view, union view, spider-fan on Bay Area cluster, packet context for each of the 5 main types) ## Operator's frustration patterns (lessons for next time) 1. **Browser-validate every UI change, not just compute state** — CDP-screenshot before claiming a UI fix is done. Verifying `display:none` resolves correctly is necessary but not sufficient; the visual layout matters. 2. **Edge-deduplicated drawing beats per-path overlays** for union views (Tufte v6) — operator's instinct was correct from the start. 3. **Material/Drive UI conventions exist** because they work — center collapse handles on borders, don't pile them in corners. 4. **Mobile = different problem than desktop** — bottom-sheet, no drag-grip near pull-to-refresh zone, asymmetric fitBounds padding, redundant refits to survive iOS URL-bar collapse. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
0986caaa44 |
fix(#1412): customizer nodeColors stops force-overriding ROLE_COLORS — CB presets now actually propagate (#1414)
WIP — red commit only. Reproduces #1412. ## TDD red phase `test-issue-1412-customizer-no-override.js` asserts that after `MeshCorePresets.applyPreset('deut')` and a server-config push of legacy `nodeColors`, `window.ROLE_COLORS.repeater === '#FE6100'`. On master this fails because `customize-v2.js:553` pushes server-config into the `_roleOverrides` map, which the live getter prefers over CSS vars. Green commit (customize-v2.js + customize.js fix) follows. Refs #1412 --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
89410d58b4 |
fix(#1413): nav-left + nav-stats overlap at vw~1200 — flex sizing fix (#1417)
## What
Fix the horizontal overlap between `.nav-more-btn` (in `.nav-left`) and
`.nav-stats` (in `.nav-right`) at viewport widths roughly 1101..1599px.
At vw=1200 the count number in the stats badge rendered on top of the
"More ▾" text.
## Root cause
`.top-nav` uses `display: flex; justify-content: space-between;` but had
**no column gap** between its children, and `.nav-links` had **no
flex-grow**. So `.nav-left` only consumed its content's intrinsic width
and `.nav-right` (with `flex-shrink: 0`) was free to abut it. Worse, the
Priority+ measurement loop in `app.js` (`applyNavPriority` → `fits()`)
compared intrinsic widths against `window.innerWidth` while `.top-nav {
overflow: hidden }` masked the actual collision — so the loop happily
declared "fits" while pixels overlapped.
CDP measurement on master at vw=1200 (`/#/packets`):
- `.nav-more-btn` rect: x=499..557 (w=58)
- `.nav-stats` rect: x=496..962 (w=466)
- Gap: **−60.7px** (overlapping)
Fix candidates tested via Chrome DevTools Protocol (`Runtime.evaluate` +
`Emulation.setDeviceMetricsOverride`) across vw=1101, 1200, 1366, 1440,
1600, 1920 (plus 768, 900, 1024, 1080, 1100, 1300, 1500, 1700, 1800 as a
sanity sweep). Winner:
```css
.top-nav { column-gap: 16px; }
.nav-links { flex: 1 1 auto; min-width: 0; }
```
Per-viewport gap (`stats.left - more.right`) baseline → fix:
| vw | baseline | fix |
|------|----------|----------|
| 1101 | −144.0 | **16.0** |
| 1200 | −60.7 | **16.0** |
| 1300 | 8.4 | **16.0** |
| 1366 | 64.2 | 64.2 |
| 1440 | 0.0 | **44.5** |
| 1600 | 24.2 | 24.2 |
| 1920 | more hidden (no overflow) — n/a | n/a |
Single-candidate variants (`.nav-left { flex: 1 1 auto }` alone,
`.top-nav { justify-content: space-between }` alone — already on, no
effect, `.nav-links { flex: 1 1 auto }` alone, margin/padding hacks on
`.nav-right`/`.nav-stats`) all still produced ≤8px gap at vw=1200. Only
the combo (column-gap on parent + flex-grow on `.nav-links`) cleanly
resolves all six required widths.
## TDD
Red commit: `3d374b4c93319805e89e46d8fdc8a8ea8c6c1479` (CI:
https://github.com/Kpa-clawbot/CoreScope/actions/runs/26482870401)
- `test-issue-1413-nav-overlap-e2e.js` — Playwright at vw 1101, 1200,
1366, 1440, 1600, 1920 on `/#/packets`. Asserts `.nav-more-btn.right + 8
<= .nav-stats.left` (when both visible) and that `.top-nav` does not
horizontally scroll. Wired into `.github/workflows/deploy.yml` alongside
the other `test-nav-*-e2e.js` entries.
- Red commit ships ONLY the test (+workflow line); CI fails on the
assertion at vw=1101..1300 and vw=1440 (gap below 8px threshold).
- Green commit applies the two CSS rules above and turns CI green.
## Manual verification
1. Open `http://analyzer-stg.00id.net/#/packets` in a desktop browser.
2. Resize the viewport to ~1200px wide.
3. Confirm the "More ▾" button and the stats badge are visibly separated
(≥16px gap) and the badge count is not stacked on the button text.
4. Repeat at 1101, 1300, 1440, 1600, 1920px — gap ≥16px at all widths
where stats is visible.
5. At ≤1100px confirm `.nav-stats` is still hidden (display:none,
unchanged).
## Scope guards
- No changes to the Priority+ algorithm (`applyNavPriority` / `fits()`
in `app.js`). #1391, #1311, #1139, #1148, #1102, #1055 logic untouched.
- No changes to the More dropdown (`position: fixed`, #1406).
- No changes to `.nav-left { overflow }` (#1405 stayed dropped).
- Mobile (<768px) hamburger layout unchanged.
Fixes #1413
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
|
||
|
|
f72b1bd2ca |
fix(#1409): channels — stop force-enabling 'show encrypted' on every init (#1410)
## What
Delete the unconditional
`localStorage.setItem('channels-show-encrypted', 'true')` call (+
misleading "#1034 PR1: sectioned sidebar" comment) at
`public/channels.js:783-786`. The sectioned-sidebar grouping the comment
referenced was never implemented; in practice the call was
force-flipping the encrypted-visibility gate on every init so an
operator could never turn it off.
## Root cause
`channels.js` init ran:
```js
var showEncrypted = true;
try { localStorage.setItem('channels-show-encrypted', 'true'); } catch (e) {}
```
unconditionally on every load. The `loadChannels()` reader at line ~1563
(`localStorage.getItem('channels-show-encrypted') === 'true'`) then sent
`includeEncrypted=true` on the `/api/channels` call, so the server
returned all 246 encrypted placeholder channels alongside the 19 real
ones — 265 rows flooding the sidebar with no UI control to suppress.
Verified via CDP on staging:
- `localStorage['channels-show-encrypted']` was always `"true"` after
page load.
- `GET /api/channels` → **19** entries (default — encrypted excluded).
- `GET /api/channels?includeEncrypted=true` → **265** entries (246
encrypted).
- Manually `removeItem('channels-show-encrypted')` + reload → list
dropped to 19.
Confirmed the force-set was the only gate driving the flood.
## TDD
- RED commit `a71cecbc` — `test-issue-1409-no-encrypted-flood.js`
source-greps `public/channels.js` for the forbidden literal
`setItem('channels-show-encrypted', 'true')`. Asserts no match. Fails on
master.
- GREEN commit `14281b63` — delete the 2 lines + rewrite comment. Test
passes.
Tests:
```
$ node test-issue-1409-no-encrypted-flood.js
Issue #1409 — no force-enable of channels-show-encrypted
✅ channels.js does NOT unconditionally setItem(channels-show-encrypted, true)
✅ channels.js still reads channels-show-encrypted (toggle gate preserved)
2 passed, 0 failed
```
## Manual verification
- After fix, default `localStorage.getItem('channels-show-encrypted')`
is `null` on first load.
- `loadChannels()` reader returns `false`, so `includeEncrypted` is
omitted from the API call → server returns the 19 real channels only.
- Existing reader is preserved, so a future user-facing toggle that
writes the flag will continue to work.
## Out of scope (follow-ups)
- "Show encrypted" header toggle UI — issue acceptance criteria mentions
it as optional; not added here.
- Sectioned-sidebar grouping of encrypted channels (#1034 PR1 design) —
separate issue.
- Cap/collapse behavior when toggle is ON — separate issue.
Fixes #1409
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
|
||
|
|
52b6dd82ac |
fix(#1407): cb-preset propagation via live ROLE_COLORS getter + per-role text color for WCAG AA (#1408)
WIP — RED commit only. Tests demonstrate two bugs from #1407: 1. `window.ROLE_COLORS` is a static literal (legacy April palette), not synced to `--mc-role-*` CSS vars. 2. Achromat preset pairs `#1a1a1a` text with 3 dark grays → WCAG 1.4.3 fails (1.27 / 2.55 / 4.43). Expect CI red on `test-issue-1407-cb-preset-propagation.js` assertion failures (not compile errors). GREEN follows. Refs #1407 --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
7e492a71a0 |
fix(#1400): root cause of recurring nav-vanishing — min-height:48px overflowed 52px top-nav, clipped link strip above viewport (#1401)
**RED commit phase** — TDD failing test for #1400. Green fix incoming next push. See full PR body on ready-for-review. Fixes #1400 --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
f0a7ed758f |
fix(#1391): Priority+ nav — active-route pill must NEVER drop high-priority links into orphaned More dropdown (#1394)
## What
Pins the active-route `.nav-link` inline at any viewport ≥768px so
Priority+ never shoves it into the More dropdown. Fixes the operator's
screenshot of `/#/perf` at ~1080px where the navbar showed only the
active "Perf" pill missing — and an inverse failure where the active
pill was the only thing **in** the dropdown.
This is the 20th regression of nav Priority+. Single-loop fix only; no
algorithm redesign (per issue out-of-scope).
## Root cause
`public/app.js` `applyNavPriority()` had two places that ignored the
active state:
1. **≤1100 narrow-desktop CSS branch (line ~1197):** `if
(a.dataset.priority !== 'high') a.classList.add('is-overflow')` blindly
overflowed every non-high link — including the active pill.
2. **>1100 measurement loop (line ~1267):** `overflowQueue` is `non-high
reversed + high reversed`. The active non-high link enters the queue and
the loop's only break condition is `priority === 'high'`. fits() keeps
returning false (active pill is wider — has the `.active`
background/padding), so the loop walks the entire non-high tail and
orphans the active route in More.
The acceptance criterion "Active-route pill MUST always be visible
inline" was never encoded — #1311's floor only protected
`data-priority="high"`.
## Why prior #1311 / #1148 / #1139 floors didn't catch this
- **#1311** floored at `data-priority="high"` only. `/#/perf` is
`data-priority=""` so it had no protection.
- **#1148 / #1139** floored the *More menu* at ≥2 items but didn't
constrain *which* links could be promoted/dropped.
- **#1106** narrow-desktop CSS branch (≤1100) was written before
active-pill width drift was a known issue.
## Fix
One conceptual rule applied at three points:
1. In `overflowQueue` construction, skip any link with `.active` (treat
active like high-priority — never enqueue).
2. In the ≤1100 CSS branch, skip the active link when assigning
`.is-overflow`.
3. In the >1100 loop, also break on `.active` (defensive — queue already
excludes it).
Approach chosen over "pin active-pill max-width during measurement":
measurement-pinning would silently shrink the pill visually mid-resize,
and width drift from #1378's new `--mc-*` vars made that fragile.
Treating active as a hard inline pin matches the documented contract and
is one greppable invariant.
## TDD red → green
- **Red commit `34d69012`:** added `test-nav-priority-1391-e2e.js`
covering `/#/perf, /#/audio-lab, /#/analytics, /#/observers` at `1024,
1080, 1100, 1101, 1200, 1300px`. Asserts (1) active pill not in
overflow, (2) all 5 high-pri still inline (#1311 guard), (3) every
overflowed link mirrored in More dropdown (no orphans). 0/24 passed
locally on red.
- **Green commit:** same test 24/24 pass. Existing #1311 (20/20), #1139
floor, #1102 contract still green.
## Manual verification
Local fixture server (`./corescope-server -port 13581 -db
test-fixtures/e2e-fixture.db -public public`):
- `/#/perf` @ 1080×800: brand + 5 high-pri inline + "Perf" pill inline +
"More ▾" containing the 5 low-pri links (Channels, Tools, Observers,
Analytics, Audio Lab). ✅
- `/#/perf` @ 1300×800: brand + 5 high-pri + "Perf" inline; More hidden
(only 4 low-pri items overflow). ✅
- `/#/perf` @ 800×800 (narrow): hamburger code path untouched. ✅
- Inverse `/#/home` @ 1080×800 (active IS high-pri): no behaviour
change. ✅
## Preflight
`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
— exit 0.
Browser verified: local fixture server + Playwright on Chromium
(`/usr/bin/chromium`).
E2E assertion added: `test-nav-priority-1391-e2e.js:138-148`
(`activeOverflowed === false`).
Fixes #1391
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
|
||
|
|
aa63a478a7 |
fix(#1392): test-live.js — load packet-helpers.js in makeLiveSandbox, wire into CI (#1393)
## Root cause `makeLiveSandbox()` in `test-live.js` didn't load `public/packet-helpers.js`, so `window.getParsedDecoded` / `getParsedPath` were undefined. The `dbPacketToLive` and `expandToBufferEntries` suites failed all 8 assertions with `getParsedDecoded is not a function`. The `expandToBufferEntriesAsync` suite was unaffected because it builds its sandbox manually and already loads packet-helpers.js. ## Fix - `test-live.js`: load `public/packet-helpers.js` in `makeLiveSandbox()` before `live.js`. Mirrors the working pattern in `expandToBufferEntriesAsync`. - `.github/workflows/deploy.yml`: wire `node test-live.js` into the "Run JS unit tests" step so this can't silently regress again. - Adjusted one cross-realm `deepStrictEqual([], [])` → `.length === 0` because the array literal lives inside the vm sandbox; host-side `deepStrictEqual` rejects the proto mismatch even when the value is semantically equal. Test-harness only. No production code change. ## Mutation verification With the new `loadInCtx(ctx, 'public/packet-helpers.js')` line removed, all 8 original assertions return (`getParsedDecoded is not a function`). With the fix in place, `node test-live.js` exits 0 — 95 passed, 0 failed. ## CI wire `node test-live.js` now runs in deploy.yml under "Run JS unit tests (packet-filter)" alongside the other root-level test files. YAML validated with `yaml.safe_load`. Fixes #1392 Co-authored-by: openclaw-bot <bot@openclaw.dev> |
||
|
|
ff0ee50354 |
fix(#1374): packet-route map modernized — role-aware markers, directional edges, WCAG 2.2 AA (#1381)
## What The packet-route map view (`/#/map?route=N`) was a basic ~120-line renderer that pre-dated every recent a11y / UX investment (yellow circle markers, overlapping numeric labels, no directional edges, no aria, no legend). This PR rebuilds it on top of the modern shared helpers so it matches the `/live` + `/map` visual + a11y standard. Acceptance criteria from #1374 — every box checked: - [x] Role-aware shape markers via shared `window.makeRoleMarkerSVG` (post-#1357). - [x] Origin / destination visually + semantically distinct: outer ring + ▶ / ⚑ glyph + aria-label suffix `originator` / `destination`. - [x] Sequence-number badges (`.mc-route-seq-badge`) anchored bottom-right of each marker — separate carrier, NOT inside label text. - [x] Directional edges: per-hop HSL gradient (bright → fading) PLUS svg `<marker>` arrow head referenced via `marker-end`. Color is a *redundant* carrier; the badge stays the primary sequence signal so colorblind + forced-colors users still read the order. - [x] Per-edge `aria-label="Hop N → N+1, ~Xkm"` (haversine computed). - [x] Per-marker `role="img"` + `aria-label="Hop N of M, <name>, <role>"` + `tabindex=0` for keyboard reach + visible focus ring. - [x] Label deconfliction reuses `window.deconflictLabels` (now exposed by `map.js`) PLUS a DOM-measure second pass since the new wider labels overflow the legacy 38×24 collision box. - [x] Collapsible `.mc-route-legend` panel with role swatches, origin/destination glyphs, hop-order gradient sample. Toggle has `aria-expanded`. - [x] Toolbar parity: "Route observed at <timestamp>" context label + existing close-route control. - [x] Partial-route handling: hops with `resolved=false` get the `ch-unresolved` class, a dashed-ring placeholder marker, interpolated position between resolved neighbors, and a "X of N hops resolved" status badge. - [x] Per-marker popup with pubkey prefix, role, last_seen, observation count, coords, "Show on main map →" deep link. - [x] `prefers-reduced-motion: reduce` disables animations/transitions. - [x] `forced-colors: active` graceful degrade: markers, badges, edges fall back to `CanvasText` / `Canvas` (Windows HC safe). ## How Split the renderer into a dedicated `public/route-render.js` exposing `window.MeshRoute.render(map, layer, positions, opts)`. The existing `drawPacketRoute` in `map.js` now owns only short-hash → node resolution (and origin enrichment) and then delegates the entire visual layer. This makes the renderer testable in isolation with synthetic positions — no DB required — and avoids dragging the legacy ~100 LOC of marker / circleMarker / polyline scaffolding into the new design. Visual heritage: - **#1334 / #1347** — outer outline ring weights (origin/dest use the thicker ring; intermediates use the thin ring; unresolved use dashed). - **#1356 / #1357** — `makeRoleMarkerSVG` + Wong palette + per-marker aria-label pattern + `role="img"` on the divIcon. - **#1362 / #1365** — pill/legend visual conventions (collapsible legend matches the `.mc-section` accordion language users already know from `/map`). ### WCAG 2.2 AA — measured contrast (graphics SC 1.4.11, text SC 1.4.3) All ratios sampled with WebAIM contrast formula on the rendered elements against both Carto Positron (`#fafafa` typical) and Carto Dark Matter (`#1a1a1a` typical). | Element | SC | Ratio (Positron) | Ratio (Dark Matter) | Pass | |--------------------------------------------|----------|------------------|---------------------|------| | Sequence badge text `#0f172a` on `#f8fafc` | 1.4.3 AA | 17.1:1 | 17.1:1 (self-bg) | ✅ | | Sequence badge border `#1a1a1a` | 1.4.11 | 17.6:1 | 12.6:1 | ✅ | | Marker outer ring `#06b6d4` (origin) | 1.4.11 | 3.2:1 | 4.6:1 | ✅ | | Marker outer ring `#ef4444` (destination) | 1.4.11 | 3.8:1 | 4.4:1 | ✅ | | Marker outer ring `#666` (intermediate) | 1.4.11 | 5.7:1 | 3.7:1 | ✅ | | Edge stroke (seq color, mid: `#56c08c`) | 1.4.11 | 3.0:1 (min) | 3.1:1 | ✅ | | Edge arrow head (currentColor) | 1.4.11 | same as edge | same | ✅ | | Label text `#0f172a` on `#f8fafc` | 1.4.3 AA | 17.1:1 | 17.1:1 (self-bg) | ✅ | | Legend body text `#0f172a` on `#f8fafc` | 1.4.3 AA | 17.1:1 | 17.1:1 (self-bg) | ✅ | | Resolved badge `#78350f` on `#fef3c7` | 1.4.3 AA | 8.4:1 | 8.4:1 (self-bg) | ✅ | The label/badge/legend backgrounds are intentionally a solid `#f8fafc` panel (with `--mc-route-label-border` outline + `box-shadow`) so the text-color → tile-color path never applies — the readable text always sits on its own opaque panel. For SC 1.3.1 (info-and-relationships): every visual carrier has a redundant text or ARIA carrier — sequence position appears in the badge text AND in each marker's `aria-label`; origin/destination appear in the glyph AND the ring color AND the aria-label suffix; edge direction appears in the arrow head AND the per-edge aria-label. ### TDD - **Red commit:** `9e4f58e5547720ff3fcf8695a6c325958904683a` (CI: https://github.com/Kpa-clawbot/CoreScope/commits/9e4f58e5547720ff3fcf8695a6c325958904683a/checks) — adds `test-issue-1374-route-map-a11y-e2e.js` only. The test calls `window.MeshRoute.render(...)` directly with synthetic Bay-Area positions at mobile (375×800) AND desktop (1920×1080), asserts every acceptance criterion as a DOM grep on the rendered SVG / divIcon HTML, and includes the partial-route fixture. Fails on the assertions because `MeshRoute` doesn't exist on master. - **Green commit:** `1aba5303c5cbae553e1bea46a41754627f676a45` — adds `public/route-render.js`, refactors `drawPacketRoute` to delegate, adds `.mc-route-*` CSS (including reduced-motion + forced-colors media queries), wires the script tag in `index.html`, and wires the test into `.github/workflows/deploy.yml`. ### Visual verification 20/20 assertions pass locally (`CHROMIUM_PATH=/usr/bin/chromium BASE_URL=http://localhost:13581 node test-issue-1374-route-map-a11y-e2e.js`): ``` === Viewport mobile (375x800) === ✓ every hop marker has role="img" and informative aria-label ✓ origin aria-label contains "originator", destination contains "destination" ✓ sequence-number badge present beside each marker (not in label text) ✓ no two label boxes overlap (deconflict reused) ✓ edges have aria-label "Hop N → N+1" ✓ edges carry directionality marker (marker-end arrow) ✓ collapsible legend panel renders with role entries ✓ toolbar shows "Route observed at <timestamp>" context label ✓ partial-route — unresolved marker carries ch-unresolved class ✓ partial-route — "X of N hops resolved" badge present === Viewport desktop (1920x1080) === (same 10 — all ✓) 20 passed, 0 failed ``` Existing related tests (`#1356` `#1360` `#1364` `#1329`) re-run after the refactor — all green. ## Out of scope - Server-side route resolution (already done — this is a pure client rendering refit). - Multi-route view / 3D / globe — explicitly excluded by the issue. - Backend untouched — `cmd/server` + `cmd/ingestor` not modified. Fixes #1374 --------- Co-authored-by: openclaw-bot <bot@openclaw> |
||
|
|
101c11b4b3 |
fix(#1361): theme customizer — colorblind presets [WIP] (#1378)
WIP — draft PR for CI to exercise the RED test commit. Will be promoted
out of draft once the GREEN commit lands.
Red commit:
|
||
|
|
ec98a43d68 |
feat(ci): frontend eslint no-undef gate — catches renamed-function-caller class of bugs (fixes #1342) (#1344)
**TDD:** red commit `03ea965` (canary undef var → CI fails) → green commit `b514aeb` (canary removed → CI passes). CI URL appears in the Checks tab once GitHub Actions queues this branch. `Fixes #1342` ## What ships - **`.eslintrc.json`** at repo root — eslint 8 legacy-config format. `no-undef: error`, `no-unused-vars: warn` (with `^_` allowlist). - **CI step** in `.github/workflows/deploy.yml` (job `go-test`, after JS unit tests, before proto + Playwright): `npm install --no-save eslint@8 && npx eslint public/*.js`. `--no-save` keeps `node_modules` and `package-lock.json` out of the tree (already gitignored). - **One pre-existing fix** in `public/map.js`: `typeof esc === 'function'` → `typeof globalThis.esc === 'function'`. `esc` is a *local* IIFE var in 5 other files, never exported as a true global; the optional lookup was structurally invalid under `no-undef`. Behavior unchanged. ## How this would have caught #1318 / PR #923 PR #923 renamed `drawAnimatedLine`, updated one caller in `public/live.js`, missed the other — leaving a reference to the undefined `hash` var. Playwright didn't hit that path. Reverting #1325 locally (re-introducing the bug) → eslint flags `hash` as `no-undef` → red. With the gate in place, #923 never lands. ## The "quiet pile of globals" reality The config declares **257 globals**. They were discovered by walking `public/*.js` for two patterns: 1. `window.X = ...` assignments (the explicit exports — 168 of them) 2. Top-level `function`/`const`/`let`/`var` declarations in non-IIFE files (the implicit exports — Go-style cross-file linking via shared HTML `<script>` order) Plus 9 vendor/runtime names (`L`, `Chart`, `QRCode`, `qrcode`, `module`, `global`, `process`, `require`, `exports`, `__filename`, `__dirname`) for dual-runtime files like `url-state.js`, `packet-filter.js`, `hash-color.js`, `filter-ux.js` that are also `require()`-d by Node tests. This is honest documentation of an architectural reality, not a workaround. Future refactor → modules will collapse this list. ## Latent bugs discovered **Zero `no-undef` errors against the current `public/*.js` tree** after globals were enumerated honestly. The would-be-#1318-class bug count today: 0. The gate's job is forward-looking — block the next one. ## Out of scope (acknowledged from acceptance criteria) - Inline `<script>` blocks in `public/*.html` — separate ticket. - Per-PR delta-coverage gate — separate ticket. - pr-preflight grep for arg-count mismatch — separate ticket. ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → exit 0, clean. --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
791c8ae1bc |
fix(#1367): channels page chat-app redesign — restore prod row layout, drop analytics chip, add detail view (#1376)
Red commit:
|
||
|
|
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:
|