Skip to content

feat(ui): show the current view in the browser tab title - #584

Merged
dborup merged 2 commits into
masterfrom
feat/563-route-tab-titles
Oct 25, 2026
Merged

dborup merged 2 commits into
masterfrom
feat/563-route-tab-titles

Conversation

@dborup-agent

Copy link
Copy Markdown
Collaborator

Relates to #563

Plan / what changes

  • New public/page-title.js is the single owner of document.title, format <View> · <entity> · <instance> (for example Live · My Mesh, Reach · Node A · My Mesh). Unknown routes get Not found · <instance>.
  • app.js navigate() calls PageTitle.setRoute(basePage) once per navigation, after the previous page's destroy(). This also clears any entity label.
  • customize.js / customize-v2.js: the five document.title = siteName writes now call setInstanceTitle(), which only changes the instance part. A delayed theme load therefore cannot erase the route title, and navigation never resets the configured name to a default.
  • Node Reach, Node Analytics and Observer detail add their label from the response they already render (no extra requests), through setEntity(label, token). The token is taken at init(); any navigation (including leaving and revisiting the same route) invalidates it, so a late response cannot rename the tab. Failed or unavailable lookups never set a label.
  • Labels are written only to document.title (text), collapsed to one line and capped at 60 characters.
  • Work is bounded per navigation / per detail load; nothing runs per packet or WebSocket event.

Tests

  • test-page-title-563.js (unit, added to test-all.sh): 13 cases.
  • test-e2e-563-route-titles.js (Playwright against the e2e fixture, added to shard 1 in scripts/e2e-shards.json): deep link, route change, back/forward, unknown route, node Reach and Analytics, customised instance name with a 1.5 s delayed theme response, late Reach response after navigating away. Red on unchanged master, green with the change.

