Skip to content

Reflect run status in tab title + favicon (BEN-50)#54

Closed
benSepanski wants to merge 1 commit intomainfrom
claude/nifty-bardeen-d99QH
Closed

Reflect run status in tab title + favicon (BEN-50)#54
benSepanski wants to merge 1 commit intomainfrom
claude/nifty-bardeen-d99QH

Conversation

@benSepanski
Copy link
Copy Markdown
Owner

Context

BEN-50: src/web/index.html hardcoded <title>Symphony</title> and shipped no favicon, so users keeping Symphony open alongside tracker / docs / logs had no at-a-glance signal that a run went red, and multiple run-detail tabs were indistinguishable.

TL;DR

Each top-level route now sets its own tab title via a tiny useDocumentChrome hook, and ships a monotone SVG favicon that flips red on a recent failure.

Summary

  • New src/web/documentTitle.ts — pure helpers (dashboardTitle, runDetailTitle, dashboardFaviconColor, runFaviconColor, findMostRecentFailure, buildFaviconHref). 14 unit tests in documentTitle.test.ts.
  • New src/web/useDocumentChrome.ts — thin React hook that writes document.title and patches a single <link rel="icon">; lazily creates the link tag if the page didn't ship one.
  • Dashboard.tsx — derives title from runs + a 30 s now tick. Symphony● N running · Symphony✖ <issue> <status> · Symphony for failures within the 5 min window.
  • RunDetail.tsx<issue> · <status> · Symphony; favicon flips red while the run is failed / rate_limited.
  • Search.tsx<query> · search · Symphony (or just search · Symphony) so the route doesn't inherit a stale dashboard title.
  • src/web/index.html — ships the neutral SVG favicon up front so the first paint is already correct; runtime updates the same <link> for state changes.
  • docs/FRONTEND.md — documents the per-route title pattern + 5 min failure-escalation window.

Demo

n/a — automated cron run; no browser MCP in this environment. Manual smoke-test path: pnpm dev WORKFLOW.md --mock, open the dashboard with a live mock run (● 1 running · Symphony), trigger the crash scenario and confirm the tab title escalates to ✖ <issue> failed · Symphony and the favicon flips rose; open two run-detail tabs and confirm each tab title differs.

Alternatives

  • Lift runs into App.tsx and set the title from there — rejected: requires re-plumbing every Dashboard data path through a shared store; the per-view hook keeps each route owning its own state.
  • requestAnimationFrame to update title — rejected: BEN-50 acceptance explicitly calls out that backgrounded tabs must still update, and rAF is paused when the tab is hidden. setInterval keeps the failure window honest.
  • Ship a real PNG favicon under public/ — rejected: a 64×64 SVG data URL is two lines of code, recolorable at runtime, and avoids a binary asset in the repo.
  • Use the Notification API for run-finished events (mentioned in the ticket) — deferred. Tab-chrome alone covers the acceptance criteria; opt-in notifications belong in a follow-up alongside BEN-9's server-side hook.

Test Plan

  • pnpm all — typecheck + fmt:check + lint + test + eval (134 unit + 5 eval, all green)
  • pnpm build:web — bundle builds (176 ms, 228.86 kB)
  • New documentTitle.test.ts covers running-count titles, recent-failure escalation, stale-failure expiration, missing/future finishedAt, per-route favicon color, SVG data URL encoding, and findMostRecentFailure ordering.

Generated by Claude Code

Each top-level route now sets its own tab title and a tiny SVG favicon.
The dashboard escalates to a recent failure for five minutes, so a tab
parked alongside other work surfaces a red 'S' + '✖ BEN-30 failed'
without forcing the user to switch tabs.

- documentTitle.ts: pure helpers (dashboardTitle, runDetailTitle,
  faviconColor, buildFaviconHref) with 14 unit tests.
- useDocumentChrome.ts: thin React hook that mutates document.title
  and a single <link rel="icon"> on each render.
- Dashboard / RunDetail / Search call the hook with the right data;
  Dashboard ticks 'now' every 30s so the failure window expires
  without an SSE event.
- index.html ships the neutral favicon up front so the very first
  paint already has it; runtime swaps to the fail variant when needed.
@benSepanski benSepanski closed this May 4, 2026
@benSepanski benSepanski deleted the claude/nifty-bardeen-d99QH branch May 4, 2026 13:05
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