Note: this issue was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.
Background
#863 / #872 added idle_decay_minutes to Config.theme (configurable via the TUI's Settings -> Theme view, or directly in config.toml). The TUI honors the user's setting at runtime via HomeView.idle_decay_window.
The dashboard does not. web/src/lib/session.ts declares:
export const IDLE_DECAY_WINDOW_MS = 20 * 60 * 1000;
The Rust Config.theme.idle_decay_minutes field IS already serialized and reachable at /api/settings (the existing handler returns the full Config blob), but the web side never reads it. So a user who changes the decay window in the TUI sees the new value reflected in the TUI but not in the dashboard.
Proposal
Wire the web dashboard to fetch the configured value once on app boot and use it in place of the hard-coded constant.
Sketch:
- Extend
web/src/lib/api.ts with a typed fetchSettings() (or extract just the relevant slice) that returns { theme: { idle_decay_minutes: number } } plus whatever else is useful.
- Store the resolved window in a small React context (or a Zustand-style atom if the project leans that way) created at the top of
App.tsx. Default to 20 min while the fetch is in flight so the UI doesn't flicker.
- Change
isFreshIdle / idleAgeMs / getStatusDotClass / getStatusTextClass / isSessionActive from pure functions over IDLE_DECAY_WINDOW_MS to either accept a window parameter or read from the context. Probably the explicit param keeps testability cleanest.
- Update the vitest cases in
web/src/lib/session.test.ts to pass the window through (or use a default in test fixtures).
Out of scope
- No new keybind / settings UI on the web side; reads through the existing TUI-driven config.
- No live-refresh: a settings change in the TUI mid-session won't reflect in the dashboard until the user reloads the page. That's acceptable; the gradient is a soft signal, not a critical one.
Acceptance
- A dashboard session that just stopped reflects the user's configured decay window (e.g. 5 min if the user set
idle_decay_minutes = 5), not the hard-coded 20.
- Setting
idle_decay_minutes = 0 in config.toml makes the dashboard skip the freshness signal entirely (matching TUI behavior).
- vitest still passes; no new Playwright cases required.
Why split from #872
The PR scope was already broad (TUI + web colors + settings wiring + a status-poller bug fix). Pulling the web/server config sync into its own PR keeps the diff reviewable and gives the existing PR a clean ship.
Background
#863 / #872 added
idle_decay_minutestoConfig.theme(configurable via the TUI's Settings -> Theme view, or directly inconfig.toml). The TUI honors the user's setting at runtime viaHomeView.idle_decay_window.The dashboard does not.
web/src/lib/session.tsdeclares:The Rust
Config.theme.idle_decay_minutesfield IS already serialized and reachable at/api/settings(the existing handler returns the fullConfigblob), but the web side never reads it. So a user who changes the decay window in the TUI sees the new value reflected in the TUI but not in the dashboard.Proposal
Wire the web dashboard to fetch the configured value once on app boot and use it in place of the hard-coded constant.
Sketch:
web/src/lib/api.tswith a typedfetchSettings()(or extract just the relevant slice) that returns{ theme: { idle_decay_minutes: number } }plus whatever else is useful.App.tsx. Default to 20 min while the fetch is in flight so the UI doesn't flicker.isFreshIdle/idleAgeMs/getStatusDotClass/getStatusTextClass/isSessionActivefrom pure functions overIDLE_DECAY_WINDOW_MSto either accept a window parameter or read from the context. Probably the explicit param keeps testability cleanest.web/src/lib/session.test.tsto pass the window through (or use a default in test fixtures).Out of scope
Acceptance
idle_decay_minutes = 5), not the hard-coded 20.idle_decay_minutes = 0inconfig.tomlmakes the dashboard skip the freshness signal entirely (matching TUI behavior).Why split from #872
The PR scope was already broad (TUI + web colors + settings wiring + a status-poller bug fix). Pulling the web/server config sync into its own PR keeps the diff reviewable and gives the existing PR a clean ship.