Follow-ups (not in this PR)

  • The Not found wording and 60-character label cap are hardcoded; they could be exposed in the customizer later.
  • Node detail (#/nodes/<pubkey> side pane) keeps the plain Nodes · <instance> title.

🤖 Generated with Claude Code

dborup and others added 2 commits October 25, 2026 04:18
Relates to #563

Add page-title.js as the single owner of document.title
("<View> · <entity> · <instance>"). The router sets the view on every
navigation; branding/customizer updates only change the instance name;
Reach, Node Analytics and Observer pages add their label from data they
already loaded, guarded by a per-visit token.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tions (#563)

eslint no-undef flagged the bare global; the two customizer E2E suites
asserted a bare site name as document.title, which is now
"<View> · <instance>".

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@dborup-agent

Copy link
Copy Markdown
Collaborator Author

Rapport — CS-pve-agent2 PR#584 #563 — head 501db83

Status: Draft PR, all CI jobs green on head 501db83; not ready for merge by me (draft, awaiting human review).

Requirements, tests and mutants

Requirement Test (red on master, green now) Mutant (killed)
Route-aware title <View> · <entity> · <instance>, concise fallback for unknown routes [T] test-page-title-563.js (13 cases); [T] test-e2e-563-route-titles.js: Live, Channels, Analytics, back/forward, unknown route [T] router no longer calls setRoute (15 E2E failures); fallback label removed (1 unit failure)
Configured instance name survives async theme load; navigation never resets it [T] E2E with a 1.5 s delayed theme response ("Harbour Mesh"); unit setInstance after/before route [T] customizer writes document.title directly (4 E2E failures); setInstance ignores the name (10 unit failures)
One central helper, called on navigation and when the node label is known [T] E2E: node Reach and Node Analytics titles with the node name [T] Reach label not set (1 E2E failure); Analytics visit token broken (1 E2E failure)
Late response cannot rename the tab, including a revisit of the same route [T] unit token tests; [T] E2E with gated Reach response after navigating away [T] token check removed (2 unit failures); entity not cleared on route change (3 unit failures)
Labels are text only [T] unit: hostile <img onerror> label stays literal text; control chars stripped, length capped [T] control-character stripping removed (1 unit failure)
  • [T] Red run: the E2E suite against the unchanged master public/ failed on every title check.
  • [T] Existing assertions updated: test-customize-branding-e2e.js and test-logo-rebrand-e2e.js expected a bare site name as the title. They now accept the … · <instance> form.
  • [A] No new API requests: labels come from the responses the pages already fetch.

Local checks

  • [T] sh test-all.sh: 269 passed, 0 failed.
  • [T] node test-frontend-helpers.js: 719 passed, 0 failed.
  • [T] cd cmd/server && go test ./...: ok. No Go files changed.
  • [T] scripts/check-xss-sinks.sh --diff origin/master: clean.
  • [T] eslint 8 on public/*.js: 0 errors.
  • [A] No colours added; no new map[string]interface{}; cmd/server untouched; workflow files untouched, so fork guards are unchanged.

CI per job (head 501db83)

  • [T] Pass: Go Build & Test, Go Ingestor, Go Libraries & CLI, Go Server 1/3, 2/3 and 3/3, Verify E2E Shards, Docker Image validation, JS Unit Tests & Lint, Script & Container, Playwright 1/3, 2/3 and 3/3, Docker build, Verify Go Test Assignments.
  • Skipped, as expected for a PR: Publish Badges, Release Artifacts, Deploy Staging.
  • [T] The first head (314dd16) failed two jobs: eslint no-undef on setInstanceTitle, and the branding E2E expecting a bare title. Both were fixed in 501db83. flaky: #180 packets URL E2E 'reload shows the same list' depends on fixture age (default 15-min window) #271 did not occur, so no reruns.

Residuals

  • [K] Node detail (#/nodes/<pubkey> side pane) keeps the plain Nodes · <instance> title.
  • [K] The Not found wording and the 60-character label cap are hardcoded. They are customizer candidates later.
  • [K] Browser validation was Playwright (Chromium) against a local server with the fixture DB. No screenshots, since the change is not visual beyond the tab title.

@dborup

dborup commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Review — CS-Minimax PR#584 — head 501db83

Dom: APPROVE med nits

Findings

# Severity Finding
1 nit [T] The customizer/branding write paths at customize-v2.js (overrides apply, ~l.2765) and the v1 equivalents are not covered by the new E2E. Mutant M2 (revert only that line to document.title = siteName) survives test-e2e-563-route-titles.js, test-customize-branding-e2e.js and test-logo-rebrand-e2e.js. The delayed-theme E2E exercises the server-theme path only.
2 nit [T] The page-level token guard (PageTitle.visit() captured in init()) is only proven at helper level (unit). In the E2E, a late Reach response is already dropped by the page's own loadGen/loadSequence. Mutant M4 (analytics uses the current visit instead of its captured token) survives, and M1 (no visit++) survives the E2E but is killed by the unit test. So "leave and revisit the same route" is covered by unit only, not by the real router. Defence in depth, so not blocking.
3 nit [T] There is no E2E for Observer detail, and none for a failed/hidden/unavailable node lookup. [A] By code reading, the label is only set after a successful response, so a failed lookup never sets a name and setRoute clears the entity on every navigation.
4 nit [A] clean() also caps the instance name at 60 chars, so a longer configured site name is truncated in the tab only. Harmless, but it is a small behaviour difference from the header.

No blocking findings.

Answers to the review points

  • Consistent format via one helper: [T] Yes. public/page-title.js is the only document.title writer. The remaining document.title hit in public/*.js is a comment. The format is <View> · <entity> · <instance>. Every page registered with registerPage has a label (checked against ROUTE_LABELS).
  • Instance name preserved: [T] A 1.5 s delayed theme response ("Harbour Mesh") does not clear the route title, and navigation afterwards keeps the name. Empty site names are ignored, so navigation never resets to a default.
  • No HTML: [T] Labels only reach document.title through clean() (control chars removed, one line, max 60). The unit test covers a hostile <img onerror> label. scripts/check-xss-sinks.sh --diff origin/master: clean.
  • Unknown routes: [T] Not found · <instance> (E2E + unit).
  • E2E coverage: [T] Live, Channels, Analytics, back/forward, node Reach, Node Analytics and a custom instance name are all covered. Flakiness: 5/5 green runs.
  • Acceptance criteria red→green: [T] The new E2E against unchanged master public/ gives 15 failures. With the change it passes. Unit test fails when the helper's behaviour is mutated (see below).
  • No behaviour change beyond purpose: [A] Diff is limited to the title plumbing; no extra API requests (labels come from existing responses). Existing branding E2Es were adjusted to accept the … · <instance> form, as expected.
  • Invariants: [T] No Go files changed, so cmd/server stays read-only and no map[string]interface{} is added. No colours added. Commit author on both commits is dborup <kontakt@meshview.dk>. No closing keywords (Relates to #563). No workflow files changed, so fork guards are untouched (github.repository count in deploy.yml unchanged at 9).

Tests

Merged tree (git merge-tree of origin/master fe3b650 + head):

Mutants (own)

Mutant Result
M1: setRoute without visit++ killed by unit test, survives E2E (see finding 2)
M2: overrides-apply branding write back to document.title = … survives all three title/branding E2Es (finding 1)
M3: unknown-route fallback label emptied killed (1 E2E failure)
M4: Node Analytics uses current visit instead of captured token survives E2E (finding 2)

Not verified

  • Visual check in a real, headed browser; Playwright/Chromium only.
  • Seeds 245 and 122; cmd/ingestor tests.
  • Behaviour with a real (non-fixture) theme/override stored in user localStorage.
  • Go toolchain here was 1.26.0, CI uses 1.27.1.

@dborup
dborup marked this pull request as ready for review October 25, 2026 05:27
@dborup
dborup merged commit 2ce0631 into master Oct 25, 2026
18 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.

2 participants