Skip to content

feat(nav): show the running CoreScope version in the navigation drawer - #142

Merged
dborup merged 2 commits into
masterfrom
codex/issue-111-drawer-version
Sep 29, 2026
Merged

dborup merged 2 commits into
masterfrom
codex/issue-111-drawer-version

Conversation

@dborup

@dborup dborup commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Relates to #111

Plan and design

The user asked for autonomous work, so the plan is written here instead of waiting for sign-off (AGENTS.md rule 5).

Commits:

  1. 1ea7dbf3: tests that reproduce the missing footer (red on master).
  2. 19b7e882: the feature.

What the drawer footer does

  • The nav drawer (public/nav-drawer.js) gets a footer with a link CoreScope <version> pointing to https://github.com/dborup/CoreScope/releases (target="_blank", rel="noopener noreferrer").
  • The link's tooltip shows commit <sha> · built <time> when those values are known.
  • The footer is flex-shrink: 0 at the bottom of the drawer's flex column. The route list above it keeps its own scroll, so no route is covered.
  • Styling uses only existing tokens (--nav-text-muted, --nav-text, --border, --accent). Long versions are truncated with an ellipsis.

Fetching /api/health

  • When: /api/health is fetched on the first open() that passes the existing width gate (> 768px). It is never fetched at page load, and never at narrow widths where the drawer cannot open.
  • Caching: the request is cached for the page lifetime, failures included, so reopening the drawer adds no requests.
  • Field names checked against the fork: handleHealth → HealthResponse{Version, Commit, BuildTime} (json:"version", "commit", "buildTime").
  • The "unknown" placeholder: the fork's server fills unresolved values with "unknown" (resolveVersion / resolveBuildTime / resolveCommit in cmd/server/main.go), and a local build really reports version: "unknown". Those values are treated as missing.
  • Neutral label: these responses all keep the neutral CoreScope label, never blank, undefined or unknown:
    • rejected fetch
    • non-OK response
    • invalid JSON
    • missing, empty or non-string version
    • "unknown"
  • Escaping: remote values are written only through textContent and title.

Mobile "More" sheet

Deliberately not changed. The More sheet at ≤ 768px has no footer today. Adding one would need its own lazy fetch, which the issue asks to decide separately. There is no second health fetch anywhere.

How this differs from upstream Kpa-clawbot/CoreScope#2068

Upstream is read as a reference only; nothing was cherry-picked.

  • The releases URL is this fork's, not upstream's.
  • "unknown" is treated as missing. Upstream would render CoreScope unknown.
  • The version is fetched once per page, not re-requested (as a .then) on every open.
  • The CSS uses the fork's --nav-text-muted token. Upstream referenced a --nav-muted token that the fork does not define, plus an rgba fallback.

Acceptance criteria

Criterion Status Evidence
Render CoreScope <version> in the drawer footer Met Unit and E2E tests
Link to this fork's releases page Met Unit and E2E tests check the exact URL
Commit and build time in the tooltip when available Met Unit test (including dropping "unknown"), E2E test with a routed response
Fetch only when the drawer opens, after the viewport gate Met Unit: 0 requests at load and at a narrow width. E2E: 0 requests before open, 0 at 700px
Cached for the page lifetime; reopening adds no requests Met Unit: 1 request across open, close, toggle and narrow/wide changes. E2E: 1 request after four opens
Failed, non-OK or version-less response leaves a neutral CoreScope Met Unit: 8 negative cases, none retried
Remote values via textContent only Met Unit: a markup-shaped version stays text, creates no child nodes, and no innerHTML is written. E2E: the same with <b>
Current /api/health field names confirmed Met See above
No drawer layout regression at narrow desktop/tablet widths Met E2E at 1440x900, 1024x768, 800x900 and a short 1280x480: footer inside the drawer, below the list, last route reachable
Decide on mobile More parity separately Met (decision) Not added; see above

Tests

Test master d264716c this branch
test-issue-111-drawer-version.js (real nav-drawer.js, fake DOM, counting fetch) 1 passed / 13 failed 14/14
test-issue-111-drawer-version-e2e.js (Playwright, local server on test-fixtures) 2 passed / 5 failed 7/7
  • The one unit test passing on master is the narrow-width no-request guard.
  • The two E2E tests passing on master are the narrow and no-error guards.
  • The unit test is registered in the deploy.yml unit step and test-all.sh; the E2E test in the Playwright step next to test-nav-drawer-1064-e2e.js.

Existing suites (local, this branch):

