mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-04 10:22:01 +00:00
dae08a0ebbbeba2b26993b11852f736d68f7fbd8
840 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
467b01a1b3 |
fix(#1285): exclude RTC-reset outliers from clock-skew hash median + recent bad count (#1288)
Red commit:
|
||
|
|
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> |
||
|
|
21b6eb0d63 |
fix(live legend): document ACK/RESPONSE/PATH + white-ring repeater convention (#1274) (#1276)
RED commit `ac1fb4c3` (Playwright E2E asserts legend rows for ACK / RESPONSE / PATH text + "ring" + "repeater" — fails on master). CI: https://github.com/Kpa-clawbot/CoreScope/actions?query=branch%3Afix%2Fissue-1274 ## What The Live legend rendered five packet-type rows but the codebase defines eight `TYPE_COLORS`. The three gray-area types (ACK, RESPONSE, PATH) had no swatch in the legend, leaving operators guessing what gray dots meant — they're either ACKs or unknown payload types. Separately, the L.circleMarker styling block uses a brighter white ring to mark repeaters vs. all other roles; that convention was nowhere on screen. ## Changes - `public/live.js` legend HTML — adds rows for RESPONSE, PATH and a combined **Ack / Other** row (covering both ACK and the unknown-type fallback that share `#6b7280`). Adds a new **MARKER STYLES** subsection below NODE ROLES with two entries: bright white ring = repeater, faded ring = other. - `public/live.css` — adds `.live-ring` / `.live-ring--repeater` / `.live-ring--other` swatches. Background uses `var(--text-muted)`; only the white border + opacity differ between the two, matching the actual circleMarker weights (1.5 / 0.5) and opacities (0.6 / 0.3). - `test-issue-1274-legend-coverage-e2e.js` — Playwright E2E (desktop + mobile attached-DOM) asserting all four new pieces. ## Notes - All colors via `TYPE_COLORS` — no hardcoded hex in HTML. - Legend is `display:none` at ≤640px (existing #279 behavior), so no mobile CSS tweak required for the longer list. - Does not touch the legend toggle (#1219), mobile single-row header (#1234), or VCR visibility (#1269). Fixes #1274. --------- Co-authored-by: corescope-bot <bot@meshcore.local> |
||
|
|
8bf7709970 |
feat(repeater): usefulness score — bridge axis (#672 axis 2 of 4) (#1275)
RED test commit: `fd661569` — CI will fail on this (stub returns empty map; assertions fail by design). GREEN: `bf4b8592`. ## What Implements **axis 2 of 4** for the repeater usefulness score per #672 ([status comment](https://github.com/Kpa-clawbot/CoreScope/issues/672#issuecomment-4484635378)). The Bridge axis measures *structural importance*: how many shortest paths between other nodes route through this one. A high-traffic redundant node and a low-traffic critical bridge will no longer look identical. ## Algorithm **Brandes' weighted betweenness centrality** with Dijkstra for shortest paths (`cmd/server/bridge_score.go`). - Nodes: pubkeys in the `neighbor_edges` graph - Edge weight: `Score(now) * Confidence()` — per the convention from #1235 (count + recency decay scaled by observer-diversity confidence). Geo-rejected edges already excluded at graph build time (#1230) so we don't re-filter here. - Dijkstra distance: `1 / max(epsilon, weight)` — high affinity = cheap cost. - Normalize: divide by max observed centrality so output is in `[0, 1]`. Cost: `O(V · (E + V log V))`. Staging-scale (~600 nodes / ~2 000 edges) ≈ ~4.8M ops, completes in milliseconds. ## Where it lives - `cmd/server/bridge_score.go` — pure algorithm, no locks - `cmd/server/bridge_recomputer.go` — background recomputer (mirrors #1240/#1262 pattern), 5-min default interval, initial sync prewarm, snapshot stored in `s.bridgeScoreMap atomic.Pointer[map[string]float64]` - `cmd/server/routes.go` — `handleNodes` adds `node["bridge_score"]` on repeater/room rows; node-detail handler adds it on the single-node path - `public/nodes.js` — separate **Bridge** row in the node detail panel, alongside the existing **Usefulness** (Traffic) row. Distinct colour-coded bar. ## What's NOT in this PR (still pending for #672) - **Coverage axis** (axis 3) — unique observer-pair connectivity - **Redundancy axis** (axis 4) — simulated node-removal impact - **Composite** — once all 4 axes ship, swap the `usefulness_score` formula from "traffic-only" to the weighted composite `Refs #672` (not `Fixes` — issue stays open until all 4 axes + composite ship). ## Tests - `TestComputeBridgeScores_LineGraph` — 4-node line: middles non-zero, leaves zero, max normalized to 1.0 - `TestComputeBridgeScores_TriangleNoBridge` — clique has zero bridges - `TestComputeBridgeScores_Empty` — defensive nil-safety - `TestComputeBridgeScores_WeightSensitive` — mutation guard: revert the `1/w` inversion and this test fails - `TestBridgeScore_HandleNodesSurface` — integration: `/api/nodes` returns `bridge_score` on repeater rows; middle nodes > 0, ends == 0 --------- Co-authored-by: clawbot <bot@meshcore.local> |
||
|
|
46ce9590f1 |
fix(#1270): Prefix Tool Network Overview shows configured-hash-size counts, not math-only slices (#1271)
Red commit: `6b68080c24106301b6bfc25f8a05484f07d0612d` (test added that fails on master). CI: see Checks tab on this PR. Fixes #1270. ## Problem Two analytics surfaces told contradictory stories about prefix usage: - **Prefix Tool → Network Overview** showed e.g. `168 / 65,536` for the 2-byte tier — a pure math fact: every repeater pubkey sliced to 2 bytes yields N distinct values. Because collisions are rare, this number always equals (or nearly equals) the repeater count, making it look like the whole network uses 2-byte hashing. - **Hash Stats → By Repeaters** showed configured-hash-size counts straight from `/api/analytics/hash-sizes` `distributionByRepeaters` — usually a minority on 2-byte and near-zero on 3-byte. The Prefix Tool was presenting a math fact as if it were operational truth. ## Fix `renderPrefixTool` now also fetches `/api/analytics/hash-sizes` and restructures each tier card into three labeled stats with explicit hierarchy: 1. **Primary** — `X of Y repeaters configured` (from `distributionByRepeaters`). Same source the Hash Stats tab uses, so the two pages agree exactly. 2. **Operational collisions** — colliding slices among repeaters configured for *this* hash size only (matches Hash Issues semantics). 3. **Theoretical** (secondary, smaller, dashed-rule footnote) — `X unique N-byte slices across all repeater pubkeys (of Y possible)`. The math fact is preserved as educational info, no longer impersonating operational truth. The "Total repeaters" card now also notes how many have a known configured hash size. The "About these numbers" footer was rewritten to explain the three numbers and link to both Hash Stats and Hash Issues. The prefix collision detector (Check / Generate panels) is unchanged — it still scans every repeater pubkey because that is its job. ## Test Added `#1270 Prefix Tool primary counts match Hash Stats By Repeaters` to `test-e2e-playwright.js`. It fetches `/api/analytics/hash-sizes` for the ground-truth `distributionByRepeaters`, then visits `#/analytics?tab=prefix-tool`, opens Network Overview, and scrapes the primary count via a new `data-pt-configured="<bytes>"` `data-value="<count>"` marker on each tier card, asserting exact equality for 1/2/3-byte. - Red commit `6b68080c` (test only): fails on master with `NO data-pt-configured marker`. - Green commit `12ed2789` (fix): test passes; full E2E suite `123/126 passed, 3 skipped`. ## Acceptance - [x] Prefix Tool Network Overview shows configured-hash-size repeater counts as the primary number - [x] "Unique slices" math is shown as secondary/educational - [x] Two pages tell the same story (E2E asserts byte-equal match) - [x] E2E asserts the configured-count matches what Hash-Sizes tab shows at the same point in time |
||
|
|
78b666c248 |
fix(#1267): mobile VCR bar invisible — JS height clobbered bottom-nav reserve (#1269)
## Summary Mobile-only regression: on the Live page at ≤768px viewports the VCR bar was rendered behind the fixed bottom-nav and never visible to the user. iOS Safari screenshot at 375x812 showed: top header strip, full-height map, bottom-nav — **no VCR row at all**. Fixes #1267. ## Root cause `public/live.js` `initResizeHandler` (the existing JS height override) was setting `page.style.height = window.innerHeight + 'px'`, which clobbered the CSS rule that already subtracts `--bottom-nav-reserve` from the live-page height. Because `.live-page` then spanned the full viewport, the VCR bar (`position:absolute; bottom:0; z-index:1000`) was painted underneath `.bottom-nav` (`position:fixed; z-index:1200`). The VCR bar element WAS in the DOM, WAS `display: flex`, and HAD `height: 53px` — it just sat at y=758..812 underneath the bottom-nav at y=754..812. CSS-only checks for `display:none` would never catch this; the test asserts the bar's bottom edge is at or above the bottom-nav's top edge. ## Fix One-liner in spirit: subtract the bottom-nav height before applying `page.style.height`. The implementation measures the rendered `.bottom-nav` (with a fallback to a hidden probe that resolves the `--bottom-nav-reserve` token), so it survives safe-area inset and the bottom-nav's 1px border. ```js const reserve = /* measure .bottom-nav, fall back to --bottom-nav-reserve token */; const h = Math.max(0, window.innerHeight - reserve); ``` Desktop is unchanged: `.bottom-nav` is `display: none`, the probe resolves to 0, and `h === window.innerHeight` exactly as before. ## TDD - **RED** (commit 1): `test-e2e-1267-mobile-vcr.js` — Playwright at iPhone 375x812 asserts `.vcr-bar` has `display !== 'none'`, `visibility !== 'hidden'`, `height > 0`, `top < viewport.height`, and (the key check) `bottom <= bottom-nav.top`. Fails on `master` with: *"VCR bar bottom 812 overlaps bottom-nav top 754"*. - **GREEN** (commit 2): the fix above. Test passes: *"VCR bar bottom 754 ≤ bottom-nav top 754"*. ## Verification - ✅ Mobile (375x812) repro reproduced against `master` (bar at y=758..812, behind bottom-nav) - ✅ Mobile (375x812) E2E green after fix (bar at y=700..754, flush above bottom-nav) - ✅ Desktop (1440x900) unaffected — bottom-nav hidden, page height = viewport height as before, VCR bar at viewport bottom - ✅ #1234 (top-nav hidden on /live), #1246 (single-row VCR), #1206/#1213 (VCR/feed clearance) unchanged — none touched ## Files - `public/live.js` — single function (`initResizeHandler`) modified - `test-e2e-1267-mobile-vcr.js` — new mobile-viewport Playwright regression test Run: `BASE_URL=http://localhost:13581 node test-e2e-1267-mobile-vcr.js` --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
094a96bd6c |
perf(#1258): /#/perf — parallel health fetch, sort endpoints, pause refresh while hidden (#1261)
Fixes #1258 — Perf dashboard (/#/perf) was slow because of three frontend issues; backend APIs were never the problem. ## Findings 1. **`/api/health` fetched sequentially after `Promise.all`** in `refresh()` — added a full RTT (~50-200ms) on every 5s tick on top of the parallel batch. 2. **Endpoints table not actually sorted** despite the heading "sorted by total time". JSON shape is `map[string]EndpointStatsResp` (no defined order); frontend rendered map iteration order. Visible correctness bug surfaced during investigation. 3. **`setInterval(refresh, 5000)` kept firing while tab was hidden**, rebuilding the entire ~10-section `innerHTML` (cards + 3 tables) in the background. On tab return the user saw a backlog thrash + felt the page was "slow to render". ## Fix (`public/perf.js`) - Move `/api/health` into the same `Promise.all` as the other 4 endpoints — saves one RTT per refresh. - Sort `Object.entries(server.endpoints)` by `count * avgMs` DESC client-side. - Add `document.hidden` guard in the interval tick + `visibilitychange` listener that refreshes once on return; `destroy()` removes the listener. ## Tests `test-perf-render-1258.js` (new): - All 5 initial fetches issued in parallel (including `/api/health`) - Refresh suppressed while `document.hidden` - Endpoints table sorted by total time DESC, regardless of input map order RED commit first (`6b54f9e8`, 0/3 pass) → GREEN commit (`be81303b`, 3/3 pass). Existing `test-perf-go-runtime.js` (13/13) and `test-perf-disk-io-1120.js` (15/15) still green. ## Investigation exemption No Playwright timing test — sandbox can't run a real browser. Static analysis + render-shape unit tests cover the three identified bottlenecks. Documented per AGENTS "investigation surfaces" exemption. ## Measurement Before: refresh = parallel batch (~max(server-side)) + sequential `/api/health` (~50ms) + full innerHTML rebuild every 5s including hidden tabs. After: refresh = single parallel batch, runs only while visible. Expected improvement on tab-return ≈ -1 RTT per refresh + zero background work. --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
1d33ac53b0 |
fix(#1254): trim .badge-iata h-padding on mobile to clear 1.25px clip (#1255)
Fixes #1254. Master CI Playwright fail-fast on every push since #1252: ``` ❌ Mobile viewport (375px): observer IATA badge stays visible — not clipped: .badge-iata right edge 376.25 exceeds 375px viewport ``` ## Root cause After #1252 unhid `.col-observer` at narrow widths so the IATA pill from #1188 renders on mobile, at 375px the cell padding + truncated observer name (10 chars in grouped rows) + `.badge-iata` pill (`padding: 1px 5px` + `margin-left: 4px`) sums to ~376.25px — overflowing the viewport by 1.25px. Same class of failure as #1250/#1251 (VCR LCD-clip). ## Fix `public/style.css` — inside the existing `@media (max-width: 640px)` block, shrink `.badge-iata` `padding: 1px 5px → 1px 3px` and `margin-left: 4px → 2px`. Reclaims ~6px horizontally, well clear of the 1.25px overflow. Desktop (≥641px) styling untouched. ## TDD The failing E2E sub-test in `test-observer-iata-1188-e2e.js` (added in #1189 R1) IS the red. Mutation verified locally: | Variant | Result | |--------------------|--------| | WITHOUT this fix | ❌ `.badge-iata right edge 376.25 exceeds 375px viewport` | | WITH this fix | ✅ all 3 sub-tests pass | ## Local verification ``` $ go build -o /tmp/corescope-server ./cmd/server $ /tmp/corescope-server -port 13581 -db test-fixtures/e2e-fixture.db -public public & $ CHROMIUM_PATH=/usr/bin/chromium BASE_URL=http://localhost:13581 \ node test-observer-iata-1188-e2e.js Running observer-IATA E2E tests against http://localhost:13581 ✅ Packets table renders an IATA badge in an observer cell ✅ Filter grammar: observer_iata == "<code>" narrows the table ✅ Mobile viewport (375px): observer IATA badge stays visible — not clipped All observer-IATA E2E tests passed. ``` ## Constraints honored - All colors via existing CSS variables (no theming illusions; only `padding` / `margin-left` change inside `@media (max-width: 640px)`). - No JS changes. - Desktop badge display unaffected (selector scoped to narrow viewport). - `config.example.json`: no config field added. - PII preflight: clean. Co-authored-by: OpenClaw Bot <bot@openclaw.local> |
||
|
|
43203b09b7 |
fix(#1249): IATA badge missing on fixture + mobile clipping (#1252)
Failing test commit: `bdb4eefb` (added in #1189 R1) — original CI failure: https://github.com/Kpa-clawbot/CoreScope/actions/runs/25995819598 Fixes #1249. ## Root cause Two independent bugs surfaced by the same E2E test: 1. **Fixture join broken.** `scripts/capture-fixture.sh` wrote the text observer hash into `observations.observer_idx`, but the v3 join in `cmd/server` is `observers.rowid = observations.observer_idx`. The join silently nulled out `observer_id` / `observer_iata` for every packet. 2. **Mobile clipping.** `.col-observer` had `data-priority=3` (hides at ≤1024px) and was in the narrow-viewport `defaultHidden` list, so at 375px the cell collapsed to `display:none` and `.badge-iata` had a 0×0 box. ## Changes - `test-fixtures/e2e-fixture.db`: remap `observer_idx` text hash → integer rowid (500/500 rows resolved). - `scripts/capture-fixture.sh`: build an `observer_id → rowid` map before insert; skip rows whose observer isn't in the fixture. Comment explains the trap. - `public/packets.js`: bump `.col-observer` priority `3 → 1` and drop `observer` from narrow-viewport `defaultHidden`. ## Verification All three sub-tests in `test-observer-iata-1188-e2e.js` pass locally against the freshened fixture. `curl /api/packets?limit=5` returns real IATA codes (OAK / MRY / SFO) instead of empty strings. Co-authored-by: OpenClaw Bot <bot@openclaw.local> |
||
|
|
45872c8371 |
fix(#1250): trim mobile VCR bar h-padding 8px→4px to clear 0.83px LCD clip (#1251)
Red: master CI run https://github.com/Kpa-clawbot/CoreScope/actions/runs/25995768081 already fails on `test-e2e-playwright.js` `#1221 LCD clipped on right (right=375.828125, vw=375)`. No new test commit — the existing E2E assertion is the gate. **Root cause.** PR #1222's mobile rule set `.vcr-bar { padding: 4px 8px }`. The flex row holds three `flex-shrink: 0` children (controls + scope-btns + lcd) and one `flex: 1 1 0` absorber (`.vcr-timeline-container`, `min-width: 40px`). At 375px viewport the absorber hits its floor, so the intrinsic widths of the shrink-frozen children spill 0.83px past the padding box. **Fix.** Drop horizontal padding 8px → 4px inside the `@media (max-width: 640px)` block. That's 8px of new slack — order of magnitude above the 0.83px clip — keeping LCD's `getBoundingClientRect().right ≤ 375`. Desktop layout untouched (rule is mobile-scoped). VCR/feed overlap (#1206/#1213) not reintroduced because `--vcr-bar-height` is JS-measured by the ResizeObserver, not pinned in CSS. Fixes #1250 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:
|
||
|
|
74685ac82f |
fix(#1243): node detail mobile QR overlays map semi-transparently (#1245)
RED commit `fc9b619a` — CI: https://github.com/Kpa-clawbot/CoreScope/actions Fixes #1243. ## Problem On `#/nodes/<pubkey>` at 375×800, the QR code rendered as a separate ~250px-tall panel below the map. Desktop already overlays the QR semi-transparently via `.node-map-qr-overlay` for the compact view. ## Fix Extend the mobile breakpoint (`@media (max-width: 640px)`) so the full-screen `.node-top-row` mirrors the desktop overlay pattern: - `.node-top-row` → `position: relative`; map wrap expands to 100% - `.node-qr-wrap` → `position: absolute; bottom/right: 8px; z-index: 400` - Semi-transparent background (`rgba(255,255,255,0.85)` light / `0.4` dark) - Caption hidden in overlay (already shown above) Desktop (≥768px) flex layout untouched. ## TDD - RED `fc9b619a` — E2E at 375×800 asserts QR is `position: absolute|fixed`, overlaps map rect, and bg alpha < 1. - GREEN `ded978c0` — CSS adds overlay rule. ## Verification Preflight clean. Desktop layout unaffected — change is scoped inside `@media (max-width: 640px)`. ## Files - `public/style.css` (+29) - `test-e2e-playwright.js` (+57) --------- Co-authored-by: clawbot <clawbot@local> |
||
|
|
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:
|
||
|
|
30ff45ad34 |
fix(#1220): collapse MESH LIVE mobile header into a single ~50px strip (#1223)
RED commit `c1a8cea` — E2E at 375x800 asserts MESH LIVE header is either ≤60px (collapsed) or ≥60px with a visible body. Fails on master with `height=118, bodyVisible=false, ctrlsVisible=false` — the empty-chrome middle state. CI for red commit: https://github.com/Kpa-clawbot/CoreScope/actions (will populate after push). ## Diagnosis On `(max-width: 768px)`, `#1180` collapses both `.live-header-body` and `.live-controls-body` to `display:none`. But `.live-controls` carries `flex: 0 0 100%` from the wide-viewport rule (introduced for `#1219` so the toggles wrap onto their own row below the title on tablet). On mobile, with the body hidden, that 100% basis still forces the gear button onto a full-width second row inside `#liveHeader`'s flex-wrap, ~60px tall — yielding the `~118-200px` empty panel the bug screenshot shows (the count badge + 📊 toggle on row 1, gear alone on row 2, nothing else). ## Fix — Option C Inside `@media (max-width: 768px)`, when `.live-controls.is-collapsed`: - drop `flex: 0 0 100%` → `flex: 0 0 auto; width: auto` so the gear inlines with the critical strip + 📊 toggle - when the header is also collapsed (`.is-collapsed:has(.live-controls.is-collapsed)`), zero the vertical padding so the strip hugs the 48px tap targets Result: collapsed mobile panel = single ~50px row, three icons inline. Expanded mobile = full toggle list (149px). Desktop unchanged (83px). Why Option C over A/B: a packet-watching mobile user keeps the map dominant and reaches for the gear when they want filters. The compact strip preserves both the WS-down red beacon (always visible) and the pkt count, with one-tap access to expand either body. Does not reintroduce #1204 (counter still attached to header) or #1205 (toggles still children of `#liveHeader`). Fixes #1220 --------- Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com> |
||
|
|
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> |
||
|
|
24f277e5c6 |
fix(#1221): VCR LED clock in-row with controls and unclipped on mobile (#1222)
Red commit:
|
||
|
|
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:
|
||
|
|
3255395bd0 |
fix(#1204): MESH LIVE panel — header inherited column flex from .live-overlay (#1215)
Red commit:
|
||
|
|
4925770aa4 |
fix(#1207): empty-state placeholder for Live Feed panel (no more orphan chrome) (#1210)
Red commit: `6c28227884a1e79e277653465028365dc0863171` — CI: https://github.com/Kpa-clawbot/CoreScope/actions?query=branch%3Afix%2Fissue-1207 Fixes #1207 ## Diagnosis The Live Map page renders `#liveFeed` (bottom-left panel) with two header buttons — `◫` (panel-corner-btn) and `✕` (feed-hide-btn) — but its `.panel-content` body has zero children on first paint, before any packets have been ingested via WebSocket. The user-reported "X + book icons, no content" is exactly these two header buttons sitting on an empty body. **Verdict:** intended panel, missing content due to a data race — the chrome mounts in HTML before the WS pushes its first packet. Not orphaned, not a leftover from #1186. ## Fix - Always render a persistent `.live-feed-empty` placeholder ("Waiting for packets…") inside `#liveFeed .panel-content`. - CSS hides it via `.live-feed .panel-content:has(.live-feed-item) .live-feed-empty { display: none; }` when real feed items exist. - `rebuildFeedList` re-adds the placeholder defensively after a wipe; eviction loop counts `.live-feed-item` only so the placeholder is never trimmed out. All colors via CSS variables (`var(--text-muted)`). ## Test (RED → GREEN) - **RED** `6c28227884a1e79e277653465028365dc0863171` — `test-e2e-playwright.js` adds a new test ("#1207 Live Feed panel never renders as empty chrome") that wipes `.live-feed-item` children to simulate the empty state and asserts the panel body has visible text or children. Fails on master. - **GREEN** `a5af80960ac42759ec83fd5ca5a72e81856228d4` — adds the placeholder; test now passes. ## Acceptance criteria - [x] No empty panel chrome visible on Live Map page - [x] Panel renders "Waiting for packets…" while feed is empty - [x] CSS auto-hides placeholder when packets arrive - [x] E2E assertion in `test-e2e-playwright.js` enforces non-empty `.panel-content` on `#liveFeed` ## Files - `public/live.js` — HTML markup + `rebuildFeedList` re-add + eviction-loop guard - `public/live.css` — `.live-feed-empty` style + `:has()` hide rule - `test-e2e-playwright.js` — regression test --------- Co-authored-by: clawbot <clawbot@kpabap.local> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
03b5d3fe28 |
fix(#1065): first-visit gesture discoverability hints (#1186)
Red commit:
|
||
|
|
b4f186af19 |
fix(#1062): gesture system — swipe rows, tabs, slide-over dismiss (#1185)
Red commit: bbb98cf81aae38bff1ef77a7c8a701813b25bb77 (CI run: pending — see Checks tab) Fixes #1062. Parent: #1052. ## Gesture system Adds touch-gesture handling on phones (≤768px): 1. **Swipe-left on a packets/nodes/observers row** → reveals row-action overlay (trace, filter, copy hash). Threshold: 24% of row width OR 80px. Sub-threshold = visual peek that snaps back. 2. **Horizontal swipe on the bottom-nav strip** → advances tabs in TAB order from `bottom-nav.js`. Packets ↔ Live ↔ Map etc. 3. **Swipe-down on a slide-over panel** → calls `window.SlideOver.close()`. ## Hard constraints met - **Pointer Events ONLY** — no `touchstart`/`touchend` mixing. `setPointerCapture` for tracking continuity. - **Axis-lock** — direction committed in first 8–12px movement. Vertical scroll is never blocked unless we explicitly committed to a horizontal swipe. `body { touch-action: pan-y }` so the browser owns vertical natively. - **Leaflet exclusion** — handlers early-bail on `e.target.closest('.leaflet-container')` so pinch/pan on the map tab are untouched. - **Singleton pattern** — module-scoped `__touchGestures1062InitCount` guard. Document-level pointer listeners registered exactly once even if the script loads multiple times (mirrors the #1180 fix class). - **prefers-reduced-motion** — animations have `transition-duration: 0s` under the media query; gestures still trigger, snaps are instant. ## E2E `test-gestures-1062-e2e.js` — Playwright with synthesized PointerEvents (page.touchscreen unreliable in headless for axis-locked custom handlers). Wired into the deploy.yml matrix. E2E assertion added: test-gestures-1062-e2e.js:120 (overlay-visible after left-swipe), :201 (tab advance), :219 (Leaflet exclusion), :247 (slide-over dismiss). --------- Co-authored-by: openclaw-bot <bot@openclaw> Co-authored-by: OpenClaw Bot <bot@openclaw.dev> Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: corescope-bot <bot@corescope.local> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
9b9848611b |
fix(#1064): edge-swipe nav drawer (Option A, wide-only) (#1184)
Red commit:
|
||
|
|
9d1f5d2395 |
fix(#1061): bottom navigation for narrow viewports (#1174)
Red commit: a200704d5e27e47c0b29a4745bf1a1772a8876fe (CI URL added once Actions resolves the run) Fixes #1061 ## What Bottom navigation at ≤768px with 5 tabs in spec order: Home, Packets, Live, Map, Channels. Top-nav suppressed at the same breakpoint — no duplicate nav UX. ## Files - NEW `public/bottom-nav.js` — renders 5 tabs, syncs `.active` on `hashchange`, reuses the existing in-app hash router (`<a href="#/...">`). Stable selector `[data-bottom-nav-tab="<route>"]`. Container `[data-bottom-nav]`. - NEW `public/bottom-nav.css` — styles. Tokens reused: `--nav-bg`, `--nav-text`, `--nav-text-muted`, `--nav-active-bg`, `--accent`, `--border` (all global → resolve in BOTH light and dark themes). - `public/index.html` — one `<link>` for the CSS, one `<script>` after `app.js`. The `<nav>` is appended by JS as a sibling of `<main id="app">` at DOMContentLoaded. - `test-bottom-nav-1061-e2e.js` + `.github/workflows/deploy.yml` — Playwright wiring. ## Decisions - **Breakpoint:** `@media (max-width: 768px)`. No `@container` rules exist anywhere in `style.css` today — media query is consistent. - **Top-nav suppression:** `display:none` at ≤768px. Simpler than a hamburger collapse; long-tail routes (Tools/Lab/Perf) remain reachable by URL; "More"-tab/hamburger fallback deferred per issue body. - **Active indicator:** `var(--nav-active-bg)` + 2px accent top-border. No moving pill. - **Safe-area:** `padding-bottom: env(safe-area-inset-bottom)` on nav + reciprocal `body` reservation. `viewport-fit=cover` already in place. - **Reduced motion:** `prefers-reduced-motion: reduce` disables the transition. ## TDD - Red: `a200704` — assertions fail (no bottom-nav). - Green: `53851a1` — component + styles. E2E assertion added: `test-bottom-nav-1061-e2e.js:71` (case (a) — bottom-nav visible at 360x800). --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: corescope-bot <bot@corescope.local> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: openclaw-bot <bot@openclaw> |
||
|
|
05876b3a59 |
fix(#1173): replace #liveDot with packet-driven brand-logo node-pulse (#1177)
Red commit: PENDING (will update) Fixes #1173. Replaces the `#liveDot` WebSocket-connected indicator with a packet-driven node-pulse animation on the brand logo's two inner circles. ## Behavior (locked per issue spec) - **Animation curve:** `ease-out` (default per open-question 1). - **Rate cap:** 15/sec (66ms gap; default per open-question 2). Excess triggers are dropped, never queued. - **Direction:** alternates A→B / B→A across messages (aesthetic, not semantic). - **Idle ≥10s:** logo at full brightness, no animation. - **Disconnected:** `.logo-disconnected` applies `filter: grayscale(0.6) opacity(0.7)`. - **`prefers-reduced-motion: reduce`:** single-step `.logo-pulse-blip` on destination only. ## Implementation - WS handler hook lives in `public/app.js` `connectWS()` (`ws.onmessage` triggers `Logo.pulse()`; `ws.onopen`/`ws.onclose` toggle `Logo.setConnected()`). - `Logo` is a small IIFE in `app.js` that exposes `window.__corescopeLogo` for E2E injection. - All animation is pure CSS; JS only toggles `.logo-pulse-active` / `.logo-pulse-blip` / `.logo-disconnected`. Colors come exclusively from `--logo-accent` / `--logo-accent-hi` tokens. - Two new classes (`.logo-node-a`, `.logo-node-b`) attached to inner circles in both `.brand-logo` and `.brand-mark-only` SVGs so the mobile mark animates too. ## `#liveDot` removal proof ``` $ grep -rn liveDot public/ (no output) ``` ## E2E - E2E assertion added: `test-logo-pulse-1173-e2e.js:54` and follows. - Wired into the Playwright matrix in `.github/workflows/deploy.yml` (mirrors PR #1168 pattern from commit `5442652`). - Test injects synthetic pings via `window.__corescopeLogo.pulse({ synthetic: true })`; matches the existing harness style (no new WS-mock pattern invented). Red→green discipline preserved: the test commit lands first and CI fails on assertion; the implementation commit follows. --------- Co-authored-by: Kpa-clawbot <bot@kpa-clawbot> Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
16c48e73b3 |
fix(live): compact header + pinned controls with narrow-viewport collapse (#1178, #1179) (#1180)
Red commit: 61fcc8c19b96543f1b4bbd6fd2ce54e6265d5e38 (CI run: pending — see Checks tab on this PR) Fixes #1178 Fixes #1179 ## Summary Live page layout polish — both issues touch `public/live.css` + a small `public/live.js` slice, so they ship as one PR per AGENTS rule 34. ### #1178 — Header compactness + narrow-viewport collapse - `.live-header` total height ≤ 40px at desktop widths (smaller padding, gap, title font, and pill sizing; `max-height: 40px` as a belt-and-suspenders gate). - Body wrapped in `.live-header-body` so it can collapse cleanly. - New 32×32 toggle button `[data-live-header-toggle]`, hidden at wide viewports, visible at `≤768px`. ### #1179 — Controls pinned bottom-right + narrow-viewport collapse - New `.live-controls` cluster around the toggles list and audio controls, `position: fixed; right: 12px;` and `bottom: calc(78px + var(--bottom-nav-height, 56px) + env(safe-area-inset-bottom, 0px))`. - That bottom calc reserves space for the VCR bar **and** the bottom nav (#1061, currently in PR #1174). When the bottom-nav exposes `--bottom-nav-height` the cluster tracks it; otherwise the 56px fallback keeps it clear regardless of merge order. - `z-index: 1000` keeps it above map markers but below modals. - New 32×32 toggle button `[data-live-controls-toggle]`, hidden at wide viewports, visible at `≤768px`. ### Breakpoint + selectors - Narrow = `max-width: 768px` (matches #1061 bottom-nav activation). - Stable selectors for E2E: `[data-live-header-toggle]`, `[data-live-header-body]`, `[data-live-controls-toggle]`, `[data-live-controls-body]`. No DOM-order dependence. ### Bottom-nav coexistence The expanded narrow-viewport controls panel uses `max-height: 50vh; overflow-y: auto` on its toggles list, and the cluster's `bottom` reservation guarantees the panel's bottom edge sits above the (possibly absent) bottom-nav region. The E2E test asserts exactly this with `expandedRect.bottom + 8 < innerHeight − navH`, defaulting `navH` to 56 if `.bottom-nav` is not in the DOM yet. ### Theming All new colors via existing CSS tokens (`--surface-1`, `--text`, `--text-muted`, `--border`, `--accent`). check-css-vars passes. ### TDD - Red commit: `61fcc8c` — assertions only (no impl), wired into `.github/workflows/deploy.yml` Playwright matrix. - Green commit: `7d591be` — DOM split + CSS + collapse JS. - E2E assertion added: `test-live-layout-1178-1179-e2e.js:55` (desktop header height) through `:170` (narrow controls bottom-nav coexistence). ### Local verification ``` ./corescope-server -port 13581 -db test-fixtures/e2e-fixture.db & CHROMIUM_PATH=/usr/bin/chromium BASE_URL=http://localhost:13581 \ node test-live-layout-1178-1179-e2e.js # → 8/8 passed ``` --------- Co-authored-by: meshcore-bot <bot@meshcore.local> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
9774403fa4 |
fix(#1058): analytics chart containers — fluid + auto-stacking (#1175)
Red commit: 0f29da3 (CI is pending — will be linked once dispatched) Fixes #1058 This PR is in **red phase**. The new E2E asserts the desired fluid + auto-stacking behavior; with `master`'s code it FAILS at ≥768px (cards don't stack). Green commit follows. E2E assertion added: `test-charts-fluid-1058-e2e.js:99`. --------- Co-authored-by: OpenClaw Bot <bot@openclaw.local> Co-authored-by: Kpa-clawbot <bot@kpa-clawbot> |
||
|
|
f4cf2acbc0 |
perf: cancelled writes + ingestor I/O + threshold tests (#1120 follow-up) (#1167)
Red commit:
|
||
|
|
89d644dd72 |
fix(#1056): row-detail slide-over panel at narrow widths (AC #4) (#1168)
Red commit:
|
||
|
|
cf604ca788 |
fix(nav): #1109 mobile hamburger dropdown clipped by .top-nav overflow:hidden (#1163)
# Fix #1109 — mobile hamburger dropdown clipped invisible by `.top-nav { overflow:hidden }` Red commit: `5429b0f` (failing E2E, asserts pixel-level visibility). ## Symptom On <768px viewports, tapping `#hamburger` toggles `.nav-links.open` and `body.nav-open` correctly — DOM state is right, `aria-expanded="true"`, computed `display:flex` — but **nothing appears below the navbar**. The dropdown is laid out at `y=52..626` but visually clipped. ## Root cause `.top-nav` is `position:sticky; height:52px; overflow:hidden` (added in #1066 fluid scaffolding at `417b460` to guard against horizontal overflow during the Priority+ measurement pass). At <768px the dropdown becomes `position:absolute; top:52px`, so its containing block is `.top-nav` — and `.top-nav`'s `overflow:hidden` clips everything below `y=52`. Result: the dropdown renders inside a 52px box and the user sees nothing. Full RCA + screenshots: https://github.com/Kpa-clawbot/CoreScope/issues/1109#issuecomment-4398900387 ## Fix In `public/style.css`, inside `@media (max-width: 767px)`, change `.nav-links` from `position:absolute` to `position:fixed`. `position:fixed` escapes any `overflow:hidden` ancestor (its containing block becomes the viewport), so the dropdown is no longer clipped. All other rules (display/flex/background/padding/z-index) keep working. This deliberately does **not** relax `overflow:hidden` on `.top-nav` — that would reopen the #1066 horizontal-overflow regression on desktop. ## Why prior tests missed this Existing nav E2Es asserted `.classList.contains('open')` / `getComputedStyle().display === 'flex'` — pure DOM state. Those passed even while the dropdown was clipped invisibly. The new test in this PR asserts **pixel-level visibility**: `document.elementFromPoint(viewportWidth/2, 100)` must land on something inside `.nav-links` (not `<body>`), and the first `.nav-link`'s bounding rect must satisfy `bottom > 60` and have non-zero area. A state-only fix can never satisfy this. E2E assertion added: `test-issue-1109-hamburger-dropdown-visible-e2e.js:113` (the `hitInsideNavLinks` check). ## Files changed - `public/style.css` — one line in the mobile media query: `position: absolute` → `position: fixed` - `test-issue-1109-hamburger-dropdown-visible-e2e.js` — new E2E - `.github/workflows/deploy.yml` — wire the new E2E into the suite Fixes #1109 --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
d6256c4f94 |
fix(#1151): drop orphan separators from side-panel Heard By rows (#1161)
Fixes #1151 ## Problem The side-panel "Heard By" row template in `public/nodes.js` (line 1337) built its stats suffix with inline ternaries: ```js ${o.packetCount} pkts · ${o.avgSnr != null ? '...' : ''}${o.avgRssi != null ? ' · RSSI ...' : ''} ``` When `avgSnr` and/or `avgRssi` were `null` (very common in prod — many CJS observers have both null), this produced orphan separators: - both null → `"110 pkts · "` (trailing dot) - snr null only → `"55 pkts · · RSSI -50"` (double dot) ## Fix Build a filtered parts array, then `.join(' · ')`. Only present fields contribute, so the separator can never appear next to nothing. ```js const stats = [`${o.packetCount} pkts`]; if (o.avgSnr != null) stats.push('SNR ' + Number(o.avgSnr).toFixed(1) + 'dB'); if (o.avgRssi != null) stats.push('RSSI ' + Number(o.avgRssi).toFixed(0)); // → stats.join(' · ') ``` Full-page table (line 1337's neighbor) was already null-safe (separate `<td>` cells), so only the side-panel template needed the change. ## TDD Red commit: `1c02ff9a7889aadd16f87f4e673287f9742d4ad0` — adds `test-issue-1151-orphan-separators-e2e.js` to the deploy.yml E2E job. The test stubs `/api/nodes/:pubkey/health` via Playwright `page.route()` with four observer permutations (both null, snr-only-null, rssi-only-null, both set), opens the side panel, and asserts no `.observer-row` stat suffix matches `· ·`, leading `·`, or trailing `·`. E2E assertion added: `test-issue-1151-orphan-separators-e2e.js:96` ## Preflight All hard gates pass — see preflight output in the implementation log. --------- Co-authored-by: CoreScope Bot <bot@corescope> |
||
|
|
cfd1903c6b |
fix(logo): default sage/teal brand colors, customizer mirrors accent (#1162)
## Summary Restores sage/teal as default logo colors while preserving customizer theming. Closes the gap from #1157 (closed) and the user's complaint about lost two-tone. Out-of-the-box, the navbar + hero CORE/SCOPE wordmarks now render the brand-identity duotone — `#cfd9c9` (sage / fog) and `#2c8c8c` (teal / water). When an operator picks a theme via the customizer (or sets a custom accent color), the wordmark recolors to follow. ## Approach (Option C — decoupled defaults + customizer mirror) - **`public/style.css` `:root`** — set `--logo-accent: #cfd9c9` and `--logo-accent-hi: #2c8c8c` as literal defaults. Removes the previous `var(--accent)` cascade so blue-by-default no longer leaks into the brand mark. - **`public/customize-v2.js`** — `applyTheme()`, the early-apply path, and the live color-picker `input` handler now mirror `themeSection.accent` → `--logo-accent` and `themeSection.accentHover` → `--logo-accent-hi`. - **`public/customize.js`** (legacy) — same mirroring in `applyThemePreview()` and the early localStorage replay. - **`.github/workflows/deploy.yml`** — adds the new e2e to the Chromium batch. This preserves `--accent` as the canonical app-wide accent token (no other UI changes) while giving the logo its own brand-defaulted tokens that the customizer still drives. ## Tests Red → green commit pair on the branch. - **NEW: `test-logo-default-sage-teal-e2e.js`** — gates both halves of the contract: 1. Clean localStorage → navbar + hero CORE = `rgb(207, 217, 201)`, SCOPE = `rgb(44, 140, 140)`. 2. Seeded `cs-theme-overrides` with red accent → navbar + hero recolor to red. - **UPDATED: `test-logo-theme-e2e.js`** — replaces the old "must NOT be sage" sentinel (sage was a regression marker; it's now the brand default) with a theme-reactivity probe that overrides `--logo-accent` / `--logo-accent-hi` directly and asserts the wordmark fill changes. Duotone, mobile-fit, and clip checks are unchanged. ## Verification - Default load: sage CORE + teal SCOPE in navbar AND hero ✔ (asserted by step 1 of the new e2e). - Customizer override: wordmark follows `accent` / `accentHover` ✔ (asserted by step 2 of the new e2e + the theme-reactivity probe in `test-logo-theme-e2e.js`). - Preflight: all hard gates green (PII, branch scope, red commit, CSS-var defined, CSS self-fallback, LIKE-on-JSON, sync migration); all warnings green. ## Browser verified E2E assertion added: `test-logo-default-sage-teal-e2e.js:73` (default sage), `test-logo-default-sage-teal-e2e.js:124` (customizer override). CI runs both via `deploy.yml:243`. Browser verified: covered by Chromium e2e against `http://localhost:13581` in CI; staging URL TBD on merge. --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
81f95aaabe |
fix(nav): floor More menu at >=2 items (#1139 Bug B) (#1148)
Partial fix for #1139 — closes Bug B (desktop More menu degenerate). Bug A (mobile hamburger) blocked on user device info; left for separate PR. ## What this changes `public/app.js` `applyNavPriority()` (the >1100px measurement branch): add a "minimum More menu size" floor. After the greedy `fits()` loop terminates, if exactly one link ended up in `is-overflow`, promote one more from the overflow queue so the dropdown contains ≥2 items. ```diff let i = 0; while (!fits() && i < overflowQueue.length) { overflowQueue[i].classList.add('is-overflow'); i++; } + // #1139 Bug B: floor the More menu at >=2 items. + var overflowedCount = allLinks.filter(a => a.classList.contains('is-overflow')).length; + if (overflowedCount === 1 && i < overflowQueue.length) { + overflowQueue[i].classList.add('is-overflow'); + i++; + } rebuildMoreMenu(); ``` The ≤1100px Priority+ design contract (5 high-priority + More) is unchanged; the floor only applies on the measurement branch. ## Why Above 1100px the measurement loop greedily fills inline links until something overflows. If exactly one non-priority link is wider than the remaining slack, the loop pushes only it into overflow and stops — producing a one-item "More ▾" dropdown. With the fixture stats this reproduces deterministically at 1600px (overflow=`["🎵 Lab"]`); the prod report on 1101–1278px is the same root cause with realistic `#navStats` width consuming most of the remaining slack. ## TDD - Red: `test-nav-more-floor-1139-e2e.js` sweeps 1101, 1150, 1200, 1240, 1278, 1280, 1340, 1500, 1600, 1700px and asserts `#navMoreMenu.children.length` is 0 or ≥2 — never 1. On master it fails at 1600px (`items=1, overflow=[#/audio-lab]`). - Green: with the floor in place all 10 viewports pass. - Existing `test-nav-priority-1102-e2e.js` and `test-nav-fluid-1055-e2e.js` still pass (5/5 and 20/20). - Wired into CI alongside the other nav E2E tests. ## Out of scope (Bug A) The mobile hamburger inert-button report needs a console snapshot from the affected device (pasted in the issue body) to pin the root cause. Left open for a follow-up PR. This PR uses "Partial fix" intentionally and does NOT include `Fixes #1139` so the issue stays open. --------- Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local> |
||
|
|
f27132e44e |
ui(node-detail): re-order sections so Recent Packets appears before Paths (#1147) (#1160)
Fixes #1147 ## What Re-orders the node-detail sections in **both** the side panel and the full node detail page. New sequence matches operator mental order (identity → what this node SAID → who heard it → relay topology → meta): 1. Identity (name, role, badges) 2. Map + QR (full page) / Public key (side panel) 3. Overview (Last Heard, First Seen, Total Packets, etc.) 4. **Recent Packets** ← lifted from bottom 5. Heard By (observers) 6. Neighbors 7. Paths Through This Node 8. Clock Skew (hidden until populated) ## Why "What did this node originate?" is the most-asked operator question at the node-detail surface. Previously Recent Packets was the LAST section in both views — operators had to scroll past Clock Skew, Heard By, Neighbors, and Paths just to see the node's own activity. Section B4 of the node-analytics review flagged this as P1. ## Changes - `public/nodes.js`: pure template re-order in two render paths (full-page `loadFullNode`, side-panel `renderDetail`). No data, styling, or behavior changes — same DOM ids, same CSS classes, same content per section. - `test-issue-1147-section-order-e2e.js`: new Playwright test that loads a node detail page (and the side panel) against the fixture DB and asserts `Recent Packets` index in DOM order is **before** `Paths Through This Node`, `Heard By`, and `Neighbors` for both surfaces. - `.github/workflows/deploy.yml`: wired the new E2E into the existing `e2e-test` job. ## TDD trail - Red commit: `c0829fd` — adds failing E2E (Recent Packets is last). - Green commit: `29cdb22` — re-orders the templates, test passes. ## Browser verified E2E assertion added: `test-issue-1147-section-order-e2e.js:84` (full page) and `:115` (side panel). Local Chromium can't run on this host (libc reloc), so verification is via CI; server-side `grep` of rendered `/nodes.js` confirms the new section order in both code paths. ## Preflight All hard gates pass (PII, branch scope, red commit, CSS vars, self-fallback, LIKE-on-JSON, sync migration). All warning gates pass. --------- Co-authored-by: kpaclawbot <bot@kpaclawbot.local> |
||
|
|
051c251e7f |
fix(#1146): WCAG AA contrast for Paths Through This Node links in dark mode (#1159)
Red commit: a4ec258fb82f72b8d5da64492dfe9a5ff4241886 (CI run linked from
`gh pr checks` once it starts)
## Problem
"Paths Through This Node" entries in node detail (side panel
#pathsContent and full-screen #fullPathsContent) render as `<div>`
blocks, not tables. The existing rule
```css
.node-detail-section .data-table td a,
.node-full-card .data-table td a { color: var(--accent); }
```
(public/style.css:1231) only covers `<td a>`, so path-hop links inherit
UA-default `rgb(0,0,238)`. On dark theme that's ~1.8–3.0:1 against
`--card-bg: #1a1a2e` — well under the 4.5:1 WCAG AA body-text floor.
## Fix
Add an explicit rule scoped to `#pathsContent` / `#fullPathsContent`
that uses `var(--accent)` (matching the data-table pattern) plus a
`:hover` to `var(--accent-hover)`. Tracks active theme + customizer
overrides — no hard-coded colours.
After: contrast measured at **6.19:1** in dark mode (link
`rgb(74,158,255)` on `rgb(26,26,46)`).
## TDD
- **Red commit** (`a4ec258`): adds
`test-issue-1146-path-link-contrast-e2e.js` + wires it into the e2e-test
job. Loads a node detail page, mocks `/paths`, forces `data-theme=dark`,
computes WCAG luminance/contrast on the path-hop `<a>`, asserts ≥ 4.5:1.
Reverting only the CSS commit restores the failure.
- **Green commit** (`5ad20fe`): the CSS fix.
E2E assertion added: `test-issue-1146-path-link-contrast-e2e.js:120`
Browser verified: local fixture run on `http://localhost:13591` (build
of `cmd/server` with this branch's `public/`) — 3 passed, 0 failed.
## Files changed
- `public/style.css` (+14 lines, scoped CSS rule + comment)
- `test-issue-1146-path-link-contrast-e2e.js` (new, +132 lines)
- `.github/workflows/deploy.yml` (+1 line, register the new E2E)
## Preflight
`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
→ all gates pass, no warnings.
Fixes #1146
---------
Co-authored-by: openclaw-bot <bot@openclaw.local>
|
||
|
|
844536a4cc |
fix(#1150): render error state on full-page node detail when API 404s (#1158)
Red commit: |
||
|
|
dfacfd0f6e |
fix(logo): widen navbar SVG viewBox so CORE/SCOPE wordmark fits (#1141 followup) (#1156)
Fixes #1141 follow-up — the visible-on-staging SCOPE→SCOP clip that the prior PRs (#1137, #1141) intended to address but didn't. ## What was actually broken (ground truth from staging) Staging at `http://20.109.157.39:80/` renders the inline navbar SVG correctly — duotone CORE/SCOPE fills inherit page CSS vars, mobile mark-only swap fires at ≤400px, customizer logo override path works. Those parts of #1137 + #1141 landed cleanly. What did **NOT** land: the SVG `viewBox` was never widened to fit the rendered Aldrich wordmark. At every desktop viewport the SCOPE `<text text-anchor="start" x="773.8">` produces a bbox extending to user-space x≈1112, but the navbar `viewBox="170 10 860 280"` ends at x=1030. Result: SCOPE renders as **SCOP** on every desktop load. CORE also slightly overflows the left edge (bbox.x=153.7 < viewBox.x=170). The original brief premise (mushroom emoji still in `index.html` + `<img>`-loaded SVG monotone fallback on staging) does not match current state — `public/index.html:45` already has the inline SVG, staging renders it, and computed fills are duotone (`rgb(74,158,255)` vs `rgb(109,179,255)`). The visible bug is geometric clipping, not CSS-var inheritance or a mushroom revert. ## Fix (one-liner SVG geometry change) - `public/index.html` — navbar `svg.brand-logo`: `viewBox="170 10 860 280"` → `viewBox="150 10 970 280"`; intrinsic `width="111"` → `width="125"` (preserves ~36px nav row height). - `public/style.css` — `.brand-logo { width }` 111px → 125px (desktop), tablet `@media (max-width:900px)` pin 99px → 112px to keep the new aspect ratio so wordmark still doesn't clip on tablets. - `public/customize-v2.js` — `_setBrandLogoUrl` `<img>` swap dimensions updated to match (when an operator overrides `branding.logoUrl`). The `≤400px` mobile mark-only swap is unchanged — at narrow widths the wordmark still hides entirely and the dedicated `.brand-mark-only` SVG (no `<text>`) renders. ## TDD (red → green) | commit | role | |---|---| | `16b7a60` | **RED** — `test-logo-theme-e2e.js` assertion #7: every `CORE`/`SCOPE` `<text>` bbox must fit inside the SVG `viewBox`. Master fails: `[{text:CORE, bboxX:153.7, bboxRight:426.2, vbX:170}, {text:SCOPE, bboxX:773.8, bboxRight:1111.5, vbRight:1030}]` | | `0db473b` | **GREEN** — widen viewBox + width to fit | Test exercises real `getBBox()` measurement on a headless Chromium DOM with the Aldrich webfont loaded — not a unit-test fill string check. The earlier #1141 tests asserted computed `fill` colors (which were correct) but never measured rendered geometry; that's the gap. ## Visual proof **Before** (master HEAD against staging, viewport 1280): `/tmp/staging-logo-before-1280.png` — SCOPE clearly clipped to "SCOP". **After** (this branch against local server, viewport 1280): `/tmp/local-after-1280-screen.png` — full CORE / SCOPE rendered. **Mobile (after, 375px)**: `/tmp/local-after-mobile.png` — mark-only SVG (no wordmark, no clip). ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` — all hard gates clean (PII, branch-scope, red-commit-genuine, css-vars-defined, css-self-fallback, like-on-json, sync-migration), all warnings clean (img-svg-ratio, themed-img-svg, fixture-coverage). E2E assertion added: `test-logo-theme-e2e.js:286-310` Browser verified: `/tmp/local-after-1280-screen.png` (local server) + `/tmp/staging-logo-before-1280.png` (staging baseline). --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
0d8bc7536c |
fix(logo): restore duotone fog/teal split + mobile mark-only swap (#1141)
Two related logo fixes bundled together (small scope each). Cc @user-display-not-by-name. ## 1. Restore duotone (fog/teal) split — the original ask The M2 (light-theme readability) fix-cycle on #1137 collapsed both halves of the inline CoreScope wordmark to `var(--logo-text)` so they would invert correctly on light themes. That restored readability but erased the original side-split palette. This change re-uses the existing `--logo-accent` / `--logo-accent-hi` vars (already driving the left/right node arcs and dots) for the wordmark too: - `CORE` → `fill="var(--logo-accent)"` — matches left arcs + left node dot - `SCOPE` → `fill="var(--logo-accent-hi)"` — matches right arcs + right node dot - chirp polyline + `MESH ANALYZER` tagline → unchanged, `var(--logo-muted)` No hardcoded hex; theme customizer overrides via `--accent` / `--accent-hover` keep working on both themes. ## 2. Fix mobile clipping (SCOPE → "SCOF" at ≤390px) The full inline wordmark SVG has ~111px intrinsic content; the `.brand-logo` mobile pin from #1137 (99px width) was squeezing it and visibly clipping SCOPE. **Approach:** swap the full wordmark SVG for a dedicated mark-only inline SVG at ≤400px (option #1 from the design call). Keeps the duotone arcs, dots, and chirp visible — drops the wordmark cleanly. - `public/index.html`: CORE/SCOPE wrapped in `<g class="brand-wordmark">` (clean grouping); new sibling `<svg class="brand-mark-only">` with tight viewBox `425 15 250 230` covering both nodes + dots only. Same `--logo-accent` / `--logo-accent-hi` vars → duotone preserved on mobile. - `public/style.css`: `.brand-mark-only` defaults `display:none`; new `@media (max-width:400px)` rule hides `.brand-logo` and shows `.brand-mark-only`. ## TDD Three commits, red→green→red→green: | commit | role | |---|---| | `d53d328` | RED — duotone assertions (#4, #5) added; master fails (CORE === SCOPE) | | `3e53031` | GREEN — split CORE/SCOPE fills | | `e6b078f` | RED — mobile mark-only swap assertion (#6) at 360x640; master fails (no `.brand-mark-only`) | | `1a3b5db` | GREEN — add the mark-only SVG + media-query swap | ## Files changed - `test-logo-theme-e2e.js` — assertions expanded from 3/3 to 6/6 - `public/index.html` — duotone fills + brand-wordmark grouping + brand-mark-only sibling SVG - `public/home.js` — duotone fills (hero) - `public/style.css` — `.brand-mark-only` defaults + `@media (max-width:400px)` swap rule ## Verification CI Playwright run on commit `3e53031` (after the duotone fix, before the mobile fix) confirmed assertions 1–5 pass: - `navbar duotone preserved (dark: CORE=rgb(74,158,255) SCOPE=rgb(109,179,255); light: CORE=rgb(74,158,255) SCOPE=rgb(109,179,255))` - `hero duotone preserved (dark: CORE=rgb(74,158,255) SCOPE=rgb(109,179,255); light: CORE=rgb(74,158,255) SCOPE=rgb(109,179,255))` Final CI run on `1a3b5db` will additionally exercise the 6th (mobile mark-only swap at 360×640). --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
eae1b915ca |
feat: load Aldrich webfont for #1137 logo SVG (#1138)
Adds Aldrich webfont so the merged #1137 logo renders in the intended typeface. ## Problem The inline SVG logo merged in #1137 declares `font-family="Aldrich, monospace"` in `public/index.html` and `public/home.js`, but the page never loaded the Aldrich font face. Browsers silently fell back to monospace. ## Fix Self-hosted webfont: - `public/fonts/aldrich-regular.woff2` — Regular 400, ~16KB, downloaded from Google Fonts (latin subset). Self-hosted to avoid third-party CDN dependency, privacy concern, and FOUT delay. - `@font-face` declaration added at the top of `public/style.css` with `font-display: swap`. Aldrich only ships in 400; the SVG `font-weight="700"` on the wordmark synthesizes bold (matches the design intent of #1137). ## TDD - Red commit: E2E test asserting `document.fonts.check('1em Aldrich')` is true and the navbar SVG `<text>` `font-family` contains "Aldrich". Without the font face declaration, both assertions fail on an assertion (not a build error). - Green commit: adds the woff2 + `@font-face` rule, both assertions pass. ## Files - `public/fonts/aldrich-regular.woff2` (new, 16460 bytes) - `public/style.css` — `@font-face` rule - `test-e2e-playwright.js` — new test --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
eddca7acde |
fix(live): region filter wipes feed — parse {observers:[...]} response (#1136) (#1140)
## Summary Fixes #1136. The live page region filter wiped all packets, polylines, and feed entries the moment any region was selected. Root cause: `public/live.js` parsed `/api/observers` as a top-level array, but the endpoint returns `{observers:[...], server_time:"..."}` — so `observerIataMap` stayed empty and `packetMatchesRegion` rejected every packet. This was a regression introduced in #1080 (live region filter) after the typed-struct refactor wrapped the observer list in `ObserverListResponse` (cmd/server/types.go). ## Fix - Extracted the parse into `buildObserverIataMap(data)` — a pure helper that accepts both the real `{observers:[...]}` shape and a bare array (defensive). Skips observers with no IATA so the result is a direct lookup map. - `initLiveRegionFilter` now uses the helper, so the map is populated on first paint. - Exposed `_liveBuildObserverIataMap` and `_liveGetObserverIataMap` on `window` for tests (read-only — no behavior change). Backend untouched — the API shape is correct. ## Tests (red → green) **Red commit** (`test(live): failing tests for #1136 region filter wipes feed`): - `test-issue-1136-observer-iata-map.js` — failed at "helper must be exposed" assertion (parser was inlined, not extracted). - `test-issue-1136-live-region-e2e.js` — Playwright. Loads `/#/live`, queries `/api/observers` to discover an SJC observer, asserts the live module's `observerIataMap` is populated, selects SJC via `RegionFilter.setSelected`, pushes a fixture packet through `_liveBufferPacket`, and asserts a `.live-feed-item[data-hash=...]` renders. Failed at both the "map populated" and "feed renders" assertions — exactly the user-reported symptom. - Both wired into `.github/workflows/deploy.yml` (unit step + Playwright step). **Green commit** (`fix(live): parse {observers:[...]} ...`): all five unit assertions + all five E2E assertions pass. Existing `test-live-region-filter.js` from #1080 still passes (no behavior change to `packetMatchesRegion`). ## Verification (local) ``` node test-issue-1136-observer-iata-map.js # 5/5 pass node test-live-region-filter.js # 9/9 pass (regression guard) BASE_URL=http://localhost:13581 \ CHROMIUM_PATH=/usr/bin/chromium \ node test-issue-1136-live-region-e2e.js # 5/5 pass against fixture DB ``` ## Scope - One frontend file changed (`public/live.js`). - Two new tests + 2 lines of CI wiring. - No backend changes. - No refactor of unrelated `live.js` code. - Out of scope: #1108 (the related "hide nodes not seen by region" feature request) is intentionally not addressed here. Fixes #1136 --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
494d3022f9 |
Partial fix for #1128: close audit gaps (z-scale, css-var lint, multi-viewport E2E, Bug 1+5 polish) (#1133)
## Partial fix for #1128 — closes the gaps PR #1131 left behind PR #1131 was a partial fix for the packets-page layout chaos (merged 2026-05-06 ~01:55 UTC, then the issue was reopened by the maintainer). #1131 shipped Bug 4 (`--surface` definition), the `.path-popover` flip + lower z-index, the debounced re-measure for Bug 1, the `.filter-bar` row-gap + `.multi-select-trigger` truncation for Bug 3, the new z-index TOKENS, and a single-viewport E2E with five individual-component assertions. This PR closes everything else the issue body and the `specs/packets-layout-audit.md` audit asked for. ### What changed (per gap) **Gap A — apply the z-index scale (audit Section 2)** #1131 added `--z-dropdown` / `--z-popover` / `--z-modal` / `--z-tooltip` but explicitly left existing literal values in place. This PR renumbers the 7 dropdowns/popovers the audit named: | Selector | Before | After | |---|---:|---:| | `.col-toggle-menu` | 50 | `var(--z-dropdown)` (100) | | `.multi-select-menu` | 90 | `var(--z-dropdown)` | | `.region-dropdown-menu` | 90 | `var(--z-dropdown)` | | `.node-filter-dropdown` | 100 | `var(--z-dropdown)` | | `.fux-saved-menu` | `var(--z-tooltip)` (9200) | `var(--z-dropdown)` | | `.fux-ac-dropdown` | `var(--z-tooltip)` | `var(--z-dropdown)` | | `.hop-conflict-popover` | `var(--z-tooltip)` | `var(--z-popover)` (300) | `.fux-ctx-menu` deliberately retains the tooltip band — context menus must float above all toolbar UI. `.region-filter-options-menu` no longer exists in the source (was renamed `.region-dropdown-menu`). The `style.css` doc-block at the top is rewritten to record the applied scale and to point operators at the new lint. **Gap B — CSS-var lint (audit Section 5 #1, "single highest-value addition")** Adds `scripts/check-css-vars.js` (~70 lines). Walks `public/*.css`, extracts every `var(--name)` reference WITHOUT a fallback, asserts the name is defined in some `public/*.css`. References WITH a fallback are tolerated. Wired into CI in the `go-test` job before the JS unit tests. The red commit (`608d81f`) shipped this lint exiting 1 against the master tree — three undefined vars that bypassed earlier review: ``` public/style.css:2628 var(--text-primary) public/style.css:2675 var(--bg-hover) public/style.css:2924 var(--primary) ``` The green commit (`1369d1e`) defines those three as aliases in the :root block (`--text-primary` → `--text`, `--bg-hover` → `--hover-bg`, `--primary` → `--accent`). Light + dark themes inherit through the existing tokens. **Gap C — multi-viewport E2E (issue acceptance criterion)** Adds `test-issue-1128-multi-viewport-e2e.js` — sister of the existing single-viewport test. At each of three viewports (1280×900, 1080×800, 768×1024): - takes a screenshot to `e2e-screenshots/issue-1128-<viewport>.png` - asserts no two `.filter-group` siblings vertically overlap - on desktop+laptop, opens the Saved menu and the Types multi-select and asserts the dropdown does not vertically overlap any `.filter-group` below it Plus three viewport-agnostic assertions: - dropdown selectors compute z-index in `[100,199]` (`.col-toggle-menu`, `.multi-select-menu`, `.region-filter-options-menu`, `.fux-saved-menu`, `.fux-ac-dropdown`) - `.path-hops .hop / .hop-named / .arrow` compute `line-height ≤ 18px` - `.col-path` computes `height ≤ 28px` Wired into the e2e-test job after the existing #1128 test. **Gap D — Bug 5 polish (toolbar reorder)** Audit Section 3 Bug 5: swaps `filter-group-dropdowns` and `filter-group-toggles` in `public/packets.js` so time range + Group by Hash + ★ My Nodes sit next to the search input. Pure markup reorder. No CSS / no JS-handler changes. **Gap E — Bug 1 belt-and-suspenders** Audit Section 3 Bug 1 sub-bullets: - locks `.path-hops .hop / .hop-named / .arrow` to `line-height: 18px` so a chip with mixed font metrics cannot overflow the 22px host vertically and bleed into the row above - converts `.col-path { max-height: 28px }` → `height: 28px` because browsers widely ignore `max-height` on `<td>`s; the earlier rule was a no-op ### TDD discipline (red → green) ``` $ git log --oneline origin/master..HEAD |
||
|
|
364c5766fc |
feat(logo): wire new CoreScope SVG logo into navbar + home hero (#1137)
## Adds new logo and home hero Replaces the navbar mushroom emoji + "CoreScope" text spans with the new CoreScope SVG mark, and adds a hero SVG (with the MESH ANALYZER tagline) above the home page H1. ### What changed - `public/img/corescope-logo.svg` — navbar mark, no tagline (locked "aggressive low-amp chirp" variant: facing-arcs + low-amp chirp connector between the two nodes). - `public/img/corescope-hero.svg` — home hero version, includes the MESH ANALYZER tagline. - `public/index.html` — replaces `<span class="brand-icon">🍄</span><span class="brand-text">CoreScope</span>` with `<img class="brand-logo" src="img/corescope-logo.svg?__BUST__" …>`. `.nav-brand` link still routes to `#/`. `.live-dot` retained. - `public/style.css` — adds `.brand-logo { height: 36px }` (32px on tablet ≤900px). Existing 52px nav height unchanged. - `public/home.js` / `public/home.css` — adds `<img class="home-hero-logo">` above the hero `<h1>`, sized `max-width: min(720px, 90vw)` and centered. ### TDD Red→green is visible in the branch: - `3159b82` — `test(logo): add failing E2E …` (red commit). Adds `test-logo-rebrand-e2e.js` and wires it into the `e2e-test` job in `deploy.yml` with `CHROMIUM_REQUIRE=1`. On this commit `index.html` still has the emoji + text spans, `home.js` has no hero img, and the SVG asset files do not exist — the test asserts on each so CI fails on assertion. - `19434e1` — `feat(logo): wire new CoreScope SVG logo …` (green commit). Implements the fix. ### E2E asserts 1. `.nav-brand img` exists with `src` ending `corescope-logo.svg` 2. legacy `.brand-icon` / `.brand-text` are gone 3. `.live-dot` is present, visible, and to the right of the logo (no overlap) 4. `.home-hero img.home-hero-logo` exists with `src` ending `corescope-hero.svg`, positioned BEFORE the `<h1>` 5. both `/img/corescope-{logo,hero}.svg` return 200 with svg content-type ### Customizer compatibility - `customize.js` still does `querySelector('.brand-text')` / `.brand-icon` for live branding updates. Both now return `null`; existing `if (el)` guards make those branches silent no-ops. **No JS errors, but the customizer's `branding.siteName` and `branding.logoUrl` fields no longer rewrite the navbar brand** — the brand is now a fixed SVG asset. - **Theme accent does NOT recolor the SVG.** SVGs loaded via `<img src>` are isolated documents and cannot inherit document CSS variables; the SVG falls back to its embedded brand colors. This is appropriate for a brand mark; if recoloring per theme is desired later, swap to inline SVG (separate PR). ### Browser validation Local Chromium not available in this env; the E2E test soft-skips locally and hard-fails in CI (`CHROMIUM_REQUIRE=1`). Server-side checks done locally: - `curl http://localhost:13581/` → confirmed `<img class="brand-logo" src="img/corescope-logo.svg?<bust>" …>` rendered, no `.brand-icon`/`.brand-text` spans. - `curl -I /img/corescope-logo.svg` and `/img/corescope-hero.svg` → both 200. ### Performance No hot-path changes. Two new static SVG assets (~7.6KB each), served directly by the Go static handler. Cache-busted via `?__BUST__` (auto-replaced server-side). --------- Co-authored-by: OpenClaw Bot <bot@openclaw.local> Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local> |
||
|
|
b03ef4abd3 |
fix(packets): resolve --surface undefined + z-index scale + path chip re-measure + +N popover (#1128) (#1131)
## Summary Resolves the **5 layout bugs** documented in `specs/packets-layout-audit.md` from the issue investigation. All fixes shipped in one PR per the audit's recommended fix order. Fixes #1128 ### Bug 4 (P0, 1 line) — `--surface` undefined `var(--surface)` was referenced in **8** rules across `style.css` (`.fux-saved-menu`, `.fux-popover`, `.path-popover`, `.fux-ac-dropdown`, `.fux-ctx-menu`, `.path-overflow-pill:hover`, `.fux-saved-trigger:hover`, `.fux-popover-header sticky`) but the variable was **never defined** — every caller resolved to `transparent` and row content bled through. Aliased `--surface: var(--surface-1);` in the `:root`, `@media (prefers-color-scheme: dark)`, and `[data-theme="dark"]` blocks. ### Z-index scale (foundational) Added documented custom properties at the top of `style.css`: ```css --z-base: 0; --z-dropdown: 100; --z-popover: 300; --z-modal-backdrop: 9000; --z-modal: 9100; --z-tooltip: 9200; ``` New code uses these tokens. Existing working values left in place to avoid behavioural risk. ### Bug 1 — path chip re-measure `_finalizePathOverflow` runs **before** `hop-resolver` mutates chip text from hex prefix → longer node name. Chips that fit on first measurement overflow once names resolve, but the `+N` pill never gets appended. Cleared the per-host `overflowChecked` guard and re-ran finalize on a 120 ms debounced timer, so post-resolution overflow is detected. ### Bug 2 — `+N` popover position + z-index `.path-popover` was `z-index: 10500` (above the modal stack) and only ever positioned **below** the pill — when near the bottom of the viewport it hung over adjacent rows. Lowered to `var(--z-popover)` (300), capped `max-height` from `60vh` → `240px`, and added flip-above logic when there isn't room below. ### Bug 3 — filter-bar gap + multi-select truncation `.filter-bar { row-gap: 6px }` was too tight for the 34px controls; bumped to `12px`. `.multi-select-trigger` had no `max-width`, so a selection like `"TRACE,MULTIPART,GRP_TXT"` ballooned the row and overlapped toolbar buttons. Capped `max-width: 180px` with `text-overflow: ellipsis` and surfaced the full selection in the trigger's `title` attribute (so the value remains discoverable). ### Bug 5 — already addressed in #1124 Verified `.filter-group` structure prevents mid-cluster wrap; no further change needed here. ## TDD Branch shows the required **red → green** sequence: | commit | result | |---|---| | `8ad6394` test(packets): red E2E for issue #1128 layout chaos | ✗ Bug 4 (alpha=0), ✗ Bug 2 (z=10500), ✗ Bug 3 (gap=6) | | `eacadc1` fix(packets): resolve --surface undefined + z-index scale + ... | ✓ 5/5 | Test file: `test-issue-1128-packets-layout-e2e.js` — asserts opaque dropdown background, every overflowing `.path-hops` has a `+N` pill, popover z-index ≤ 9000 + anchored to pill, filter-bar gap ≥ 10px, trigger `max-width` bounded. ## E2E Local run against the e2e fixture: ``` === #1128 packets layout E2E === ✓ navigate to /packets and wait for table + rows ✓ Bug 4: Saved-filter dropdown background is OPAQUE (alpha ≥ 0.99) ✓ Bug 1: every overflowing .path-hops has a .path-overflow-pill ✓ Bug 2: +N popover anchored to pill + z-index ≤ 9000 ✓ Bug 3: .filter-bar row-gap ≥ 10px AND .multi-select-trigger has bounded max-width === Results: passed 5 failed 0 === ``` CI hookup: please add `node test-issue-1128-packets-layout-e2e.js` alongside the other `test-issue-XXXX-*-e2e.js` invocations in `.github/workflows/deploy.yml` (line ~226). ## Files - `public/style.css` — `--surface` definition × 3 blocks, z-index scale tokens, `.path-popover`, `.filter-bar`, `.multi-select-trigger` - `public/packets.js` — flip-above popover logic, debounced re-finalize, trigger `title` - `test-issue-1128-packets-layout-e2e.js` — new E2E (red → green) --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: Kpa-clawbot <kpa-clawbot@users.noreply.github.com> |
||
|
|
f86a44d6b5 |
fix(packets): filter UX disaster — modal help, single header, bounded path rows (#1122) (#1124)
## Summary Fixes #1122 — Packets page filter UX repairs. ## Bugs addressed 1. **Filter syntax help no longer floats over the packet table.** It now opens inside a real `.modal-overlay` backdrop and is centered via the existing `.modal` flex pattern (same pattern as BYOP). 2. **Duplicate "Filter syntax" header removed.** The inner `<h3>` was redundant with the popover header `<strong>`. Help body now contains exactly one occurrence. 3. **Path column chips no longer spill into adjacent rows.** `.path-hops` now uses `flex-wrap: nowrap` + `max-height: 22px` + `overflow: hidden`; individual `.hop-named` chips cap at `max-width: 120px` with ellipsis. `td.col-path` itself caps at `max-height: 28px` so a long hop chain can never push the row past 28px regardless of hop count. 4. **Toolbar grouping documented.** Added a fenced section comment in `style.css` enumerating the four logical clusters (quick filters / toggles / time window / sort & view), bumped `.filter-bar` gap from 6→8px and added `row-gap: 6px` so wrapped controls stay readable at narrow widths. ## Test TDD red→green. New Playwright E2E `test-issue-1122-packets-filter-ux-e2e.js` asserts: - Help panel rect does not overlap any visible `#pktBody` row, and a `.modal-overlay` backdrop is present. - Help panel contains exactly 1 `Filter syntax` occurrence (not 2). - Every rendered `.col-path` cell stays under 60px height. Wired into `.github/workflows/deploy.yml` Playwright fail-fast step. Red commit: `bd58634` (test only). Green commit: `c580254` (impl). ## Files - `public/filter-ux.js` — `_showHelp` wraps the popover in `.modal-overlay`; `_buildHelpHtml` drops the duplicate `<h3>Filter syntax</h3>`. - `public/style.css` — `.modal-overlay > .fux-popover` reset, `.path-hops` clipping, `td.col-path` height cap, `.filter-bar` section comment. - `test-issue-1122-packets-filter-ux-e2e.js` — new Playwright E2E. - `.github/workflows/deploy.yml` — runs the new E2E. --------- Co-authored-by: clawbot <clawbot@example.com> Co-authored-by: Kpa-clawbot <kpa-clawbot@users.noreply.github.com> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
74dffa2fb7 |
feat(perf): per-component disk I/O + write source metrics on Perf page (#1120) (#1123)
## Summary Implements per-component disk I/O + write source metrics on the Perf page so operators can self-diagnose write-volume anomalies (cf. the BackfillPathJSON loop debugged in #1119) without SSHing in to run iotop/fatrace. Partial fix for #1120 ## What's done (4/6 ACs) - ✅ `/api/perf/io` — server-process `/proc/self/io` delta rates (read/write bytes per sec, syscalls) - ✅ `/api/perf/sqlite` — WAL size, page count, page size, cache hit rate - ✅ `/api/perf/write-sources` — per-component counters from ingestor (tx/obs/upserts/backfill_*) - ✅ Frontend Perf page — three new sections with anomaly thresholds + per-second rate columns ## What's NOT done (deferred to follow-up) - ❌ `cancelledWriteBytesPerSec` field — issue #1120 lists this under server-process I/O ("writes the kernel discarded — interesting signal"); not exposed in this PR - ❌ Ingestor `/proc/<pid>/io` — issue #1120 says "Both ingestor and server"; only server-process I/O lands here. Adding ingestor I/O requires either a unix socket back to the server, or surfacing the ingestor pid through the stats file. Doable without changing the existing API shape. - ❌ Adaptive baselining — anomaly thresholds remain static (10×, 100 MB, 90%); steady-state baselining can come once we have enough deployed Perf-page telemetry Per AGENTS.md rule 34, this PR uses "Partial fix for #1120" rather than "Fixes #1120" so the issue stays open until the remaining ACs land. ## Backend **Server (`cmd/server/perf_io.go`)** - `GET /api/perf/io` — reads `/proc/self/io` and returns delta-rate `{readBytesPerSec, writeBytesPerSec, syscallsRead, syscallsWrite}` since last call (in-memory tracker, no allocation per sample). - `GET /api/perf/sqlite` — returns `{walSize, walSizeMB, pageCount, pageSize, cacheSize, cacheHitRate}`. `cacheHitRate` is proxied from the in-process row cache (closest available signal under the modernc sqlite driver). - `GET /api/perf/write-sources` — reads the ingestor's stats JSON file and returns a flat `{sources: {...}, sampleAt}` payload. **Ingestor (`cmd/ingestor/`)** - `DBStats` gains `WALCommits atomic.Int64` (incremented on every successful `tx.Commit()` and on every auto-commit `InsertTransmission` write) and `BackfillUpdates sync.Map` keyed by backfill name with `IncBackfill(name)` / `SnapshotBackfills()` helpers. - `BackfillPathJSONAsync` now increments `BackfillUpdates["path_json"]` per row write — the BackfillPathJSON-style infinite loop becomes immediately visible at `backfill_path_json` in the Write Sources table. - New `StartStatsFileWriter` publishes a JSON snapshot to `/tmp/corescope-ingestor-stats.json` (override via `CORESCOPE_INGESTOR_STATS`) every second using atomic tmp+rename. The tmp file is opened with `O_CREATE|O_WRONLY|O_TRUNC|O_NOFOLLOW` mode `0o600` so a pre-planted symlink in a world-writable `/tmp` cannot redirect the write to an arbitrary file. ## Frontend (`public/perf.js`) Three new sections on the Perf page, all auto-refreshed via the existing 5s interval: - **Disk I/O (server process)** — read/write rates (formatted B/KB/MB-per-sec) + syscall counts. Write rate >10 MB/s flags ⚠️. - **Write Sources** — sorted table of per-component counters with a per-second rate column derived from snapshot deltas. Backfill rows show ⚠️ only when `tx_inserted >= 100` (meaningful baseline) AND the backfill's per-second rate exceeds 10× the live tx rate. Avoids the startup-spurious-alarm where cumulative-vs-cumulative was a tautology. - **SQLite (WAL + Cache Hit)** — WAL size (⚠️ when >100 MB), page count, page size, cache hit rate (⚠️ when <90%). ## Tests - **Backend** (`cmd/server/perf_io_test.go`) — `TestPerfIOEndpoint_ReturnsValidJSON`, `TestPerfSqliteEndpoint_ReturnsValidJSON`, `TestPerfWriteSourcesEndpoint_ReturnsSources` exercise the three new endpoints. Skips the `/proc/self/io` non-zero-rate assertion when `/proc` is unavailable. - **Frontend** (`test-perf-disk-io-1120.js`) — vm-sandbox runs `perf.js` with stubbed `fetch`, asserts the three new sections render with their headings + values. E2E assertion added: test-perf-disk-io-1120.js:91 ## TDD 1. Red commit (`21abd22`) — added the three handlers as no-op stubs returning empty values; tests fail on assertion mismatches (non-zero rate, `pageSize > 0`, headings present). 2. Green commit (`d8da54c`) — fills in the real `/proc/self/io` parser, PRAGMA queries, ingestor stats writer, and Perf page rendering. --------- Co-authored-by: corescope-bot <bot@corescope.local> Co-authored-by: Kpa-clawbot <kpa-clawbot@users.noreply.github.com> |
||
|
|
50676d5e65 |
fix(live): #1110 node filter — autocomplete, theming, no reload (#1113)
## Summary Fixes the broken **Filter by node** input on the Live page. The previous implementation used a native `<datalist>` (no consistent styling, no real autocomplete UX), only applied on `change` (Enter), and mutated `location.hash` on commit — which the SPA router treated as a navigation, triggering a full re-init. ## What changed - **Markup** (`public/live.js`): replaces the `<datalist>` with a styled custom `#liveNodeFilterDropdown` and adds combobox/listbox ARIA wiring. - **Styling** (`public/live.css`): new `.live-node-filter-input` rules use `color-mix` on `var(--text)` for the background and `var(--border)` / `var(--text)` for border + foreground — fully theme-aware. Dropdown uses `var(--surface-1)` + `var(--border)`. - **Behavior**: 200 ms debounced `/api/nodes/search` call as the user types. Suggestions render with name + 8-char pubkey prefix. Clicking a suggestion (`mousedown` so it beats blur) sets the filter to the pubkey. - **No reload**: `applyFilterFromInput` and the clear button now use `history.replaceState` instead of mutating `location.hash`, so the SPA router never re-runs and the page never reloads. Enter is `preventDefault`-ed and either selects the highlighted suggestion or commits the typed text. - **Keyboard**: ArrowUp/Down navigate suggestions, Esc closes, Enter selects. ## TDD Per `AGENTS.md`, the failing E2E test landed first (commit `74f3e92`), then the fix made it green (commit `a5c5c65`). The test file `test-1110-live-filter.js` (and an integrated block in `test-e2e-playwright.js`) asserts: 1. The input's computed `background-color` is **not** hardcoded white when `data-theme="dark"` is set. 2. The input is not vastly larger than the surrounding toolbar row. 3. Typing `"te"` shows a visible `#liveNodeFilterDropdown` with at least one `.live-node-filter-option`. 4. Clicking a suggestion sets `_liveGetNodeFilterKeys()` to a non-empty list **without** reloading the page (verified via a `window.__m` marker that survives) and **without** navigating away from `#/live`. 5. Pressing **Enter** in the filter input never reloads or navigates. ### How to run the E2E ``` go build -o /tmp/corescope-server ./cmd/server /tmp/corescope-server -port 13581 -db test-fixtures/e2e-fixture.db -public public & CHROMIUM_PATH=/usr/bin/chromium-browser BASE_URL=http://localhost:13581 \ node test-1110-live-filter.js # 4/4 passed ``` ## Acceptance criteria from #1110 - [x] Filter input visually matches Live page toolbar (theme-aware bg, border, padding) - [x] Typing 1+ characters shows dropdown of matching node names - [x] Selecting a suggestion filters the live feed immediately - [x] Clearing input restores unfiltered view - [x] No page reload on any interaction with the input - [x] E2E test asserts: type → suggestions appear → click suggestion → feed filters → no navigation Fixes #1110 --------- Co-authored-by: Kpa-clawbot <kpa-clawbot@users.noreply.github.com> |