mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-13 18:57:24 +00:00
> **Requires #2167 to be merged first.** This page calls the transport view API that #2167 adds (`/api/scope-audit?mode=transport`), so it cannot be split from it; until #2167 merges this PR also shows its commits. ## Summary Page half of #2142. **Stacked on #2167** (the API); review that first. #2171 adds the observer region filter on top. Partial fix for #2142 until all three are in. Adds a **Declared / Transport** toggle to `#/scope-audit`. The transport view calls `?mode=transport` and lists every repeater seen forwarding in the window: - **Seen carrying:** one chip per region scope with its packet count (first/last seen in the tooltip); - **Declared:** the repeater's declared regions coloured by whether they were seen carried (the declared view's own chips), or *not asked* when no declared-regions answer exists. Never asked is worded differently from declaring nothing; - **Other traffic:** unscoped, unmatched and ambiguous-hop counts. A **Region** box (suggestions from the scopes seen) adds a *Carries X* column, lists the repeaters not yet carrying it first, and summarises carriers against active non-carriers: the rollout report the issue asks for. Window, view and region are deep-linked (`#/scope-audit?window=7d&mode=transport&scope=be`) and survive a reload. The page says plainly that *transported* means seen carrying, not configured, and that over a short window a quiet region looks the same as a missing one. The intro text changes with the view. ## Screenshot The transport view with scope `be` typed in: every repeater seen forwarding in the last 24 h, whether it carries `be` (non-carriers first), the scopes it was seen carrying with counts, and its declared regions or *not asked*. Taken against `test-fixtures/e2e-fixture.db` with fictional `be` / `be-van` / `nl` scopes.  ## Browser validation (staging, real data) Headless Chromium against a staging copy of a production database with its live feed: - `#/scope-audit?window=24h`: the declared view, 16 rows, as before. - Clicking **Transport**: 385 rows; the hash gains `mode=transport`. - Typing a scope as `#XX-YY` in capitals: normalised to `xx-yy`; the hash gains it (now `scope=xx-yy`); summary "385 repeaters seen forwarding in the last 24h · 16 with a declared-regions answer · 51 carried xx-yy · 334 active but not seen carrying it"; the first rows are the non-carriers. - Reloading that hash restores view and region. Switching back to Declared hides the Region box and restores the declared intro. No page errors. - Checked at 1280 px and 390 px wide. Two layout bugs found this way were fixed before opening: the region box inherited `width: 100%` from `.nodes-search`, and `display: flex` overrode the `hidden` attribute on the region bar. ## Tests `tests/unit/test-frontend-helpers.js` gains 10 cases on rendered markup and state, using the page's exported internals like the existing scope-audit tests: hash and API paths (and that a region never leaks into the declared view's link), region normalisation (`#`, capitals, empty), *not asked* vs declared chips (with the `*` wildcard), carried-scope chips with counts and the filtered region marked, the carries column only with a region, HTML escaping of names, the summary counts, region suggestions, and the per-view intro. 768 passing in that file. Net-new UI surface, so per TDD.md the tests land in this PR rather than as a preceding red commit. Frontend suites on this branch: 193 of 194; the one failure, `test-issue-1956-release-routing.js`, needs CI's `GITHUB_REF_NAME` and fails identically on `master` locally (#2149 and #2160 address that). ## Review notes - Colours only through existing CSS variables (no hex literals); the filtered region is marked with the link colour, not a new status colour. - The window note's wording is in the same spirit as the declared view's `windowHonestyNote`. - No API calls per row: one request per view/window/region, cached client-side for 30 s like the declared view. ## SonarLint SonarQube Community rules (the SonarLint engine) were run on the files this PR touches, and only issues on lines it adds are listed. - **Fixed** in `df53e770`: - one `modeQuery` helper for the page hash and the API path (S3358; the two copies were identical); - the carries-scope cell built before the row (S3358); - `localeCompare` for the region suggestions (S2871), `for…of` and `.dataset` (S4138, S7761), optional chaining (S6582); - object spread in the helper tests (S6661). - Optional chaining and spread are already used throughout `public/`. No behaviour change. ## Checked against the repo rules - Rebased onto `master` (`673442cc`). Each red commit was re-run and still fails on its assertions; each green commit passes (TDD.md). - `make test`: all 16 modules pass. Frontend suites: all pass except `test-issue-1956-release-routing.js`, which needs CI's `GITHUB_REF_NAME` and fails the same way on `master` locally (#2160 addresses it). - No private data: test fixtures and examples use the codes the existing tests use (SJC/SFO/LAX, `#be`-style scopes). - No new `map[string]interface{}`, no hard-coded colours. `git add` was explicit, and there is one logical change per commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
7.5 KiB
CSS
121 lines
7.5 KiB
CSS
/* Network-wide scope audit page (#/scope-audit). Reuses .ns-decl / .ns-table /
|
|
.ns-empty / .ns-truncated from node-scopes.css (same badge vocabulary, so
|
|
the per-node and network-wide views agree visually) plus .analytics-time-range
|
|
for the window buttons. No hex/rgb literals — --text-muted / --border /
|
|
--status-* / --link-color are defined globally. */
|
|
|
|
.sa-page { max-width: 1200px; margin: 0 auto; padding: 12px 16px; }
|
|
|
|
.sa-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
|
|
.sa-head h2 { margin: 0; font-size: 18px; }
|
|
|
|
.sa-intro { color: var(--text-muted); font-size: 12px; margin: 4px 0 10px; }
|
|
.sa-intro a { color: var(--link-color); }
|
|
|
|
/* .sa-search reuses .nodes-search (style.css) for the input's colours/border
|
|
so this page doesn't invent a second search-box style — only the sizing
|
|
below is page-specific. */
|
|
.sa-search-bar { margin: 0 0 10px; }
|
|
.sa-search { width: 100%; max-width: 360px; }
|
|
|
|
.sa-window-note {
|
|
font-size: 12px; color: var(--text-muted); margin: 4px 0 10px;
|
|
border-left: 3px solid var(--border); padding: 4px 10px;
|
|
}
|
|
|
|
.sa-table-wrap { overflow-x: auto; }
|
|
.sa-table { min-width: 720px; }
|
|
.sa-name a { color: var(--link-color); text-decoration: none; }
|
|
.sa-name a:hover { text-decoration: underline; }
|
|
.sa-name-unknown { color: var(--text-muted); font-style: italic; }
|
|
.sa-role { font-size: 10px; text-transform: uppercase; letter-spacing: .3px; }
|
|
|
|
.sa-chip {
|
|
display: inline-block; padding: 1px 6px; margin: 1px 2px 1px 0; border-radius: 4px;
|
|
font-size: 11px; font-family: var(--mono); white-space: nowrap;
|
|
}
|
|
.sa-chip-declared { background: var(--section-bg, var(--card-bg)); color: var(--text); border: 1px solid var(--border); }
|
|
.sa-chip-undeclared { background: color-mix(in srgb, var(--status-yellow) 18%, transparent); color: var(--status-amber-text); }
|
|
/* Same outline, same reason as .ns-decl-unknown: --section-bg is dark-theme
|
|
only, so in light this falls back to --card-bg, which is white on a
|
|
near-white page ground and leaves the wildcard chip unpainted. */
|
|
.sa-chip-wildcard { background: var(--section-bg, var(--card-bg)); box-shadow: inset 0 0 0 1px var(--border); color: var(--text-muted); font-weight: 700; }
|
|
.sa-chip-ambiguous { background: var(--section-bg, var(--card-bg)); color: var(--text-muted); border: 1px dashed var(--border); font-family: inherit; font-style: italic; }
|
|
|
|
.sa-count { font-size: 11px; margin-top: 6px; }
|
|
|
|
/* sa-summary is the per-configState count line at the top of the page —
|
|
"13 no scopes configured" etc — reusing .ns-decl (node-scopes.css) for the
|
|
count pill so it shares the exact colour vocabulary the row-level Config
|
|
badge (also .ns-decl) uses, rather than a separate summary palette. */
|
|
.sa-summary { display: flex; flex-wrap: wrap; gap: 10px 16px; font-size: 12px; color: var(--text-muted); margin: 2px 0 10px; }
|
|
.sa-summary-item { display: inline-flex; align-items: center; gap: 5px; }
|
|
|
|
/* A 640px rule hiding the table's sixth column used to live here. The table
|
|
has five (scope-audit.js), so it hid nothing, and the wrapper already scrolls
|
|
horizontally. Removed rather than repointed: hiding a column of this table is
|
|
what made the Config badge disappear on phones when node-scopes.css landed. */
|
|
|
|
/* Observed forwarding, the green half of the merged Scopes column. Pairs with
|
|
.sa-chip-unobserved below, which stays neutral. Only the observed side is
|
|
coloured: red on every unobserved region would read as an alarm on rows that
|
|
are often just a quiet region over a short window. */
|
|
/* #1996: the chip's own tint darkens whatever is behind it, so
|
|
--status-green-text (green-700, #15803d) landed at 4.04:1 on --surface-0 and
|
|
4.38:1 on white — both under the 4.5:1 the #1719 gate requires. Measured, and
|
|
it reproduces the ratio in the report exactly.
|
|
Fixed on the TEXT rather than by thinning the tint: 12% tint would reach
|
|
4.52:1, two hundredths above the line, which is the kind of margin that fails
|
|
again on the next surface change. green-800 gives 6.23:1 on white and 5.74:1
|
|
on --surface-0, and keeps the tint that makes the chip read as a chip.
|
|
Dark is untouched: --status-green-text is the bright #22c55e there and
|
|
already passes at 6.48:1 / 5.73:1. */
|
|
.sa-chip-observed { background: color-mix(in srgb, var(--status-green) 16%, transparent); color: var(--sa-chip-observed-fg); }
|
|
|
|
/* Declared but not observed in this window. Deliberately NOT red: absence over
|
|
a short window is weak evidence, which the page header says in words, so it
|
|
should not shout in colour. */
|
|
.sa-chip-unobserved { background: var(--section-bg, var(--card-bg)); color: var(--text-muted); border: 1px solid var(--border); }
|
|
|
|
/* Provenance note under the intro. Same muted treatment as .sa-intro; it is
|
|
context, not a finding, and must not compete with the table. */
|
|
.sa-sources { color: var(--text-muted); font-size: 12px; margin: 0 0 10px; line-height: 1.5; }
|
|
.sa-sources a { color: var(--link-color); }
|
|
.sa-sources code { font-family: var(--mono); font-size: 11px; }
|
|
|
|
/* Provenance note under the intro, and the empty state that has to carry the
|
|
same explanation on its own: on a stock install the table IS empty, so that
|
|
is where an operator actually reads it. Muted like .sa-intro; this is
|
|
context, not a finding, and must not compete with the table. */
|
|
.sa-sources { color: var(--text-muted); font-size: 12px; margin: 0 0 10px; line-height: 1.5; }
|
|
.sa-sources a, .sa-empty a { color: var(--link-color); }
|
|
.sa-sources code, .sa-empty code { font-family: var(--mono); font-size: 11px; }
|
|
.sa-empty { line-height: 1.6; max-width: 70ch; }
|
|
.sa-empty ul { margin: 8px 0; padding-left: 20px; }
|
|
.sa-empty li { margin: 4px 0; }
|
|
|
|
/* Same muted, dashed treatment as .sa-chip-ambiguous on purpose: both are
|
|
caveats on the row's finding, not findings themselves, and neither may
|
|
compete visually with the red/green scope chips beside them. */
|
|
.sa-chip-unmatched { background: var(--section-bg, var(--card-bg)); color: var(--text-muted); border: 1px dashed var(--border); font-family: inherit; font-style: italic; }
|
|
|
|
/* Verified-by-declaration: the same green as any observed chip, because the
|
|
region IS observed. The dotted underline says how that was established
|
|
without introducing a third colour into a column that already carries two. */
|
|
.sa-chip-verified { text-decoration: underline dotted; text-underline-offset: 2px; }
|
|
|
|
/* #2142 transport view: view toggle, region filter, carried-scope chips. */
|
|
.sa-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
.sa-region-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 10px; font-size: 12px; }
|
|
.sa-region-bar[hidden] { display: none; } /* display:flex above would otherwise override the hidden attribute */
|
|
.sa-region-bar label { font-weight: 600; }
|
|
/* .nodes-search sets width:100%; the region box only needs room for one code. */
|
|
.sa-region-bar .sa-region { width: 160px; max-width: 100%; flex: none; }
|
|
.sa-chip-transported { background: var(--section-bg, var(--card-bg)); color: var(--text); border: 1px solid var(--border); }
|
|
.sa-chip-transported .sa-chip-count { color: var(--text-muted); }
|
|
/* The region being filtered on: same chip, heavier outline in the link colour,
|
|
so it stands out without adding a third status colour. */
|
|
.sa-chip-match { border-color: var(--link-color); box-shadow: inset 0 0 0 1px var(--link-color); }
|
|
.sa-not-asked { color: var(--text-muted); font-style: italic; font-size: 12px; }
|
|
.sa-counts { font-size: 11px; white-space: nowrap; }
|