Suite Result
test-nav-drawer-1064-e2e.js 11/11
test-bottom-nav-1061-e2e.js 31/31
test-gesture-hints-1065-e2e.js 15/15
test-nav-more-floor-1139-e2e.js 10/10
test-privacy-page.js 34/34
test-issue-1648-m1-emoji-scan.js ok
test-issue-1668-m4-per-route.js ok

eslint and scripts/check-xss-sinks.sh --diff origin/master are clean.

Browser check

  • Screenshots at 1024x768 and 1280x480, with a routed version v3.1.4: the footer shows CoreScope v3.1.4 at the bottom of the drawer and all routes stay visible.
  • Pre-existing and unchanged: on master the drawer header renders as a white box at these sizes (identical screenshot on master). That is not part of this change.

Perf

There is one request per page, and only once the drawer is opened. There is no work at page load and nothing on a hot path.

Not verified

  • Real touch devices and tablets.
  • The rendered version string of a real release build. Local builds report unknown, so the populated case was checked with a routed response.

Overlap with other open PRs

🤖 Generated with Claude Code

https://claude.ai/code/session_019TcZHooUiiknVWbECVWzk8


Generated by Claude Code

dborup and others added 2 commits September 29, 2026 09:17
Unit test (real nav-drawer.js, fake DOM, counting fetch): 1 of 14 pass
on master (the narrow-width no-request guard). E2E at 1440x900,
1024x768, 800x900 and 1280x480: 2 of 7 pass on master (the narrow and
no-error guards).

Relates to #111

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019TcZHooUiiknVWbECVWzk8
#111)

The drawer gets a footer link "CoreScope <version>" to the dborup/
CoreScope releases page, with commit and build time in its tooltip.
/api/health is fetched on the first open that passes the width gate,
never at page load, and cached for the page lifetime (failures too), so
re-opening adds no requests. A rejected, non-OK, invalid or version-less
response, or the server's "unknown" placeholder, keeps the neutral
"CoreScope" label. Values go through textContent/title only. The footer
does not shrink; the route list above it keeps scrolling. The mobile
More sheet is unchanged (no second health fetch).

Relates to #111

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019TcZHooUiiknVWbECVWzk8
@dborup

dborup commented Sep 29, 2026

Copy link
Copy Markdown
Owner Author

Independent review of 19b7e882

Verdict: APPROVE with nits. This is a recommendation only; merging is the owner's call.

Reviewed head: 19b7e8822a2dddd187ad47a6e19f63e698d3bb89 (unchanged before and after the review). Work done on git archive trees of the head, of commit A 1ea7dbf3 and of origin/master ad011021, with local servers built from the head and from master tree on ports 13760/13761 (e2e fixture, seeded and migrated).

Labels: [F] freshly verified by me · [T] taken from the PR text · [A] assumption · [K] known limitation.

Findings

  1. nit. Second, diverging implementation of the "unknown" placeholder check public/nav-drawer.js:105-110 vs public/perf.js:80-81. perf.js compares exactly with !== 'unknown'. The drawer trims and compares case-insensitively. Both are correct for the server's actual output (resolveVersion/resolveCommit/resolveBuildTime return the literal "unknown", cmd/server/main.go:29-58). But AGENTS.md DRY asks for one shared helper, and the two will drift. There is no user-visible failure today. [F]
  2. nit. A long version is ellipsised and the full string is not available anywhere public/nav-drawer.css:170-178, public/nav-drawer.js:112-121. The tooltip carries only commit and build time. I reproduced this with a routed 67-char version at 1280x480: scrollWidth 465 > clientWidth 287, and title was "commit deadbeef". CI builds produce vX.Y.Z or edge (deploy.yml:676-686), which fit comfortably (about 45 chars fit), so this only matters for hand-built describe strings. Optional fix: include the version in title. [F]

No P1, P2 or P3 findings.

