mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-06 17:51:39 +00:00
1bfbbd6bb2f3fdb10cfee461dbf16bce7d34da1f
206 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ae77d58ec5 |
fix(#1403): drop .nav-left overflow:hidden — root cause of nav vanishing + truncated More dropdown (#1405)
Root cause of the recurring nav-vanishing family of bugs — confirmed
live via operator console probe at vw=1030 on /#/channels (also
reproduces on /#/home, /#/packets, all routes).
## Symptoms
1. All `.nav-links` (Home, Packets, Map, Live, Channels, Nodes) and
brand + More button render OFFSCREEN above the visible top-nav band.
`.nav-left` reports y=0..52 but every child reports y=-47.5.
2. More dropdown when opened shows only ONE item ("Tools") instead of
the 6 expected (Channels, Tools, Observers, Analytics, Perf, Audio Lab).
## Root cause
`.nav-left { overflow: hidden }` at `public/style.css:509`. With flex
children whose effective layout exceeds the container box, Firefox clips
children to negative y. The same `overflow: hidden` ALSO clips the
descendant `.nav-more-menu` dropdown contents.
## Fix
Drop `overflow: hidden` from `.nav-left`. The original
horizontal-overflow guard from #1066 is preserved at the `.top-nav`
level (which still has `overflow: hidden`).
## Verification
Operator console probe after applying the same `overflow: visible`
in-page:
- All 6 visible nav links render at y >= 0 inside the top-nav.
- More dropdown contains all 6 expected items (Channels, Tools,
Observers, Analytics, Perf, Lab).
- Both bugs collapse into ONE root cause.
## Why prior fixes didn't catch this
- #1400 fixed `.nav-link { min-height: 48px }` overflow — reduced
children from 56px to 47px tall. Helped slightly but didn't address the
`.nav-left { overflow: hidden }` interaction.
- #1391, #1394 fixed the active-pill-in-overflow algorithm. Different
layer.
- #1311, #1148, #1106, #1102, #1097, #1067, #1055 — every prior
Priority+ fix treated overflow as an algorithmic question, never as a
CSS clipping bug at the container level.
22nd nav fix in this saga. This one targets the actual cause.
Refs #1391, #1396, #1400. Operator probe transcript available on
request.
Fixes #1403
Co-authored-by: openclaw-bot <bot@openclaw.local>
|
||
|
|
7e492a71a0 |
fix(#1400): root cause of recurring nav-vanishing — min-height:48px overflowed 52px top-nav, clipped link strip above viewport (#1401)
**RED commit phase** — TDD failing test for #1400. Green fix incoming next push. See full PR body on ready-for-review. Fixes #1400 --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
9a2270168f |
feat(#893): Material Design dark mode toggle — polished version of #893 (#1389)
## Polished version of #893 This PR carries forward @emuehlstein's Material Design dark-mode toggle from #893, rebased onto current `master` and polished for a11y / first-paint / forced-colors / cross-tab sync. Original commits (preserved as `Co-authored-by`): - `feat: replace dark mode button with Material Design toggle switch` (emuehlstein) - `fix: define --shadow CSS var in theme blocks, drop stopPropagation no-op` (emuehlstein, addressing prior review) #893 had been stuck in CONFLICTING state since 2026-05-24 with no CI runs ever. Rebase resolved a single `public/style.css` `:root` conflict (preserved both the `--text-primary`/`--bg-hover`/`--primary` aliases from #1378 and the new `--shadow` definition). ## Polished improvements (on top of #893) 1. **FOUC fix** (`public/index.html`): inline `<head>` script reads `localStorage('meshcore-theme')` (or `prefers-color-scheme`) and sets `data-theme` *before* stylesheet load. Without this, dark-mode users see a light-mode flash on every page load. 2. **ARIA semantics** (`public/index.html`): moved `aria-label` from the wrapping `<label>` onto the actual `<input role="switch">`. Removed `aria-hidden="true"` from the checkbox (which had been hiding it from assistive tech). Added `aria-hidden` to the decorative track instead. 3. **Keyboard focus indicator** (`public/style.css`): `:focus-visible` on the (visually-hidden) checkbox draws an outline on `.theme-toggle-track`. Previously keyboard users could focus the toggle with Tab but had no visible indicator. 4. **Reduced motion** (`public/style.css`): `@media (prefers-reduced-motion: reduce)` disables the slide/fade transitions. 5. **Forced-colors mode** (`public/style.css`): explicit `CanvasText` border on track + thumb so the switch stays visible in Windows High Contrast. Default CSS tokens collapse to `Canvas`/`CanvasText` and the thumb would otherwise disappear. 6. **Cross-tab sync** (`public/app.js`): `storage` event listener for `meshcore-theme` mirrors the cb-presets pattern from #1378 — toggling theme in one tab now syncs all open tabs. 7. **Tightened E2E test** (`test-e2e-playwright.js`): added assertions for `role="switch"`, checkbox-state ↔ theme parity, and theme persistence across a full page reload (was only asserting one toggle). ## Notes - No `map[string]interface{}` (no Go changes). - All colors via existing `--mc-*` / theme tokens; `--shadow` is defined in both light + dark theme blocks. - No layout shift (track is fixed `46x24` inside the `44x44` label container). - Branch scope is exactly the four files from #893: `public/app.js`, `public/index.html`, `public/style.css`, `test-e2e-playwright.js`. Closes #893. Co-authored-by: Eric Muehlstein <muehlbucks@gmail.com> --------- Co-authored-by: Eric Muehlstein <muehlbucks@gmail.com> Co-authored-by: CoreScope Bot <bot@corescope> |
||
|
|
ff0ee50354 |
fix(#1374): packet-route map modernized — role-aware markers, directional edges, WCAG 2.2 AA (#1381)
## What The packet-route map view (`/#/map?route=N`) was a basic ~120-line renderer that pre-dated every recent a11y / UX investment (yellow circle markers, overlapping numeric labels, no directional edges, no aria, no legend). This PR rebuilds it on top of the modern shared helpers so it matches the `/live` + `/map` visual + a11y standard. Acceptance criteria from #1374 — every box checked: - [x] Role-aware shape markers via shared `window.makeRoleMarkerSVG` (post-#1357). - [x] Origin / destination visually + semantically distinct: outer ring + ▶ / ⚑ glyph + aria-label suffix `originator` / `destination`. - [x] Sequence-number badges (`.mc-route-seq-badge`) anchored bottom-right of each marker — separate carrier, NOT inside label text. - [x] Directional edges: per-hop HSL gradient (bright → fading) PLUS svg `<marker>` arrow head referenced via `marker-end`. Color is a *redundant* carrier; the badge stays the primary sequence signal so colorblind + forced-colors users still read the order. - [x] Per-edge `aria-label="Hop N → N+1, ~Xkm"` (haversine computed). - [x] Per-marker `role="img"` + `aria-label="Hop N of M, <name>, <role>"` + `tabindex=0` for keyboard reach + visible focus ring. - [x] Label deconfliction reuses `window.deconflictLabels` (now exposed by `map.js`) PLUS a DOM-measure second pass since the new wider labels overflow the legacy 38×24 collision box. - [x] Collapsible `.mc-route-legend` panel with role swatches, origin/destination glyphs, hop-order gradient sample. Toggle has `aria-expanded`. - [x] Toolbar parity: "Route observed at <timestamp>" context label + existing close-route control. - [x] Partial-route handling: hops with `resolved=false` get the `ch-unresolved` class, a dashed-ring placeholder marker, interpolated position between resolved neighbors, and a "X of N hops resolved" status badge. - [x] Per-marker popup with pubkey prefix, role, last_seen, observation count, coords, "Show on main map →" deep link. - [x] `prefers-reduced-motion: reduce` disables animations/transitions. - [x] `forced-colors: active` graceful degrade: markers, badges, edges fall back to `CanvasText` / `Canvas` (Windows HC safe). ## How Split the renderer into a dedicated `public/route-render.js` exposing `window.MeshRoute.render(map, layer, positions, opts)`. The existing `drawPacketRoute` in `map.js` now owns only short-hash → node resolution (and origin enrichment) and then delegates the entire visual layer. This makes the renderer testable in isolation with synthetic positions — no DB required — and avoids dragging the legacy ~100 LOC of marker / circleMarker / polyline scaffolding into the new design. Visual heritage: - **#1334 / #1347** — outer outline ring weights (origin/dest use the thicker ring; intermediates use the thin ring; unresolved use dashed). - **#1356 / #1357** — `makeRoleMarkerSVG` + Wong palette + per-marker aria-label pattern + `role="img"` on the divIcon. - **#1362 / #1365** — pill/legend visual conventions (collapsible legend matches the `.mc-section` accordion language users already know from `/map`). ### WCAG 2.2 AA — measured contrast (graphics SC 1.4.11, text SC 1.4.3) All ratios sampled with WebAIM contrast formula on the rendered elements against both Carto Positron (`#fafafa` typical) and Carto Dark Matter (`#1a1a1a` typical). | Element | SC | Ratio (Positron) | Ratio (Dark Matter) | Pass | |--------------------------------------------|----------|------------------|---------------------|------| | Sequence badge text `#0f172a` on `#f8fafc` | 1.4.3 AA | 17.1:1 | 17.1:1 (self-bg) | ✅ | | Sequence badge border `#1a1a1a` | 1.4.11 | 17.6:1 | 12.6:1 | ✅ | | Marker outer ring `#06b6d4` (origin) | 1.4.11 | 3.2:1 | 4.6:1 | ✅ | | Marker outer ring `#ef4444` (destination) | 1.4.11 | 3.8:1 | 4.4:1 | ✅ | | Marker outer ring `#666` (intermediate) | 1.4.11 | 5.7:1 | 3.7:1 | ✅ | | Edge stroke (seq color, mid: `#56c08c`) | 1.4.11 | 3.0:1 (min) | 3.1:1 | ✅ | | Edge arrow head (currentColor) | 1.4.11 | same as edge | same | ✅ | | Label text `#0f172a` on `#f8fafc` | 1.4.3 AA | 17.1:1 | 17.1:1 (self-bg) | ✅ | | Legend body text `#0f172a` on `#f8fafc` | 1.4.3 AA | 17.1:1 | 17.1:1 (self-bg) | ✅ | | Resolved badge `#78350f` on `#fef3c7` | 1.4.3 AA | 8.4:1 | 8.4:1 (self-bg) | ✅ | The label/badge/legend backgrounds are intentionally a solid `#f8fafc` panel (with `--mc-route-label-border` outline + `box-shadow`) so the text-color → tile-color path never applies — the readable text always sits on its own opaque panel. For SC 1.3.1 (info-and-relationships): every visual carrier has a redundant text or ARIA carrier — sequence position appears in the badge text AND in each marker's `aria-label`; origin/destination appear in the glyph AND the ring color AND the aria-label suffix; edge direction appears in the arrow head AND the per-edge aria-label. ### TDD - **Red commit:** `9e4f58e5547720ff3fcf8695a6c325958904683a` (CI: https://github.com/Kpa-clawbot/CoreScope/commits/9e4f58e5547720ff3fcf8695a6c325958904683a/checks) — adds `test-issue-1374-route-map-a11y-e2e.js` only. The test calls `window.MeshRoute.render(...)` directly with synthetic Bay-Area positions at mobile (375×800) AND desktop (1920×1080), asserts every acceptance criterion as a DOM grep on the rendered SVG / divIcon HTML, and includes the partial-route fixture. Fails on the assertions because `MeshRoute` doesn't exist on master. - **Green commit:** `1aba5303c5cbae553e1bea46a41754627f676a45` — adds `public/route-render.js`, refactors `drawPacketRoute` to delegate, adds `.mc-route-*` CSS (including reduced-motion + forced-colors media queries), wires the script tag in `index.html`, and wires the test into `.github/workflows/deploy.yml`. ### Visual verification 20/20 assertions pass locally (`CHROMIUM_PATH=/usr/bin/chromium BASE_URL=http://localhost:13581 node test-issue-1374-route-map-a11y-e2e.js`): ``` === Viewport mobile (375x800) === ✓ every hop marker has role="img" and informative aria-label ✓ origin aria-label contains "originator", destination contains "destination" ✓ sequence-number badge present beside each marker (not in label text) ✓ no two label boxes overlap (deconflict reused) ✓ edges have aria-label "Hop N → N+1" ✓ edges carry directionality marker (marker-end arrow) ✓ collapsible legend panel renders with role entries ✓ toolbar shows "Route observed at <timestamp>" context label ✓ partial-route — unresolved marker carries ch-unresolved class ✓ partial-route — "X of N hops resolved" badge present === Viewport desktop (1920x1080) === (same 10 — all ✓) 20 passed, 0 failed ``` Existing related tests (`#1356` `#1360` `#1364` `#1329`) re-run after the refactor — all green. ## Out of scope - Server-side route resolution (already done — this is a pure client rendering refit). - Multi-route view / 3D / globe — explicitly excluded by the issue. - Backend untouched — `cmd/server` + `cmd/ingestor` not modified. Fixes #1374 --------- Co-authored-by: openclaw-bot <bot@openclaw> |
||
|
|
101c11b4b3 |
fix(#1361): theme customizer — colorblind presets [WIP] (#1378)
WIP — draft PR for CI to exercise the RED test commit. Will be promoted
out of draft once the GREEN commit lands.
Red commit:
|
||
|
|
791c8ae1bc |
fix(#1367): channels page chat-app redesign — restore prod row layout, drop analytics chip, add detail view (#1376)
Red commit:
|
||
|
|
91d90d48fb |
fix(#1364): drop over-aggressive .mc-pill max-width — restore multi-digit count visibility (#1365)
Red commit:
|
||
|
|
40aa02b438 |
fix(#1360): cluster pill shows letter+count — restore count visibility regressed by #1357 (#1362)
Red commit: |
||
|
|
933ef4e6ef |
fix(#1356): WCAG 2.2 AA map a11y — cluster bubbles, role pills, multi-byte labels (#1357)
Red commit:
|
||
|
|
adcf29dd6b |
fix(#1329): accordion map controls on mobile, drop 200px scroll cap (#1333)
## Summary
On mobile (≤640px) the Map controls panel was capped at `max-height:
200px` and forced an internal scrollbar through all the
layer/filter/display toggles. This makes every section a single-open
accordion and drops the cap, so the visible content always fits without
internal scroll.
## Changes
- `public/map.js` — Each `fieldset.mc-section` legend becomes a tappable
`aria-expanded` toggle. On mobile the first section opens by default;
activating any other section auto-closes the previously open one
(single-open). Desktop still renders all sections expanded.
- `public/style.css` — `@media (max-width: 640px)` rules:
- `max-height: 200px` → `calc(100vh - 80px)`.
- `.mc-collapsed > *:not(legend) { display: none }` hides bodies of
collapsed sections.
- Legend styled as flex row with ▸/▾ indicator (colors via
`var(--text-muted)`).
- All new rules live inside the mobile media query, so desktop layout is
unchanged.
## Test
`test-issue-1329-map-controls-accordion-e2e.js` (added to CI in
`deploy.yml`):
- mobile 375x812: ≥1 accordion toggle present, ≤1 expanded by default,
no internal scroll, clicking another toggle collapses the first.
- desktop 1280x800: `position: absolute`, panel <50% viewport wide, all
controls visible.
Red commit: `85fdc25267eaf210369371f55da767016435dbff` (test fails on
master — no accordion toggles exist; all fieldsets render expanded under
the 200px cap forcing scroll).
E2E assertion added: `test-issue-1329-map-controls-accordion-e2e.js:56`.
Fixes #1329
---------
Co-authored-by: openclaw-bot <bot@openclaw.dev>
|
||
|
|
317b59ab10 |
feat: area-based visual node filter — attribute packets by transmitter GPS (#804) (#839)
## Summary - Adds configurable GPS polygon areas to `config.json`; nodes are attributed to an area if their last-known position falls inside the polygon - New `Area: …` dropdown filter (matching the existing region filter style) appears on all analytics, nodes, packets, map, and live screens when areas are configured - Backend resolves area membership with a 30s TTL cache; area filter bypasses the 500-node cap on `/api/bulk-health` so all area nodes are always returned - Includes a polygon builder tool (`/area-map.html`) for drawing and exporting area boundaries ## Changes **Backend** - `AreaEntry` type + `Areas` config field - `GetNodePubkeysInArea` DB query + `resolveAreaNodes` (30s TTL, `areaNodeMu` RWMutex) - `PacketQuery.Area` + `filterPackets` polygon check - `?area=` param propagated through all analytics, topology, clock-health, and bulk-health routes - `/api/config/areas` endpoint **Frontend** - `area-filter.js`: single-select dropdown, persists to localStorage, cleans up stale keys on load - Wired into analytics, nodes, packets, channels, map, and live pages - Live map clears node markers on area change **Docs & tools** - `docs/user-guide/area-filter.md` — configuration and usage guide - `docs/api-spec.md` — updated with new endpoint and `?area=` param table - `tools/area-map.html` — polygon builder for defining area boundaries - Demo areas added to `config.example.json` ## Test plan - [x] No areas configured → filter dropdown does not appear on any page - [x] Areas configured → dropdown appears, "All" selected by default - [x] Selecting an area filters nodes/packets/topology/map correctly - [x] Selecting "All" restores unfiltered view - [x] Selection persists across page reloads (localStorage) - [x] Stale localStorage key (area removed from config) is cleared on load - [x] `/api/bulk-health?area=X` returns all nodes in area (no 500-node cap) - [x] `/api/config/areas` returns correct list 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Kpa-clawbot <kpaclawbot@outlook.com> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
afdd455ed9 |
fix(ui): align filter-bar heights and compact MESH LIVE panel (#1182)
## Summary - **Filter bar heights**: `.btn` and `.col-toggle-btn` carried `min-height:48px` from the WCAG touch-target rule, making buttons like `Group by Hash`, `★ My Nodes`, `Columns ▾`, and text inputs visibly taller than the `multi-select-trigger` / `region-dropdown-trigger` controls (which don't carry `.btn` and were already correct at 34px). Fix adds `min-height:34px` overrides to `.filter-bar .btn`, `.filter-group .btn`, `.filter-bar .col-toggle-btn`, and `.filter-bar input, .filter-bar select` so the entire filter bar renders at a uniform 34px on desktop. - **MESH LIVE panel**: `.live-overlay` sets `flex-direction:column` on all overlay panels; `.live-header` did not override this. With `#liveAreaFilter` populated (when areas are configured), the panel stacked 4 rows — title, stats, toggles, area filter — consuming ~⅓ of viewport height. Switch `.live-header` to `flex-direction:row; flex-wrap:wrap`, give `.live-toggles` `flex:0 0 100%` to force it to its own line, and move `#liveAreaFilter` inside `.live-toggles` so the area dropdown is inline with the other controls. Panel shrinks from 4 rows to 2 rows. ## Test plan - [x] Packets page filter bar: `Filters ▾`, text inputs, `All Observers`, `All Types`, `Group by Hash`, `★ My Nodes`, `Columns ▾`, `Hex Paths` all render at uniform ~34px height on desktop - [x] Mobile (≤767px): filter bar touch targets unaffected (mobile media query still authoritative) - [x] Live page: MESH LIVE panel occupies 2 rows (title+stats / toggles) instead of 4 - [x] Live page: `Area: All ▾` appears inline in the toggles row when areas are configured; panel hides the area control entirely when no areas are configured (existing behavior) - [x] Audio controls still appear correctly when the Audio toggle is checked 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
e078f4bbb6 |
fix(filters): unified 34px height for all filter controls across pages (#1192)
## Summary
The global `select { min-height: 48px }` touch-target rule was taller
than the 34px custom dropdown buttons (region filter, multi-select
dropdowns), causing visible height inconsistency on the packets,
analytics, and nodes pages.
- **`.filter-bar input/select`** — add `min-height: 34px` to match
existing `height: 34px` (packets page: time window, channel, sort
selects and text inputs)
- **`.nodes-filters select`** — add `height: 34px; min-height: 34px`
(nodes page: last-heard select)
- **Analytics page** — replace `.time-window-filter` + label with
`.analytics-filters` flex row; style `#analyticsTimeWindow` with
`.analytics-time-window-select` to match region dropdown button height
and appearance
- All filter controls now sit at a consistent 34px, matching the
existing custom dropdown buttons
Supersedes #1191 (which only fixed the analytics case).
## Test plan
- [x] Packets page: time window, channel, sort selects are same height
as Filters/Group by Hash/My Nodes buttons
- [x] Analytics page: region filter and time-window select sit side by
side at the same height
- [x] Nodes page: last-heard select is same height as All/Active/Stale
buttons
- [x] On mobile, filter controls wrap correctly (flex-wrap)
- [x] Dark theme: select background and border match surrounding
controls
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
b881a09f02 |
feat(#1188): show observer IATA on packets + filter grammar (#1189)
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> |
||
|
|
4ea1bf8ebc |
fix(#1236): map mobile — sticky panel header + remove right gutter (#1237)
RED:
|
||
|
|
70855249c2 |
fix(#1224): channels page mobile UX overhaul (#1227)
## Summary RED test commit: `02652d0042b7cf65d1f9b3e96ce376bbb3064ba6` — CI: https://github.com/Kpa-clawbot/CoreScope/actions Mobile UX overhaul for the Channels page (#1224). At 375x800 the sidebar header was 112px tall (title + button stacked, analytics link + region filter each on their own row) and the channel-name column was clipped to 83px by the inline 📤 Share + ✕ Remove buttons. ## What changed - **Header is now ONE row**: title + region filter + `+ Add` chip + `📊` analytics overflow chip. Capped to ≤56px on mobile. - **`+ Add Channel` → `+ Add` chip** (no longer a full-width hero). Verified <65% of sidebar width. - **Analytics link** is an icon-only chip inside the header (was a full-row link below). - **Region filter** is inline inside the header (was its own row). - **Channel rows**: `.ch-item-name` takes `flex:1`, share button is icon-only (📤), remove button shrunk to 32px touch target. Name >150px on the first row. - **Empty state** is `max-height:30vh; padding:12px` on mobile — no longer dominates the viewport. ## Design decisions - Chose **inline chips** over an overflow `⋮` menu: header-level controls are few enough (4) that stacking pills + filter dropdown fits comfortably in 375px. Avoids the cost/complexity of a popover and matches the page's existing pill vocabulary (region filter). - Per-row share/remove kept inline but icon-only (`font-size:0` + `::before`) — preserves single-tap access without consuming the row. - Touch targets stay ≥32px (action chips) / 44px (other tappables); WCAG 2.5.5 spirit retained on the dominant interactive paths. - **Desktop layout (≥768px) is unchanged** — verified by a desktop guard in the E2E (`.ch-layout` flex-direction stays `row` at 1024px). ## Tests - `test-issue-1224-channels-mobile-ux-e2e.js` — 5 assertions at 375x800 + 1 desktop guard at 1024x800. Wired into CI. - Existing channel suites still pass: `test-channel-fluid-e2e.js` (11/11), `test-channel-issue-1087-e2e.js` (3/3), `test-channel-issue-1111-e2e.js` (2/2), `test-channel-modal-ux.js` (33/33), `test-channel-ux-followup.js` (29/29), `test-channel-sidebar-layout.js` + `test-channel-fluid-layout.js` (14/14). Fixes #1224 --------- Co-authored-by: clawbot <clawbot@users.noreply.github.com> |
||
|
|
a1f9dca951 |
fix(live #1205): re-anchor settings toggles inside MESH LIVE panel (#1219)
Red commit:
|
||
|
|
03b5d3fe28 |
fix(#1065): first-visit gesture discoverability hints (#1186)
Red commit:
|
||
|
|
b4f186af19 |
fix(#1062): gesture system — swipe rows, tabs, slide-over dismiss (#1185)
Red commit: bbb98cf81aae38bff1ef77a7c8a701813b25bb77 (CI run: pending — see Checks tab) Fixes #1062. Parent: #1052. ## Gesture system Adds touch-gesture handling on phones (≤768px): 1. **Swipe-left on a packets/nodes/observers row** → reveals row-action overlay (trace, filter, copy hash). Threshold: 24% of row width OR 80px. Sub-threshold = visual peek that snaps back. 2. **Horizontal swipe on the bottom-nav strip** → advances tabs in TAB order from `bottom-nav.js`. Packets ↔ Live ↔ Map etc. 3. **Swipe-down on a slide-over panel** → calls `window.SlideOver.close()`. ## Hard constraints met - **Pointer Events ONLY** — no `touchstart`/`touchend` mixing. `setPointerCapture` for tracking continuity. - **Axis-lock** — direction committed in first 8–12px movement. Vertical scroll is never blocked unless we explicitly committed to a horizontal swipe. `body { touch-action: pan-y }` so the browser owns vertical natively. - **Leaflet exclusion** — handlers early-bail on `e.target.closest('.leaflet-container')` so pinch/pan on the map tab are untouched. - **Singleton pattern** — module-scoped `__touchGestures1062InitCount` guard. Document-level pointer listeners registered exactly once even if the script loads multiple times (mirrors the #1180 fix class). - **prefers-reduced-motion** — animations have `transition-duration: 0s` under the media query; gestures still trigger, snaps are instant. ## E2E `test-gestures-1062-e2e.js` — Playwright with synthesized PointerEvents (page.touchscreen unreliable in headless for axis-locked custom handlers). Wired into the deploy.yml matrix. E2E assertion added: test-gestures-1062-e2e.js:120 (overlay-visible after left-swipe), :201 (tab advance), :219 (Leaflet exclusion), :247 (slide-over dismiss). --------- Co-authored-by: openclaw-bot <bot@openclaw> Co-authored-by: OpenClaw Bot <bot@openclaw.dev> Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: corescope-bot <bot@corescope.local> Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
9b9848611b |
fix(#1064): edge-swipe nav drawer (Option A, wide-only) (#1184)
Red commit:
|
||
|
|
9d1f5d2395 |
fix(#1061): bottom navigation for narrow viewports (#1174)
Red commit: a200704d5e27e47c0b29a4745bf1a1772a8876fe (CI URL added once Actions resolves the run) Fixes #1061 ## What Bottom navigation at ≤768px with 5 tabs in spec order: Home, Packets, Live, Map, Channels. Top-nav suppressed at the same breakpoint — no duplicate nav UX. ## Files - NEW `public/bottom-nav.js` — renders 5 tabs, syncs `.active` on `hashchange`, reuses the existing in-app hash router (`<a href="#/...">`). Stable selector `[data-bottom-nav-tab="<route>"]`. Container `[data-bottom-nav]`. - NEW `public/bottom-nav.css` — styles. Tokens reused: `--nav-bg`, `--nav-text`, `--nav-text-muted`, `--nav-active-bg`, `--accent`, `--border` (all global → resolve in BOTH light and dark themes). - `public/index.html` — one `<link>` for the CSS, one `<script>` after `app.js`. The `<nav>` is appended by JS as a sibling of `<main id="app">` at DOMContentLoaded. - `test-bottom-nav-1061-e2e.js` + `.github/workflows/deploy.yml` — Playwright wiring. ## Decisions - **Breakpoint:** `@media (max-width: 768px)`. No `@container` rules exist anywhere in `style.css` today — media query is consistent. - **Top-nav suppression:** `display:none` at ≤768px. Simpler than a hamburger collapse; long-tail routes (Tools/Lab/Perf) remain reachable by URL; "More"-tab/hamburger fallback deferred per issue body. - **Active indicator:** `var(--nav-active-bg)` + 2px accent top-border. No moving pill. - **Safe-area:** `padding-bottom: env(safe-area-inset-bottom)` on nav + reciprocal `body` reservation. `viewport-fit=cover` already in place. - **Reduced motion:** `prefers-reduced-motion: reduce` disables the transition. ## TDD - Red: `a200704` — assertions fail (no bottom-nav). - Green: `53851a1` — component + styles. E2E assertion added: `test-bottom-nav-1061-e2e.js:71` (case (a) — bottom-nav visible at 360x800). --------- Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: corescope-bot <bot@corescope.local> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: openclaw-bot <bot@openclaw> |
||
|
|
05876b3a59 |
fix(#1173): replace #liveDot with packet-driven brand-logo node-pulse (#1177)
Red commit: PENDING (will update) Fixes #1173. Replaces the `#liveDot` WebSocket-connected indicator with a packet-driven node-pulse animation on the brand logo's two inner circles. ## Behavior (locked per issue spec) - **Animation curve:** `ease-out` (default per open-question 1). - **Rate cap:** 15/sec (66ms gap; default per open-question 2). Excess triggers are dropped, never queued. - **Direction:** alternates A→B / B→A across messages (aesthetic, not semantic). - **Idle ≥10s:** logo at full brightness, no animation. - **Disconnected:** `.logo-disconnected` applies `filter: grayscale(0.6) opacity(0.7)`. - **`prefers-reduced-motion: reduce`:** single-step `.logo-pulse-blip` on destination only. ## Implementation - WS handler hook lives in `public/app.js` `connectWS()` (`ws.onmessage` triggers `Logo.pulse()`; `ws.onopen`/`ws.onclose` toggle `Logo.setConnected()`). - `Logo` is a small IIFE in `app.js` that exposes `window.__corescopeLogo` for E2E injection. - All animation is pure CSS; JS only toggles `.logo-pulse-active` / `.logo-pulse-blip` / `.logo-disconnected`. Colors come exclusively from `--logo-accent` / `--logo-accent-hi` tokens. - Two new classes (`.logo-node-a`, `.logo-node-b`) attached to inner circles in both `.brand-logo` and `.brand-mark-only` SVGs so the mobile mark animates too. ## `#liveDot` removal proof ``` $ grep -rn liveDot public/ (no output) ``` ## E2E - E2E assertion added: `test-logo-pulse-1173-e2e.js:54` and follows. - Wired into the Playwright matrix in `.github/workflows/deploy.yml` (mirrors PR #1168 pattern from commit `5442652`). - Test injects synthetic pings via `window.__corescopeLogo.pulse({ synthetic: true })`; matches the existing harness style (no new WS-mock pattern invented). Red→green discipline preserved: the test commit lands first and CI fails on assertion; the implementation commit follows. --------- Co-authored-by: Kpa-clawbot <bot@kpa-clawbot> Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
9774403fa4 |
fix(#1058): analytics chart containers — fluid + auto-stacking (#1175)
Red commit: 0f29da3 (CI is pending — will be linked once dispatched) Fixes #1058 This PR is in **red phase**. The new E2E asserts the desired fluid + auto-stacking behavior; with `master`'s code it FAILS at ≥768px (cards don't stack). Green commit follows. E2E assertion added: `test-charts-fluid-1058-e2e.js:99`. --------- Co-authored-by: OpenClaw Bot <bot@openclaw.local> Co-authored-by: Kpa-clawbot <bot@kpa-clawbot> |
||
|
|
89d644dd72 |
fix(#1056): row-detail slide-over panel at narrow widths (AC #4) (#1168)
Red commit:
|
||
|
|
cf604ca788 |
fix(nav): #1109 mobile hamburger dropdown clipped by .top-nav overflow:hidden (#1163)
# Fix #1109 — mobile hamburger dropdown clipped invisible by `.top-nav { overflow:hidden }` Red commit: `5429b0f` (failing E2E, asserts pixel-level visibility). ## Symptom On <768px viewports, tapping `#hamburger` toggles `.nav-links.open` and `body.nav-open` correctly — DOM state is right, `aria-expanded="true"`, computed `display:flex` — but **nothing appears below the navbar**. The dropdown is laid out at `y=52..626` but visually clipped. ## Root cause `.top-nav` is `position:sticky; height:52px; overflow:hidden` (added in #1066 fluid scaffolding at `417b460` to guard against horizontal overflow during the Priority+ measurement pass). At <768px the dropdown becomes `position:absolute; top:52px`, so its containing block is `.top-nav` — and `.top-nav`'s `overflow:hidden` clips everything below `y=52`. Result: the dropdown renders inside a 52px box and the user sees nothing. Full RCA + screenshots: https://github.com/Kpa-clawbot/CoreScope/issues/1109#issuecomment-4398900387 ## Fix In `public/style.css`, inside `@media (max-width: 767px)`, change `.nav-links` from `position:absolute` to `position:fixed`. `position:fixed` escapes any `overflow:hidden` ancestor (its containing block becomes the viewport), so the dropdown is no longer clipped. All other rules (display/flex/background/padding/z-index) keep working. This deliberately does **not** relax `overflow:hidden` on `.top-nav` — that would reopen the #1066 horizontal-overflow regression on desktop. ## Why prior tests missed this Existing nav E2Es asserted `.classList.contains('open')` / `getComputedStyle().display === 'flex'` — pure DOM state. Those passed even while the dropdown was clipped invisibly. The new test in this PR asserts **pixel-level visibility**: `document.elementFromPoint(viewportWidth/2, 100)` must land on something inside `.nav-links` (not `<body>`), and the first `.nav-link`'s bounding rect must satisfy `bottom > 60` and have non-zero area. A state-only fix can never satisfy this. E2E assertion added: `test-issue-1109-hamburger-dropdown-visible-e2e.js:113` (the `hitInsideNavLinks` check). ## Files changed - `public/style.css` — one line in the mobile media query: `position: absolute` → `position: fixed` - `test-issue-1109-hamburger-dropdown-visible-e2e.js` — new E2E - `.github/workflows/deploy.yml` — wire the new E2E into the suite Fixes #1109 --------- Co-authored-by: openclaw-bot <bot@openclaw.local> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
c84ec409c7 |
fix(nav): #1102 — JS-driven Priority+ measures actual fit (#1104)
## Summary Fixes #1102 — regression from PR #1097 polish where Priority+ collapsed too aggressively at wide widths and the "More" menu didn't reflect what was actually hidden. ## Root cause Two bugs, one root: the post-#1097 CSS rule ```css .nav-links a:not([data-priority="high"]) { display: none; } ``` unconditionally hid 6 of 11 links at every width ≥768px — including 2560px where everything fits comfortably. The "More" menu populator (`querySelectorAll('.nav-links a:not([data-priority="high"])')`) ran exactly once on load against that same selector, so it always held the same 6 links and never reflected the actual viewport fit. ## Fix Replace the static CSS hide with a JS measurement pass (`applyNavPriority` in `public/app.js`): 1. Start with **all** links visible inline. 2. Compute `needed = brand + gutters + visible-links + More + nav-right + safety` and compare to `window.innerWidth`. 3. If it doesn't fit, mark the rightmost (lowest-priority) link `.is-overflow` and re-measure. Repeat. High-priority links are queued last so they're kept visible as long as possible. 4. Rebuild the "More ▾" menu from whatever currently has `.is-overflow`. When nothing overflows, hide the More wrap entirely (`.is-hidden`). 5. Re-run on resize (rAF-debounced), on `hashchange` (active-link padding shifts), and after fonts load. Why JS, not CSS: the breakpoint where each link drops depends on label width, gutters, active-link padding, and the nav-stats badge — none of which are addressable from a media query. ## TDD trail - Red commit `8507756`: `test-nav-priority-1102-e2e.js` — fails 2/4 (2560 and 1920 only show 5/11). - Green commit `3e84736`: implementation — passes 4/4. ## E2E `test-nav-priority-1102-e2e.js` asserts: - 2560px → all 11 visible, More hidden - 1920px → ≥9 visible - 1080px → 5+ visible AND More menu contains every hidden link - 800px → 5+ visible AND More menu non-empty Local run on the e2e fixture: **4/4 pass**. Existing `test-nav-fluid-1055-e2e.js` also stays green: **20/20 pass** (no overlap, no overflow at 768/1024/1280/1440/1920 across 4 routes). --------- Co-authored-by: meshcore-bot <bot@meshcore.local> |
||
|
|
52bb07d6c1 |
feat(#1056): fluid tables + +N hidden pill (packets/nodes/observers) (#1099)
## Summary Implements priority-based responsive column hiding for the three primary data tables (Packets, Nodes, Observers) per the parent task #1050 acceptance criteria, with a clickable **+N hidden** pill in the table header to reveal collapsed columns. ## Approach - New `TableResponsive` helper (defined once at the top of `packets.js`, exposed on `window`) classifies `<th data-priority="N">` cells: - `1` = always visible - `2` = hide when viewport ≤ 1280 - `3` = hide ≤ 1080 - `4` = hide ≤ 900 - `5` = hide ≤ 768 - Higher priority numbers drop first. The matching `<td>` cells in `tbody` are tagged via `.col-hidden` (colspan-aware mapping). - A `.col-hidden-pill` `<button>` is appended to the last visible `<th>`. Clicking it sets a per-table reveal flag and clears all hidden classes. Re-runs on `window.resize` (debounced) and a `ResizeObserver` on the wrapping element. - Each of `packets.js` / `nodes.js` / `observers.js` wraps its primary table in `.table-fluid-wrap` and calls `TableResponsive.register` after initial render. - `style.css` removes legacy `min-width: 720px / 480px` floors on the primary tables (which forced horizontal scroll) and lets columns flex via `table-layout: auto` with `.col-time` switched to `clamp(72px, 8vw, 108px)`. Per-column priorities chosen so identifier columns stay visible (Time/Hash/Type/Name/Status) while numeric/secondary columns collapse first. ## Files changed (matches Hard rules — only these) - `public/packets.js` (`#pktTable` + `TableResponsive` helper) - `public/nodes.js` (`#nodesTable`) - `public/observers.js` (`#obsTable`) - `public/style.css` (table sections only) - `test-table-fluid-e2e.js` (new E2E) ## E2E `BASE_URL=http://localhost:13581 node test-table-fluid-e2e.js` — covers all three tables at 768/1080/1440 viewports, asserting: - No horizontal table overflow within `.table-fluid-wrap` - Visible `+N hidden` pill at narrow widths with the count `N` matching the number of `th.col-hidden` cells - Clicking the pill clears all `.col-hidden` classifiers (reveals every column) ## Manual verification in openclaw browser (local fixture server) | Page | Viewport | Hidden | Pill | |-----------|---------:|-------:|--------------| | observers | 768 | 8 | `+8 hidden` | | packets | 768 | 7 | `+7 hidden` | | packets | 1080 | 4 | `+4 hidden` | | nodes | 768 | 3 | `+3 hidden` | | nodes | 1440 | 0 | (no pill) | Pill click verified to reveal all columns. ## TDD - Red commit: `5ad7573` — failing E2E (no `.col-hidden-pill` exists yet) - Green commit: `7780090` — implementation; test passes manually against fixture server. Fixes #1056 --------- Co-authored-by: openclaw-bot <bot@openclaw.dev> Co-authored-by: meshcore-bot <bot@meshcore.local> Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
85b8c8115a |
feat(channels): fluid sidebar + container-query stacking (#1057) (#1095)
## Summary Makes the channels page sidebar + message area fluid as part of the parent #1050 fluid-layout effort. Replaces the hardcoded `.ch-sidebar { width: 280px; min-width: 280px }` with `width: clamp(220px, 22vw, 320px); min-width: 220px`. Adds an `@container` query (via `container-type: inline-size` on `.ch-layout`) that stacks the sidebar above the message area when the channels page itself is narrow (≤700px container width) — independent of the global viewport, so it adapts even when an outer panel is consuming width. Removes the legacy `@media (max-width: 900px)` fixed 220px override; the clamp + container query handle that range. `.ch-main` already used `flex: 1`, so it absorbs all remaining width including ultrawides. The existing mobile (≤640px) overlay rules and the JS resize handle in `channels.js` are untouched and still work (user drag still wins via inline width). Fixes #1057. ## Scope - `public/style.css` — channels section only - (no `public/channels.js` changes needed) ## Tests TDD: red commit (failing tests) → green commit (implementation). - `test-channel-fluid-layout.js` (new): static CSS assertions - `.ch-sidebar` uses `clamp()` for width (not fixed px) - `.ch-sidebar` keeps a sane `min-width` (200–280px) - `.ch-main` keeps `flex: 1` - `.ch-layout` declares `container-type` (container query root) - `@container` rule scopes channels stacking - legacy `@media (max-width: 900px) .ch-sidebar { width: 220px }` is gone - `test-channel-fluid-e2e.js` (new): Playwright E2E at 768 / 1080 / 1440 / 1920 (wide) and 480 (narrow). Asserts: - no horizontal scroll on the body - sidebar AND message area both visible side-by-side at ≥768px - sidebar consumes ≤45% of viewport, main ≥40% - at 480px the layout stacks (or overlays) — no overflow Wired into `test-all.sh` and the unit + e2e steps of `.github/workflows/deploy.yml`. ## Verification - Static unit test: 6/6 pass on the green commit, 4/6 fail on the red commit (only the two trivially-true assertions pass). - Local Go server boot: `corescope-server` serves the updated `style.css` containing `container-type: inline-size`, `clamp(220px, 22vw, 320px)`, and `@container chlayout (max-width: 700px)`. - Local Chromium on the dev sandbox is musl-incompatible (Playwright fallback build crashes with `Error relocating ...: posix_fallocate64: symbol not found`), so the E2E was not run locally. CI will run it on Ubuntu runners. --------- Co-authored-by: clawbot <clawbot@example.com> Co-authored-by: meshcore-bot <bot@meshcore.local> |
||
|
|
d1e6c733dc |
fix(nav): apply Priority+ at all widths (#1055) (#1097)
## Summary Make the top-nav use the **Priority+ pattern at all widths** (not just 768–1279px), so the nav-right cluster never gets pushed off-screen or visually overlapped by the link strip. Fixes #1055. ## What changed **`public/style.css`** — nav section only (clearly fenced): - Removed the upper bound on the Priority+ media query (`max-width: 1279px`). The rule now applies at any viewport `>= 768px`. Above that breakpoint, only `data-priority="high"` links render inline; the rest collapse into the existing `More ▾` overflow menu. - Swapped nav-only hardcoded spacing/type to the fluid `clamp()` tokens shipped in #1054: - `.top-nav` padding → `var(--gutter)` - `.nav-left` gap → `var(--space-lg)` - `.nav-brand` gap → `var(--space-sm)`, font-size → `var(--fs-md)` - `.nav-links` gap → `var(--space-xs)` - `.nav-link` padding → `clamp(8px, 0.6vw + 4px, 14px)`, font-size → `var(--fs-sm)` - `.nav-right` gap → `var(--space-sm)` - Mobile (<768px) hamburger layout, the More-menu markup, and the JS that builds the menu in `public/app.js` are unchanged — they already supported this pattern. `public/index.html` did not need changes — the `data-priority="high"` markup, `nav-more-wrap`, `navMoreBtn`, and `navMoreMenu` are already in place from earlier work. ## Why the bug existed The previous Priority+ rule was scoped `@media (min-width: 768px) and (max-width: 1279px)`. From 1280px–~1599px the full 11-link strip rendered but didn't fit alongside `.nav-stats` + `.nav-right`. The parent `overflow: hidden` masked the symptom, but the rightmost links physically rendered underneath `.nav-right` and were unreachable. ## E2E assertion added New `test-nav-fluid-1055-e2e.js` — Playwright multi-viewport test (768/1024/1280/1440/1920) that asserts: 1. `.nav-right.right` ≤ `document.documentElement.clientWidth` (no horizontal overflow) 2. Last visible `.nav-link.right` ≤ `.nav-right.left` (no overlap underneath the right cluster) 3. `.top-nav.scrollWidth` ≤ `.top-nav.clientWidth` (no scrolled-off content) Wired into the `e2e-test` job in `.github/workflows/deploy.yml`. **TDD evidence:** - Red commit `466221a`: test passes 3/5 (1024/768/1920) — fails at 1280 (253px overlap) and 1440 (93px overlap). - Green commit `1aa939a`: test passes 5/5. ## Acceptance criteria (from #1055) - [x] Priority+ at ALL widths (not just mobile). - [x] No nav link overflow at 1080px (or any tested width). - [x] Overflow menu accessible via keyboard + touch (existing `navMoreBtn` aria-haspopup wiring; verified by existing app.js handlers). - [x] Active route still highlighted when in overflow (existing logic in `app.js` adds `.active` to the cloned link in `navMoreMenu`). - [x] Tested at 768/1024/1280/1440/1920 — visible link count adapts (5 priority links + More menu at all desktop widths; full 11 inline only on hamburger mobile when expanded). --------- Co-authored-by: bot <bot@corescope> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: meshcore-bot <bot@meshcore.local> |
||
|
|
b52a938b27 |
fix(#1059): map controls + modals — fluid + safe max-height (#1096)
## Summary Fixes #1059 — Task 6 of #1050. Makes map controls + modals fluid and safely capped so they work across 768px–2560px viewports. ## Changes `public/style.css` only — modal section + map-controls section (per task scope). ### Map controls (`.map-controls`) - `width: clamp(160px, 18vw, 240px)` — fluid, scales with viewport. - `max-width: calc(100vw - 24px)` — never overflows narrow viewports. - Eliminates horizontal scroll on the map page at 768/1024/1440/1920/2560. ### Modal box (`.modal`) - `max-height: 80vh → 90vh` (spec §3). - `width: min(90vw, 500px)` — fluid, drops to 90vw below 555px. - `position: relative` so sticky descendants anchor to the modal box. - `.modal-overlay` gets `padding: clamp(8px, 2vw, 24px)` for edge breathing room. ### BYOP modal sticky close - `.byop-header { position: sticky; top: 0 }` with `var(--card-bg)` backdrop and bottom border — the title bar + ✕ stay reachable while the body scrolls. - `.byop-x` restyled with border, hit area, hover state. ### Untouched (intentional) - `public/map.js` did not need changes — the `.map-controls` element is the only narrow-viewport offender; the markup stays identical. - Channel modals (`.ch-modal*`, `.ch-share-modal*`) already have their own width/max-width tokens from #1034/#1087 and are out of scope for this task. ## TDD - **Red commit** `b69e992`: `test-map-modal-fluid-e2e.js` asserts (a) no horizontal scroll on `/#/map` at 1024/1440/1920/2560, (b) `.map-controls` right edge inside viewport at 768px wide, (c) BYOP modal at 1024×768 has `height ≤ 90vh`, `overflow-y: auto|scroll`, and close button is `position: sticky` and reachable. All assertions fail against the previous CSS (fixed-width 220px controls overflow at narrow widths; modal max-height was 80vh, not 90vh; close button was `position: static`). - **Green commit** `3e6df9d`: CSS changes above; all assertions pass. ## E2E - Wired into `.github/workflows/deploy.yml` after the channel-1087 E2E: ``` BASE_URL=http://localhost:13581 node test-map-modal-fluid-e2e.js ``` ## Acceptance criteria - [x] Map controls do not overlap markers at narrow viewports (fluid clamp width + max-width). - [x] Map fills extra space on ultrawide (panel caps at 240px, leaflet flex:1 takes the rest — already true; controls no longer steal grow room). - [x] Modals: `max-height: 90vh`, internal scroll, sticky close button, max-width via `min()`. - [x] No modal can exceed viewport height at any tested width. - [x] Verified via E2E at 768/1024/1440/1920/2560. ## Out of scope (left for sibling tasks under #1050) - Tab bars / nav (Task 1050-1, blocker). - Filter bars and table chrome (other 1050-N tasks). --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
6d17cac40e |
fix(#1058): fluid + container-queried analytics chart grid (#1098)
## Summary Makes the analytics chart grid fluid and auto-stacking based on its **own** width rather than the viewport's. Implements task 5 of #1050. ## What changed - `public/style.css` — `.analytics-charts` section only: - Replaced `grid-template-columns: 1fr 1fr` with `repeat(auto-fit, minmax(min(100%, 380px), 1fr))` so columns wrap when intrinsic space is too narrow. - Added `container-type: inline-size` so the grid is a query container and descendants/future tweaks can size against its own width rather than the viewport. The `auto-fit minmax` already handles the stack-on-narrow case, so the previously-included `@container (max-width: 800px)` rule was redundant and has been dropped to keep one source of truth. - `min-width: 0` on cards and `max-width: 100%; height: auto` on `<svg>`/`<canvas>` (descendant selector, robust to wrapper elements between the card and the chart media) to prevent intrinsic-content overflow. - Switched hardcoded `12px` / `16px` spacing to the #1054 tokens `--space-sm` / `--space-md`. - Removed the redundant `@media (max-width: 768px) { .analytics-charts { grid-template-columns: 1fr; } }` rule (the fluid grid supersedes it). No `analytics.js` / `node-analytics.js` markup changes were required — the existing classes are reused. ## TDD - **Red commit ( |
||
|
|
ac0cf5ac7d |
fix(channels): #1087 QR library + share modal + PSK persistence (#1090)
Red commit: 5def4d073c61058fc9f327a3c60ece27e21cbc69 (CI run pending — see Checks tab) Fixes #1087 ## What's broken (4 bugs) 1. **"QR library not loaded"** — `channel-qr.js` checked `root.QRCode` (capital), but the vendored library exports lowercase `qrcode` (Kazuhiko Arase API). Generate & Show QR always fell into the "library not loaded" branch. 2. **QR encodes `name=psk:hex`** — the Share button (and parts of the Generate path) passed the internal `psk:<hex8>` lookup key to `ChannelQR.generate`, ignoring the user's display label stored in `LABELS_KEY`. 3. **PSK channel doesn't persist on refresh** — the persistence path was scattered, and the read-back wasn't verified. Added channels disappeared on refresh and "reappeared" only when a later add ran the persist hook. 4. **Share button reuses the Add Channel modal** — wrong intent reuse (Add = INPUT, Share = OUTPUT). Replaced with a dedicated `#chShareModal` (separate DOM id, separate title, share-only affordances, privacy warning). ## TDD Red commit (this) lands ONLY the failing tests: - `test-channel-issue-1087.js` — source-string contract assertions for all 4 bugs - `test-channel-issue-1087-e2e.js` — Playwright E2E covering generate → QR render, QR display name, persistence across refresh, Share opens dedicated modal Green commit (follow-up) lands the production fixes. ## E2E assertion added E2E assertion added: test-channel-issue-1087-e2e.js:55 ## CI wiring - `test-channel-issue-1087.js` added to `.github/workflows/deploy.yml` (go-test JS unit step) + `test-all.sh` - `test-channel-issue-1087-e2e.js` added to `.github/workflows/deploy.yml` (e2e-test step) --------- Co-authored-by: bot <bot@corescope> Co-authored-by: meshcore-bot <bot@meshcore.local> Co-authored-by: clawbot <clawbot@users.noreply.github.com> |
||
|
|
f7d8a7cb8f |
feat(packets): filter UX — in-UI docs + autocomplete + right-click + saved filters (#966) (#1083)
## Summary Implements the full filter-input UX upgrade from #966 — Wireshark-style help, autocomplete, right-click-to-filter, and saved filters. Closes #966. ## Surfaces ### A. Help popover (ⓘ button next to filter input) Auto-generated from `PacketFilter.FIELDS` / `OPERATORS` so it stays in sync with the parser. Includes: - Syntax overview (boolean ops, parens, case-insensitivity, URL-shareable filters) - Full field reference (27 entries: top-level + `payload.*`) - Full operator reference with one example per op - 10 ready-to-paste examples - Tips (right-click, autocomplete, save) ### B. Autocomplete dropdown - Type partial field name → field suggestions (top-level + dynamic `payload.*` keys discovered from visible packets) - Type `field` → operator suggestions - Type `type ==` → list of canonical type values (`ADVERT`, `GRP_TXT`, …) - Type `route ==` → list of route values (`FLOOD`, `DIRECT`, `TRANSPORT_FLOOD`, …) - Keyboard nav: ↑/↓, Tab/Enter to accept, Esc to dismiss ### C. Right-click → filter by this value Right-click any of these cells in the packet table: - `hash`, `size`, `type`, `observer` Context menu offers `==`, `!=`, `contains`. Click → clause appended to filter input (with `&&` if expression already present). ### D. Saved filters - ★ Saved ▾ dropdown next to the input - 7 starter defaults (Adverts only, Channel traffic, Direct messages, Strong signal SNR > 5, Multi-hop, Repeater adverts, Recent < 5m) - "+ Save current expression" prompts for a name and persists to `localStorage` under `corescope_saved_filters_v1` - User filters can be deleted (✕); defaults cannot - User filters with the same name as a default override it ## Implementation **`public/packet-filter.js`** — exposes `FIELDS`, `OPERATORS`, `TYPE_VALUES`, `ROUTE_VALUES`, and a new `suggest(input, cursor, opts)` function that returns ranked autocomplete suggestions with replace-range. Pure function — no DOM, fully unit-tested. **NEW `public/filter-ux.js`** — `window.FilterUX` IIFE owning the help popover, autocomplete dropdown, context menu, and saved-filters store. `init()` is idempotent, called once after the filter input renders. **`public/packets.js`** — calls `FilterUX.init()` after the filter input IIFE; row builders gain `data-filter-field` / `data-filter-value` attrs on hash/size/type/observer cells. `filter-group` wrapper now `position: relative` so dropdowns anchor correctly. **`public/style.css`** — scoped `.fux-*` styles using existing CSS variables (no new theme tokens). ## Tests - `test-packet-filter-ux.js` (19 unit tests, wired into `test-all.sh`): - Metadata exposure (FIELDS / OPERATORS / TYPE_VALUES / ROUTE_VALUES) - `suggest()` for empty input, prefix match, after `==`, dynamic `payload.*` keys - `SavedFilters.list/save/delete` — defaults, persistence, override, dedup - `buildCellFilterClause()` and `appendClauseToExpr()` quoting + appending - `test-filter-ux-e2e.js` (Playwright, wired into `deploy.yml`): - Navigate /packets → metadata exposed - Help popover opens with field reference, operators, examples - Autocomplete shows on focus, filters by prefix, accepts on Enter - Saved-filter dropdown lists defaults, click populates input - Right-click on TYPE cell → context menu → click appends clause - Save current expression persists to localStorage TDD red commit (`bddf1c1`) — assertion failures only, no import errors. Green commit (`0d3f381`) — all 19 unit tests pass. ## Browser validation Spawned local server on :39966 against the e2e fixture DB and exercised every UX surface via the openclaw browser tool. Confirmed: - `window.PacketFilter.FIELDS.length === 27`, `suggest()` available - `FilterUX.SavedFilters.list().length === 7` (defaults seeded) - Help popover renders with `payload.name`, `contains`, `ADVERT` text content - Right-click on a `data-filter-field="type"` / `data-filter-value="Response"` cell → context menu showed three options → clicking == populated the input with `type == "Response"` (and the existing alias resolver matched it to `payload_type === 1`) - Autocomplete on `pay` returned `payload_bytes`, `payload_hex`, `payload.name`, `payload.lat`, `payload.lon`, `payload.text` ## Out of scope (deferred per the issue) - Server-synced saved filters (cross-device) - Visual filter builder - Custom field expressions ## Acceptance criteria - [x] Help icon (ⓘ) next to filter input opens documentation popover - [x] Field reference table + operator reference + 6+ examples in popover - [x] Autocomplete dropdown on field names (top-level + `payload.*`) - [x] Autocomplete dropdown on values for `type` / `route` operators - [x] Right-click on packet cell → "Filter ==" / "Filter !=" / "Filter contains" - [x] Right-click context menu hides when clicking elsewhere / Esc - [x] Saved-filters dropdown with at least 5 default examples (7 shipped) - [x] User-saved filters persist in localStorage - [x] Real-time match count next to filter input (already shipped pre-PR; preserved) - [ ] Improved error messages with token + position — partial: existing parse errors already cite position; not a regression - [x] No regression in existing filter behavior (`test-packet-filter.js`: 69/69 pass) --------- Co-authored-by: meshcore-bot <bot@meshcore.local> |
||
|
|
aa3d26f314 |
fix(nav): stop nav bar from jumping when Live is selected (#1046) (#1078)
## Summary The `🔴 Live` nav link could wrap onto two lines at certain viewport widths once it became the `.active` link, which grew `.nav-link`'s height and made the whole `.top-nav` "hop" the instant Live was selected (issue #1046). Adding `white-space: nowrap` to the base `.nav-link` rule keeps every nav label on a single line at every breakpoint (default desktop + the 768–1279px and <768px responsive overrides), eliminating the jump. ## Changes - `public/style.css` — `white-space: nowrap` on `.nav-link`. - `test-e2e-playwright.js` — new assertion at viewport 1115px (the width in the issue screenshots) that: - computed `white-space` prevents wrapping - the Live link renders on a single line in both states - `.top-nav` height does not change when `.active` is toggled ## TDD - Red commit `ba906a5` — test added, fails because base `.nav-link` has no `white-space` rule (default `normal`). - Green commit `51906cb` — single-line CSS fix makes the test pass. Fixes #1046 --------- Co-authored-by: corescope-bot <bot@corescope.local> |
||
|
|
417b460fa0 |
feat(css): fluid scaffolding — clamp() spacing/type/container tokens (#1054) (#1066)
## Summary Lands the **fluid CSS foundation** for the responsive scaffolding effort (parent #1050). Pure additive change to the top of `public/style.css` — no component CSS touched. ## What changed ### New tokens in `:root` - **Spacing scale** — `--space-xs … --space-2xl` via `clamp()`. 1440px targets match the prior hardcoded `4 / 8 / 16 / 24 / 32 / 48` px values to within ~1px. - **Type scale** — `--fs-sm … --fs-2xl` via `clamp(min, vw-based, max)`. Floors keep text readable at 768px; caps prevent runaway growth at 2560px+. - **Radii** — `--radius-sm/md/lg` via `clamp()`. - **Container layout** — `--gutter` (`clamp()`) and `--content-max` (`min(100% - 2*gutter, 1600px)`) for fluid horizontal layout without media queries. ### Base consumption - `html, body` now sets `font-size: var(--fs-md)`. ### Parallel-work safety - Added `FLUID SCAFFOLDING` section header at the top. - Added `COMPONENT STYLES` section header marking where the rest of the file (nav, tables, charts, map, packets, analytics …) begins. Sibling tasks 1050-3..6 / 1052-* edit inside that region and won't conflict with this PR. ## TDD - **Red:** `2d6f90a` — `test-fluid-scaffolding.js` asserts the new tokens exist with `clamp()`/`min()`, that `html, body` consumes `--fs-md`, and that the section marker is present. Fails on assertions (15 failed, 0 passed). - **Green:** `7b4d59b` — implementation in `public/style.css`. All 15 assertions pass. ## Acceptance criteria - [x] Fluid spacing scale `--space-xs..--space-2xl` via `clamp()` - [x] Fluid type scale `--fs-sm..--fs-2xl` via `clamp()` - [x] Replace base body font-size with the new token - [x] Container layout vars `--content-max`, `--gutter` via `min()`/`clamp()` - [x] No component CSS edits (only `:root`, `html`, `body`) - [x] No visual regression at 1440px (token targets numerically match prior px values) ## Notes for reviewers - Pre-existing `test-frontend-helpers.js` failure on master is unrelated (`nodesContainer.setAttribute is not a function`) and not introduced here. - `--content-max` uses `min(100% - 2*gutter, 1600px)` — the `100% - …` arm wins on small viewports and guarantees a gutter always remains. Fixes #1054 --------- Co-authored-by: clawbot <bot@corescope.local> Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: meshcore-bot <bot@meshcore.local> |
||
|
|
eaf14a61f5 |
fix(css): 48px touch targets, :active states, hover→tap (#1060) (#1067)
## Summary Fixes #1060 — free-win CSS pass for touch usability. - All major interactive controls (`.btn`, `.btn-icon`, `.nav-btn`, `.nav-link`, `.ch-icon-btn`, `.ch-remove-btn`, `.ch-share-btn`, `.ch-gear-btn`, `.panel-close-btn`, `.mc-jump-btn`, `button.ch-item`) now declare `min-height: 48px` / `min-width: 48px`. Hit-area grows; visual padding/icon size unchanged on desktop because the rules use `inline-flex` centering. - Added visible `:active` feedback (background shift + `transform: scale(0.92–0.97)` + opacity) on every button class — touch devices have no hover, so `:active` is the only press signal. - Hover-only `.sort-help` tooltip rule is now wrapped in `@media (hover: hover)`; added a CSS-only `:focus` / `:focus-within` tap-to-reveal path with a visible focus ring so the same content is reachable on touch (and via keyboard). - All changes scoped to the `=== Touch Targets ===` section. No other CSS section modified, no JS touched, no markup edits. ## Acceptance criteria - [x] All interactive controls reach 48×48 CSS-px touch target (verified by `test-touch-targets.js`). - [x] Every button has a visible `:active` state (no hover-only feedback). - [x] Hover tooltip rule is gated behind `@media (hover: hover)`, with `:focus-within` tap-to-reveal fallback. - [x] Desktop visuals preserved (padding-based, not visual-size-based). ## TDD - Red commit `327473b` — `test-touch-targets.js` asserts every required selector/property; it compiles and fails on assertion against pre-change CSS. - Green commit `e319a8f` — Touch Targets section rewrite; test passes. ``` $ node test-touch-targets.js test-touch-targets.js: OK ``` Fixes #1060 --------- Co-authored-by: bot <bot@corescope> |
||
|
|
f9cd43f06f |
fix(analytics): integrate channels list with PSK decrypt UX + add link from Channels page (#1042)
## What Integrates the Analytics → Channels section with the PSK decrypt UX (PRs #1021–#1040). Replaces nonsense `chNNN` placeholders with useful display names and groups the table the same way the Channels sidebar does. ## Before - Encrypted channels showed raw `ch185`, `ch64`, `ch?` placeholders. - Locally-decrypted PSK channels (with stored keys + labels) were not surfaced — every encrypted row looked identical and useless. - Single flat list, sorted by last activity by default. ## After - **My Channels** 🔑 — any analytics row whose hash byte matches a stored PSK key (via `ChannelDecrypt.getStoredKeys()` + `computeChannelHash`). Display name uses the user's label if set, otherwise the key name. - **Network** 📻 — known cleartext channels (server-provided names) and rainbow-table-decoded encrypted channels. - **Encrypted** 🔒 — unknown encrypted, rendered as `🔒 Encrypted (0xNN)` instead of `chNNN`. - Within each group: messages descending (most active first). - New `📊 Channel Analytics →` link in the Channels page sidebar header → `#/analytics`. ## How - Pure `decorateAnalyticsChannels(channels, hashByteToKeyName, labels)` — testable in isolation, sets `displayName` + `group` per row. - `buildHashKeyMap()` — async helper that resolves stored PSK keys to their channel hash bytes via `computeChannelHash`. Used at render time; first paint uses an empty map (best-effort) and re-renders once keys resolve. Graceful fallback when `ChannelDecrypt` is missing or there are no stored keys. - `channelTbodyHtml` gains an `opts.grouped` flag — opt-in so the existing flat sort still works for any other caller. - The analytics API endpoint is **unchanged** — this is purely frontend rendering. ## Tests `test-analytics-channels-integration.js` — 19 assertions covering decoration, grouping, sort order, and the channels-page link. Added to `test-all.sh`. Red commit: `5081b12` (12 assertion failures + stub). Green commit: `6be16d9` (all 19 pass). --------- Co-authored-by: bot <bot@corescope.local> Co-authored-by: meshcore-bot <bot@meshcore.local> |