Repository navigation
fix(a11y): unique analytics table ids and readable VCR mode badge text - #60
Merged
Merged
Conversation
Scopes: renderTab numbers .analytics-table elements by position, and the pass runs again after the tab's async sections are inserted ahead of the static overview table (and on every theme refresh). The first inserted table then reused the static table's positional id, giving axe duplicate-id analytics-tbl-scopes-0. Skip ids that are already taken; a single pass still yields the same numbering and column-width keys. Live: .vcr-mode-live rendered its text in --status-green, the swatch hue, at 2.27:1 on the light-theme VCR bar. Use the existing text token --status-green-text (#15803d light, unchanged #22c55e dark); the pulsing dot keeps the swatch hue. Add focused regression tests for both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The PAUSED and REPLAY badges had the same problem as LIVE: text in the swatch hues --status-yellow and --accent, 1.81:1 and 2.52:1 on the light-theme VCR bar. Use the semantic text tokens --warning and --link-color (light #b45309 / #2563eb, dark #fcd34d / #60a5fa); the badges' tinted backgrounds and the LIVE dot keep their indicator colors. Extend the contrast test to all three modes, the light theme and both dark token blocks, modelling the bar's color-mix background and each mode's tint (renamed to test-live-vcr-mode-contrast.js). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assert the ids the browser shows on the Scopes tab after the fix (scopes-1, -0, -2, -3), in addition to uniqueness, so the test fails on the duplicate-id assertion with the old positional algorithm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Run test-analytics-table-ids-unique.js and test-live-vcr-mode-contrast.js in the JS unit-test step and in test-all.sh, once each. No other workflow change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With the Kpa-clawbot#1706 route coverage in place (#59), the full axe-core gate (
test-a11y-axe-1668.js) reports two pre-existing UI problems on mastere17377d8(reproduced locally, identical with and without #59):duplicate-id analytics-tbl-scopes-0(4 cells: desktop/mobile × light/dark).renderTab()inpublic/analytics.jsnumbers.analytics-tableelements by position inside arequestAnimationFrame. The pass runs again after the firsttheme-refresh(~300 ms after load, via_themeRefreshHandler). By then the Scopes tab's async sections have inserted tables ahead of the static overview table, which already holds-0, so the first inserted table was also given-0. Traced with a MutationObserver in the browser: second pass producedscopes-0, scopes-0, scopes-2, scopes-3.color-contraston the VCR mode badge#vcrMode(desktop light)..vcr-mode-liveused the swatch hue--status-greenas text: 2.27:1 on the light VCR bar. The PAUSED (--status-yellow, 1.81:1) and REPLAY (--accent, 2.52:1) states have the same problem; the axe gate does not see them only because the page starts in LIVE.Changes
public/analytics.js— the id loop moves intoassignAnalyticsTableIds(el, tab), which skips ids that are already taken. A single pass yields exactly the same ids andmeshcore-analytics-<tab>-<i>-col-widthskeys as before; nothing else references these ids. Exposed aswindow._analyticsAssignTableIdsfor tests, next to the existing_analytics*hooks.public/live.css— badge text now uses the existing semantic text tokens; tinted backgrounds, the pulsing LIVE dot and all behaviour are unchanged:--status-green--status-green-text(#15803d/#22c55e)--status-yellow--warning(#b45309/#fcd34d)--accent--link-color(#2563eb/#60a5fa)Dark theme was already AA; it stays AA (LIVE unchanged, PAUSED 7.3→9.7:1, REPLAY 5.3→5.7:1).
New regression tests
test-analytics-table-ids-unique.js— drives the realassignAnalyticsTableIdsagainst a fake DOM with the observed Scopes insertion order; asserts uniqueness, that an assigned id never changes, the exact post-fix sequence (scopes-1, -0, -2, -3), positional numbering for a single pass, and that column resizing is still wired to every table.test-live-vcr-mode-contrast.js— static WCAG check for all three modes in light theme and both dark token blocks ([data-theme="dark"]andprefers-color-scheme: dark), modelling.vcr-bar'scolor-mix(… 95%, transparent)background over--surface-0/--surface-1plus each mode's tint. The modelled backgrounds match what Chrome/axe measured (#feffff,#fff8e9,#eef6fe).Test registration — both tests added once to
test-all.shand once to the existing "Run JS unit tests (packet-filter)" step in.github/workflows/deploy.yml. No other workflow change: triggers, permissions, concurrency, env, jobs,needs,if/fork-guards and all other steps are semantically identical to master (YAML compared job by job);cmd/serverfork-guard workflow tests pass.Evidence (local, macOS arm64 — not GitHub CI)
scopes-0, scopes-0, scopes-2, scopes-3. On HEAD: 4/4 pass. The mutation is not in this PR.e17377d8+ fix(map): drop the M4-banned caret from a map.js comment #5652ae9d1c+ test(a11y): gate the foreign-traffic, wardriving and areas analytics tabs #59e3f57426+ this branch):set -e, 69 commands from the integrateddeploy.yml): all pass.test-fixtures/e2e-fixture.db(prepared like CI): 116/116 cells clean (master + fix(map): drop the M4-banned caret from a map.js comment #56 + test(a11y): gate the foreign-traffic, wardriving and areas analytics tabs #59 without this branch: 111/116).#vcrModewith the app's own mode classes, desktop light/dark: LIVE 5.0 / 7.56, PAUSED 4.74 / 9.74, REPLAY 4.73 / 5.72 — all AA. The badge is hidden on mobile (display: none≤640px).node --check,sh -n test-all.sh, YAML parse,git diff --check, XSS--diffgate: clean.Dependencies and limitations
playwright/@axe-core/playwright/axe-corefrompackage-lock.json. CI uses Playwright's bundled Chromium with network access. Several Live E2E tests fail locally on baseline and integration alike because Leaflet cannot load (L is not defined); for the same reason clicking the real Pause/Rewind controls did not change mode locally, so the mode check used the app's own classes rather than the click path.🤖 Generated with Claude Code