Metadata

  • Head 19b7e882 at start and at end (gh pr view 142 --json headRefOid). The PR is a draft. [F]
  • Commits in origin/master..head: 1ea7dbf3 (test) and 19b7e882 (feat). Author and committer of both are exactly dborup <kontakt@meshview.dk>. [F]
  • Files: .github/workflows/deploy.yml (+2), public/nav-drawer.css (+29), public/nav-drawer.js (+53), test-all.sh (+1), test-issue-111-drawer-version.js (+227, new), test-issue-111-drawer-version-e2e.js (+122, new). [F]
  • The merge base is d264716c, 3 commits behind origin/master ad011021 (fix(analytics): treat the distance index's 202 as a transient building state #133/fix(analytics): treat lazy distance-index 202 responses as transient #120, which only touch the analytics files plus one registration line each in deploy.yml and test-all.sh). git merge-tree --write-tree origin/master 19b7e882 is clean (tree cc81ce27). [F]
  • PR body: says "Relates to ui: show the running CoreScope version in the navigation drawer #111". There is no closing keyword and closingIssuesReferences is []. There are no @mentions. Upstream appears only in code format (Kpa-clawbot/CoreScope#2068), with no github.com/Kpa-clawbot URL. Commit messages have no upstream URLs or closing keywords. [F]
  • CI on 19b7e882 is complete: Go Build & Test, Playwright E2E and Docker are SUCCESS; the rest are SKIPPED. [F]
  • Workflow: only two test-registration lines were added: the unit test at deploy.yml:162 and the E2E at deploy.yml:550, right after test-nav-drawer-1064-e2e.js. No github.repository == 'Kpa-clawbot/CoreScope' line, trigger, permission or job changed. test-all.sh:113 adds the unit test. [F]

Acceptance criteria (issue #111)

Criterion Result
Render CoreScope <version> in the drawer footer Met [F]. Routed v3.1.4 renders CoreScope v3.1.4 (dark-theme screenshot at 1024x768). Unit and E2E are green on the head.
Link to the fork's releases page Met [F]. nav-drawer.js:102 points to https://github.com/dborup/CoreScope/releases with target=_blank and rel="noopener noreferrer". Mutant M11 (upstream URL) is caught by unit and E2E.
Commit and build time in the tooltip when available Met [F]. nav-drawer.js:117-121. "unknown" parts are dropped (M13 is caught by unit).
Fetch only when opened, after the viewport gate Met [F]. requestVersion() sits after if (!isWide()) return; at nav-drawer.js:283-284. My probe saw 0 requests at load and 1 after a real touch-pointer swipe open (the onPointerUp → open() path). M2 (call before the gate) and M5 (fetch at build) are both caught.
Cached for the page lifetime Met [F]. versionRequested is set before the fetch (:126). E2E: 1 request after 4 opens. M1 is caught.
Failed, non-OK or version-less response → neutral CoreScope Met [F]. Unit covers 8 cases. My routed browser probe covered HTTP 500 with a version, " UNKNOWN ", and an HTML body: all showed CoreScope with an empty title and no page errors. JSON-parse rejection is handled by the second .then rejection handler (:130).
Remote values via textContent only Met [F]. :115 uses textContent, :121 uses title. There is no innerHTML on the version path. M4 (innerHTML) is caught by unit and E2E. scripts/check-xss-sinks.sh --file public/nav-drawer.js flags only the pre-existing static route href (same finding on master at :118). The new setAttribute('href', RELEASES_URL) is a constant and is not flagged.
Confirm the /api/health field names Met [F]. HealthResponse has json:"version", "commit" and "buildTime" (cmd/server/types.go:621-623). Curl on the local head server returned {version: unknown, commit: unknown, buildTime: unknown}. /api/stats carries the same three fields; the PR does not use it.
No drawer layout regression at narrow desktop/tablet widths Met [F]. E2E passes at 1440x900, 1024x768, 800x900 and 1280x480. My own probe at 1280x220 and 1280x120 keeps the footer at 38px at the drawer bottom while the list shrinks and scrolls.
Decide mobile More parity separately; no second eager fetch Met [F]. bottom-nav.js is untouched. The only new /api/health caller is the drawer.
Existing navigation/accessibility tests stay green Met [F]. See Suites. The focus trap now wraps between the close button and the version link: Shift+Tab from the first focusable lands on the version link and Tab from there returns to close [F]. The drawer stays inert when closed.

Test-first and mutants

Commit A → head: both test files are byte-identical (diff of A vs head, no changes). [F]

Red on master and A, green on the head [F]:

  • Unit test-issue-111-drawer-version.js: master 1 passed / 13 failed; A 1 / 13; head 14 / 0.
  • E2E test-issue-111-drawer-version-e2e.js: master server 2 passed / 5 failed (every "no version link" check fails, plus a timeout on the routed case); head server 7 / 0.

Mutants on the head. Each was run with the new unit test, the new E2E and test-nav-drawer-1064-e2e.js. The originals were restored and verified by shasum against git show 19b7e882:<path> (js 800dc6b5…, css dfa2b584…). [F]

# Mutant Unit E2E nav-1064 Result
M1 drop versionRequested = true (no cache) 9 fail 4 fail pass caught
M2 call requestVersion() before the width gate 1 fail 1 fail pass caught
M3 drop the "unknown" filter 2 fail 4 fail pass caught
M4 innerHTML instead of textContent 4 fail 1 fail pass caught
M5 fetch at DOM build (page load) 2 fail 5 fail pass caught
M6 drop the r.ok check 1 fail pass pass caught (unit)
M7 drop the typeof v !== 'string' guard crash (unhandled rejection) pass pass caught (unit)
M8 drop the rejection handler crash (unhandled rejection) pass pass caught (unit)
M9 drop flex-shrink: 0 on the footer pass pass pass equivalent: a probe at 1280x220 and 1280x120 shows an identical 38px footer and the same list height with and without it (a flex item's min-height: auto already stops it shrinking below its content)
M10 drop trim() 1 fail pass pass caught (unit)
M11 upstream releases URL 1 fail 4 fail pass caught
M12 never set title 2 fail 1 fail pass caught
M13 unfiltered commit in the tooltip 1 fail pass pass caught (unit)

Suites run locally

Suite master head
test-issue-111-drawer-version.js (new) 1/14 14/14
test-issue-111-drawer-version-e2e.js (new) 2/7 7/7
test-nav-drawer-1064-e2e.js 11/11 11/11
test-bottom-nav-1061-e2e.js 31/31 31/31
test-gesture-hints-1065-e2e.js 15/15 15/15
test-nav-more-floor-1139-e2e.js 10/10 10/10
test-issue-1648-m1-icons-e2e.js 16/16 16/16
test-privacy-page.js 34/34 34/34
test-issue-1648-m1-emoji-scan.js pass pass
test-issue-1668-m4-per-route.js pass pass
test-packet-filter.js 92/92 92/92
test-aging.js 19/19 19/19
test-frontend-helpers.js 705 pass / 2 fail 705 pass / 2 fail. Identical failure set (favStar ×2), pre-existing and unrelated

All counts above are [F]. Go was not touched by the PR, so the Go suites were not run. eslint could not be run: there is no config in the tree, so I rely on the PR text for that [T].

Browser

Own Playwright scenario against the head server [F]:

  • 1280x800 with touch: 0 /api/health requests at load, then a real touch pointer swipe (down 30 → up 300) opens the drawer and makes exactly 1 request.
  • The assertions of the existing "Version info lives on Perf dashboard, not in navbar" test still hold with the drawer open: there is no #navStats .version-badge or .engine-badge, and the drawer is appended to <body>, outside #navStats, the top nav and any header. The PR's footer does not touch #navStats.
  • Routed HTTP 500, " UNKNOWN " and an HTML body: all show the neutral CoreScope label, an empty title and no page errors.
  • A routed long version is ellipsised inside the drawer (see finding 2).
  • Screenshots (kept locally with the reviewer, not attached):
    • shot-1024x768-dark.png: footer CoreScope v3.1.4 at the bottom and all routes visible.
    • shot-1280x480-long.png: the ellipsis.
    • shot-1280x220.png
  • The light theme shows the pre-existing white drawer header, the same on master as the PR states. It is unrelated.
  • Computed colours: the link uses --nav-text-muted (rgb(203,213,225)) on --nav-bg, and the border uses --border in both themes. This matches the existing drawer header border.

Performance and security

  • One /api/health request per page lifetime, only after the first wide open. There is no work at load and nothing on a render, ingest or WS path. No perf claim needs proof. [F]
  • The new state is bounded: one boolean and one element ref. There are no timers or listeners. [F]
  • The only DOM sinks are textContent and title. The href is a constant. [F]
  • CSS uses variables only (--border, --nav-text-muted, --nav-text, --accent). The rgba values in nav-drawer.css are pre-existing. [F]
  • cmd/server is not touched. No map[string]interface{} was added. [F]
  • A failed first fetch is cached for the page lifetime, so a transient error leaves the neutral label until reload. That is what the issue asks for. [K]

Not verified

  • Real touch devices or tablets. The swipe was simulated with synthetic PointerEvents.
  • A real release build's version string. Local builds report unknown, so the populated case used routed responses.
  • On a mouse-only desktop the drawer cannot be opened at all (touch/pen only, and no hamburger calls __navDrawer.open), so those users never see the footer. This is pre-existing drawer design. [K]
  • The full test-e2e-playwright.js run. It is known to fail fast locally; its version assertion was reproduced in my probe instead.
  • CI's check-xss-sinks.sh --diff mode. There is no git in the archive, so I used --file mode on master and head as described above.
  • The PR's "Not verified" section (touch devices; the real release string) is honest and matches my gaps.

@dborup
dborup marked this pull request as ready for review September 29, 2026 12:35
@dborup
dborup merged commit ddae955 into master Sep 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant