Implement Fenris TUI polish and hourly history #72

Closed
opened 2026-09-13 20:50:51 +00:00 by xavierk · 1 comment
Owner

Problem Statement

Fenris now has an approved companion specification for a polished TUI and trustworthy hourly history, but no implementing issue ties that decision-ready handoff to the current codebase. Users still face a dashboard that can obscure Fenris identity, monitoring state, first-data availability, and history confidence: graph gaps can look like zero usage, one sample can look like a ready history point, projection warm-up can be confused with graph availability, and service/failure/paused states are not described by the approved status lattice. The current collection path also writes samples without publishing the complete sample → usage interval → hour observation/day aggregate contract before reporting success, so an implementer needs one execution spec that turns the approved map into behavior.

Solution

Implement the approved TUI polish and hourly-history handoff as one coherent execution effort. Fenris will show a clear 🐺 Fenris by Bongbetic titlebox with safe fallback, truthful status and collection states, writes-only daily bars with hourly drill-down, user-scoped colour and reduced-motion preferences, vendor wear in Drive health, and explicit graph/projection availability messages. The collector will publish derived usage intervals, hour observations, and day aggregates consistently before declaring collection success. Projection accounting will use exact evidence endpoints, distinguish byte-allocation completeness from coverage, withhold rates that lack a complete monitored numerator, and preserve all existing lifespan mathematics and confidence thresholds.

This spec consumes the approved Wayfinder handoff. It does not reopen design questions; it implements the approved companion specification and acceptance amendments.

User Stories

  1. As a TUI user, I want the top titlebox to read 🐺 Fenris by Bongbetic, so that I immediately know which application I am using and who makes it.
  2. As a TUI user on a terminal that cannot render the wolf glyph reliably, I want a stable Fenris by Bongbetic fallback, so that the title never shows tofu or unstable width.
  3. As a TUI user, I want the old duplicate maker credit removed from the service strip, so that product identity appears once in the intended place.
  4. As a TUI user, I want the prior continuity row behavior preserved, so that I still know whether monitoring persists across reboots.
  5. As a TUI user, I want the quit rail preserved separately from monitoring controls, so that leaving the screen never looks like pausing collection.
  6. As a TUI user, I want the polkit authentication banner wording preserved, so that privileged actions are explained accurately.
  7. As a CLI user, I want status wording that matches the TUI where parity is required, so that terminal checks and dashboard checks tell the same story.
  8. As a TUI user, I want status shown as glyph plus text, so that colour is never the only way to understand state.
  9. As a colour-blind TUI user, I want status text and glyphs to remain visible in every preset, so that semantic state is accessible.
  10. As a reduced-motion user, I want Monitoring to render without blinking, so that the dashboard remains comfortable and accessible.
  11. As a TUI user, I want only the Monitoring dot to blink in normal motion, so that animation never implies that a collection run just succeeded.
  12. As a TUI user, I want Collecting to appear explicitly while a run is in flight, so that I can distinguish collection activity from steady monitoring.
  13. As a TUI user, I want Paused to remain amber even after many stale days, so that a deliberate disable is not misrepresented as an unexpected failure.
  14. As a TUI user, I want external service stops to render as Interrupted, so that I can tell Fenris did not record a Deliberate disable.
  15. As a TUI user, I want failed runs with fresh data to show both the error and the last good sample age, so that I know collection failed without losing the fact that current data still exists.
  16. As a TUI user, I want store faults to suppress store-dependent views and point to the journal, so that Fenris never guesses from an unreadable observation store.
  17. As a CLI user, I want fenris status to use the same status lattice statically, so that CLI output does not drift from the TUI.
  18. As a TUI user, I want freshness, last outcome, boot enablement, and collection activity kept as separate facts, so that one word does not hide conflicting evidence.
  19. As a TUI user, I want status aging boundaries to update promptly from lightweight service polling, so that Waiting can become Stale without waiting for a full store refresh.
  20. As a new user with an empty observation store, I want the dashboard to say it is awaiting the first sample, so that blank history is not confused with zero usage.
  21. As a user with exactly one sample, I want Fenris to say Awaiting another sample, so that I know a usage delta cannot yet exist.
  22. As a user after the first compatible sample pair, I want measured partial-hour usage labelled so far, so that early graph data appears promptly without extrapolation.
  23. As a user whose counter did not change during an evidenced interval, I want visible 0 B, so that true zero usage is distinct from missing evidence.
  24. As a user with missing or unsupported counter evidence, I want an explicit unavailable fact, so that missing evidence never becomes zero.
  25. As a user crossing an hour boundary between two samples, I want the measured interval retained once, so that Fenris does not invent per-hour shares.
  26. As a user crossing local midnight between two samples, I want the total shown as unallocated when the split is unknowable, so that day totals remain trustworthy.
  27. As a user in a non-UTC timezone, I want history browsed by my current local display day, so that the graph matches the calendar I live in.
  28. As a user who changes system timezone, I want retained evidence regrouped without rewriting history, so that the same observation history remains truthful.
  29. As a user on a DST transition day, I want 23-hour, 25-hour, and repeated local-hour cases handled honestly, so that local-day browsing does not assume fixed 24-hour days.
  30. As a user with long-retained history, I want usage intervals retained indefinitely alongside hour observations and day aggregates, so that local browsing and repair do not lose necessary evidence.
  31. As a user whose raw samples are old, I want raw pruning to keep any boundary anchor still needed for durable derivation, so that pruning does not destroy unfinished evidence.
  32. As a user after a repair rerun, I want no duplicate or fabricated derived history, so that repair is safe to retry.
  33. As a user with old valid summaries, I want incomplete reconstruction to preserve those summaries, so that a repair does not replace valid history with less evidence.
  34. As a user during a deliberate pause, I want paused time excluded from the usage habit, so that deliberate disables do not dilute projection evidence.
  35. As a user whose counter interval crosses a pause, I want ambiguous paused bytes excluded from monitored totals, so that monitored usage is not overstated.
  36. As a user whose collector was externally stopped, I want the gap treated as unexplained in-period time, so that it is not mistaken for a deliberate pause.
  37. As a user after a controller reset, I want prior same-drive habit evidence browsable and handled under the existing re-warm gate, so that reset handling does not erase valid history.
  38. As a user after a controller replacement, I want prior identity quarantined from projection horizons, so that a different drive never contributes to this drive's projection.
  39. As a user with legacy day-only summaries, I want those summaries used only at their actual precision, so that Fenris does not invent local-day, subday, or partial-window evidence.
  40. As a TUI user, I want a writes-only daily bar graph by default, so that the main graph tracks the metric the endurance projection consumes.
  41. As a TUI user, I want 7-, 14-, 28-, and 90-day graph ranges with 14 days as default, so that history browsing and projection horizons use shared vocabulary.
  42. As a TUI user, I want to select a day and drill into hourly bars, so that I can inspect the detail behind a daily total.
  43. As a keyboard user, I want arrows, Enter, Esc/Backspace, and range-number keys to control the graph, so that the graph is fully navigable without a mouse.
  44. As a mouse user in a Textual terminal, I want clicking a bar to select or drill, so that direct manipulation works where the framework supports it.
  45. As a TUI user, I want the graph legend to distinguish allocated writes, unallocated writes, gaps, measured zero, partial days, and selection, so that graph shape never fabricates evidence.
  46. As a TUI user, I want selected-day readout to show totals, evidenced hours, unallocated usage, coverage, and partial elapsed facts, so that the graph can be audited from text.
  47. As a user on an 80×24 terminal, I want the default graph and hourly drill-down to fit, so that the polished UI remains usable in a common small terminal.
  48. As a user below 80×24, I want a textual history summary and graph needs ≥80×24, so that the UI degrades honestly instead of showing stale or broken graph state.
  49. As a TUI user, I want Amber, Nord, and High Contrast presets, so that the dashboard can match my readability preferences.
  50. As a TUI user, I want Amber to be the default with an amber graph, so that the approved visual identity appears without setup.
  51. As a TUI user, I want preset and reduced-motion preferences stored per unprivileged user, so that my display preferences survive restarts without affecting collection or projection.
  52. As a CLI user, I want TUI display preferences to have no effect on fenris status, so that CLI facts remain objective.
  53. As a TUI user, I want t preset and m motion controls with accessible clickable equivalents, so that changing display preferences is discoverable.
  54. As a TUI user, I want vendor wear under Drive health, so that vendor-reported wear appears with other drive-health facts rather than settings.
  55. As a TUI user, I want Settings to remain read-only and limited to device, baseline/provenance, retention, and display preferences, so that operational configuration is not mixed with health facts.
  56. As a user without an endurance baseline, I want a clear withheld-estimate reason, so that I know what fact is missing before a lifespan estimate can appear.
  57. As a user whose drive lacks write counters, I want a clear withheld-estimate reason, so that unsupported hardware is not shown as zero usage.
  58. As a user during projection warm-up, I want Building evidence — N of 14 days observed · Q qualifying, so that I can see evidence progress without a fabricated countdown.
  59. As a user with stale evidence, I want the estimate frozen at the last evidence endpoint with estimate not updating — last sample X ago, so that aging evidence is clear.
  60. As a user with graph data before projection readiness, I want the graph to render while lifespan remains withheld, so that first data is not hidden behind the confidence gate.
  61. As a user with a 7-day horizon cut by an ambiguous positive interval, I want that rate withheld while independent horizons remain visible, so that unavailable data does not erase valid data.
  62. As a user with a zero-delta interval crossing a boundary, I want exact zero evidence used where justified, so that zero counter evidence is not treated like missing evidence.
  63. As a user viewing scenario ranges, I want horizons anchored at the latest published usage-evidence endpoint, so that merely refreshing the dashboard does not dilute rates.
  64. As a user with unknown daily shares, I want Supported confidence blocked where burst or habit checks cannot be established, so that confidence never silently assumes missing evidence passed.
  65. As a maintainer, I want no plotting dependency added for the adopted graph, so that the implementation stays within approved dependencies.
  66. As an implementer, I want direct observable proof paths, so that completion is based on behavior, not only a successful build.

Implementation Decisions

  • Implement against the approved companion specification and TPH-1 through TPH-11 acceptance amendments. These newer criteria supersede only the old header/credit placement and refine status/history/projection behavior; they do not weaken dashboard continuity, paused-state, quit, auth, CLI parity, release-notes, or existing ADR contracts.
  • Keep the frozen redesign spec as historical baseline. The implementation may bring the approved companion spec, glossary amendments, and acceptance criteria into the implementation branch, but production behavior is governed by the approved handoff, not by re-opening Wayfinder decisions.
  • Preserve the existing architecture: privileged collection writes the observation store; TUI and fenris status are read-only; projections are recomputed on read; systemd and polkit remain the coordination and elevation surfaces.
  • Extend the observation-store model so retained usage intervals, byte-allocation completeness, unallocated usage, local-display-day browsing, partial represented spans, and projection numerator availability can be represented without interpolation. Use forward-only migrations and defensive newer-schema behavior consistent with existing store rules.
  • Keep full raw-sample retention at 14 days, but add the approved boundary-anchor exception so evidence needed for unfinished derivation is not pruned prematurely. Hour observations, day aggregates, and retained usage intervals remain long-lived observation history.
  • Make the collector own sample → usage interval → hour observation/day aggregate derivation. A successful collection run publishes a consistent validated result before reporting success; if derivation or validation fails, it writes no partial dependent history and preserves prior valid history.
  • Preserve measured interval totals exactly once. Do not proportionally split writes across hours or calendar days, assign them to endpoints, double-count interval totals and summaries, or convert missing evidence to zero.
  • Treat deliberately disabled time as outside the usage habit, not as an hour state. Intervals crossing a pause preserve original evidence but do not supply monitored totals unless the monitored share is established.
  • Keep controller segment boundaries hard: no deltas across reset/replacement boundaries; same-identity reset keeps eligible prior habit evidence subject to re-warm; identity change quarantines previous identity from every projection horizon.
  • Use the user's current system timezone only for browsing and labelling local display days. Projection evidence remains UTC. Regrouping after timezone changes must not rewrite measured history.
  • Update projection accounting so 7/28/90-day scenario windows end at the latest published usage-evidence endpoint and start at exact elapsed-second offsets. Do not round starts to UTC midnight and do not advance denominators merely because the TUI refreshes.
  • Withhold a rate when the monitored numerator is unavailable. Distinguish unknown rate from zero measured rate. Keep the endurance formula, cumulative endurance consumption operand, numeric thresholds, baseline tiers, confidence categories, and collector cadence unchanged.
  • Treat coverage and byte-allocation completeness as independent axes. A provisional UTC date can qualify by elapsed monitored coverage, but unknown daily totals cannot establish habit change, cannot pass burst/concentration checks, and cannot produce Supported confidence where required checks are unevaluable.
  • Implement the warm-up contract: first lifespan estimate after 14 represented UTC dates in the current controller segment with at least 12 qualifying; Supported still requires 14 qualifying dates and all other prerequisites.
  • Centralize the status lattice in the shared status composition layer consumed by TUI and CLI. The states are Monitoring, Collecting, Paused, Waiting, Interrupted, Error, Stale, and Unknown, with the approved precedence and Collecting overlay behavior.
  • Keep freshness, last outcome, boot enablement, and collection activity as separate facts. Use lightweight unit-state polling for status-strip transitions while keeping full store refresh on the existing cadence.
  • Implement the titlebox and maker-credit changes exactly: top titlebox with wolf/fallback, no service-strip maker credit, lifespan headline remains data rather than app title.
  • Move vendor wear to Drive health and keep Settings read-only for device selector, endurance baseline/provenance, retention facts, and display preferences. Do not add a custom colour editor.
  • Replace the old write-history sparkline with the approved writes-only daily bar graph and hourly drill-down. Implement it as a custom block-glyph renderable with keyboard and mouse navigation; do not add a plotting dependency.
  • Preserve global p, r, c, d, and q behavior. Add graph focus controls for selection, drill-down, back, and ranges. Add t preset and m motion controls with clickable equivalents where Textual supports them.
  • Implement the graph's state legend and readouts so allocated writes, unallocated measured writes, gaps, measured zero, partial days, and selection are textually distinguishable.
  • Implement 80×24 as the minimum graph layout target and a below-80×24 textual fallback. Resizing must clear hidden graph state rather than leaving stale visuals.
  • Add Amber, Nord, and High Contrast presets. Themes style chrome, graph, borders, accents, and muted text; status semantic colours/glyphs/text override theme styling.
  • Persist preset and reduced-motion choices as user-scoped TUI preferences in the approved XDG user config location. The file must not affect collection, projection, history evidence, helper state, package config, or CLI status.
  • Retain all prior dashboard clarity behavior except the explicitly superseded identity placement: continuity wording, paused block, quit rail, footer ownership, polkit banner, release notes, and TUI/CLI parity remain binding.

Testing Decisions

  • Good tests assert external behavior: rendered TUI text/visible graph state, rendered fenris status output, observation-store rows visible to readers, and projection/status facts from fixture stores. Avoid tests that couple to widget internals, private helper layout, or implementation-only constant names when user-visible behavior can be asserted instead.
  • Primary seam: a synthetic observation store, fake unit/service state, and frozen clock flow through the shared status/history/projection composition into both the TUI and CLI status surfaces. This is the highest-value seam because it covers most user-visible behavior and enforces TUI/CLI parity. Prior art exists in the headless TUI tests, acceptance sweep, and status tests.
  • Collector publication seam: fake acquisition data and controlled clock feed a collection run, then a read-only connection observes samples, usage intervals, hour observations, day aggregates, freshness, and graph/projection facts. This covers the new publication invariant that collection success means dependent history was durably derived. Prior art exists in collector, monitoring-period, day-aggregate, legacy-migration, pruning, and store-migration tests.
  • Projection/evidence seam: fixture observation stores exercise exact evidence endpoints, unavailable numerator cases, qualifying-day progress, warm-up versus Supported, segment quarantine, legacy actual-precision summaries, and zero-versus-unknown rates through the public projection result and rendered reason lines. Prior art exists in projection and acceptance-sweep tests.
  • TUI interaction seam: headless Textual Pilot drives graph focus, range switching, day selection, hourly drill-down, back navigation, theme switching, reduced-motion toggle, titlebox rendering, and resize behavior. Assert visible text, selected readouts, and action results, not internal widget structure unless no user-visible seam exists.
  • CLI parity seam: render fenris status from the same synthetic states used by TUI tests and assert the approved status vocabulary, glyphs, precedence, reason lines, and absence of TUI-only title/preferences. Prior art exists in the current parity sweep and status render tests.
  • Preference seam: run the TUI under a temporary XDG config home, change preset and reduced-motion settings through public controls, restart, and verify persistence. Separately assert that CLI status and collector behavior are unchanged by preferences.
  • Store-fault/newer-schema seam: use malformed and newer-version stores to verify that store-dependent views are suppressed and the approved error wording appears in TUI and CLI. Prior art exists in store-fault and newer-schema tests.
  • Constrained-terminal seam: run headless terminal captures at 80×24 and below 80×24 to verify graph fit/fallback and preserved title/status/health/action context.
  • Direct proof examples from the companion spec should become fixture cases: first sample, first compatible pair, measured zero, cross-hour interval, cross-local-midnight interval, pause-crossing interval, stale frozen estimate, 12 qualifying plus 2 poor dates, unknown daily shares, controller reset/replacement, trusted legacy day-only summary, and an old-enough but boundary-cut horizon.
  • A build alone is not completion. The proof must include behavioral tests at the seams above plus the smallest necessary system probes for live systemd/polkit behavior already covered by existing acceptance categories.

Out of Scope

  • Re-opening the approved Wayfinder decisions or changing the approved companion specification during implementation.
  • Editing the frozen redesign spec as part of implementation, except to carry the approved companion/criteria/glossary assets forward if the execution branch needs them.
  • Changing lifespan mathematics, numeric confidence thresholds, baseline precedence, controller-segment identity semantics, or collector cadence beyond the explicit UTC-accounting/evaluability amendments.
  • Fabricating, interpolating, proportionally splitting, endpoint-assigning, or zero-filling missing history.
  • Adding a read-throughput graph selector, custom colour editor, web GUI, alerts, notifications, telemetry, automatic vendor-data fetching, or broader application redesign.
  • Adding a plotting dependency for the usage-history graph.
  • Package publishing, release creation, installation-channel changes, or changelog/release-notes work beyond whatever ordinary implementation bookkeeping requires.
  • Runtime diagnosis of any deployed Fenris store or host. Source-level defects may be fixed, but this spec does not claim a deployed diagnosis.

Further Notes

## Problem Statement Fenris now has an approved companion specification for a polished TUI and trustworthy hourly history, but no implementing issue ties that decision-ready handoff to the current codebase. Users still face a dashboard that can obscure Fenris identity, monitoring state, first-data availability, and history confidence: graph gaps can look like zero usage, one sample can look like a ready history point, projection warm-up can be confused with graph availability, and service/failure/paused states are not described by the approved status lattice. The current collection path also writes samples without publishing the complete sample → usage interval → hour observation/day aggregate contract before reporting success, so an implementer needs one execution spec that turns the approved map into behavior. ## Solution Implement the approved TUI polish and hourly-history handoff as one coherent execution effort. Fenris will show a clear `🐺 Fenris by Bongbetic` titlebox with safe fallback, truthful status and collection states, writes-only daily bars with hourly drill-down, user-scoped colour and reduced-motion preferences, vendor wear in Drive health, and explicit graph/projection availability messages. The collector will publish derived usage intervals, hour observations, and day aggregates consistently before declaring collection success. Projection accounting will use exact evidence endpoints, distinguish byte-allocation completeness from coverage, withhold rates that lack a complete monitored numerator, and preserve all existing lifespan mathematics and confidence thresholds. This spec consumes the approved Wayfinder handoff. It does not reopen design questions; it implements the approved companion specification and acceptance amendments. ## User Stories 1. As a TUI user, I want the top titlebox to read `🐺 Fenris by Bongbetic`, so that I immediately know which application I am using and who makes it. 2. As a TUI user on a terminal that cannot render the wolf glyph reliably, I want a stable `Fenris by Bongbetic` fallback, so that the title never shows tofu or unstable width. 3. As a TUI user, I want the old duplicate maker credit removed from the service strip, so that product identity appears once in the intended place. 4. As a TUI user, I want the prior continuity row behavior preserved, so that I still know whether monitoring persists across reboots. 5. As a TUI user, I want the quit rail preserved separately from monitoring controls, so that leaving the screen never looks like pausing collection. 6. As a TUI user, I want the polkit authentication banner wording preserved, so that privileged actions are explained accurately. 7. As a CLI user, I want status wording that matches the TUI where parity is required, so that terminal checks and dashboard checks tell the same story. 8. As a TUI user, I want status shown as glyph plus text, so that colour is never the only way to understand state. 9. As a colour-blind TUI user, I want status text and glyphs to remain visible in every preset, so that semantic state is accessible. 10. As a reduced-motion user, I want Monitoring to render without blinking, so that the dashboard remains comfortable and accessible. 11. As a TUI user, I want only the Monitoring dot to blink in normal motion, so that animation never implies that a collection run just succeeded. 12. As a TUI user, I want Collecting to appear explicitly while a run is in flight, so that I can distinguish collection activity from steady monitoring. 13. As a TUI user, I want Paused to remain amber even after many stale days, so that a deliberate disable is not misrepresented as an unexpected failure. 14. As a TUI user, I want external service stops to render as Interrupted, so that I can tell Fenris did not record a Deliberate disable. 15. As a TUI user, I want failed runs with fresh data to show both the error and the last good sample age, so that I know collection failed without losing the fact that current data still exists. 16. As a TUI user, I want store faults to suppress store-dependent views and point to the journal, so that Fenris never guesses from an unreadable observation store. 17. As a CLI user, I want `fenris status` to use the same status lattice statically, so that CLI output does not drift from the TUI. 18. As a TUI user, I want freshness, last outcome, boot enablement, and collection activity kept as separate facts, so that one word does not hide conflicting evidence. 19. As a TUI user, I want status aging boundaries to update promptly from lightweight service polling, so that Waiting can become Stale without waiting for a full store refresh. 20. As a new user with an empty observation store, I want the dashboard to say it is awaiting the first sample, so that blank history is not confused with zero usage. 21. As a user with exactly one sample, I want Fenris to say `Awaiting another sample`, so that I know a usage delta cannot yet exist. 22. As a user after the first compatible sample pair, I want measured partial-hour usage labelled `so far`, so that early graph data appears promptly without extrapolation. 23. As a user whose counter did not change during an evidenced interval, I want visible `0 B`, so that true zero usage is distinct from missing evidence. 24. As a user with missing or unsupported counter evidence, I want an explicit unavailable fact, so that missing evidence never becomes zero. 25. As a user crossing an hour boundary between two samples, I want the measured interval retained once, so that Fenris does not invent per-hour shares. 26. As a user crossing local midnight between two samples, I want the total shown as unallocated when the split is unknowable, so that day totals remain trustworthy. 27. As a user in a non-UTC timezone, I want history browsed by my current local display day, so that the graph matches the calendar I live in. 28. As a user who changes system timezone, I want retained evidence regrouped without rewriting history, so that the same observation history remains truthful. 29. As a user on a DST transition day, I want 23-hour, 25-hour, and repeated local-hour cases handled honestly, so that local-day browsing does not assume fixed 24-hour days. 30. As a user with long-retained history, I want usage intervals retained indefinitely alongside hour observations and day aggregates, so that local browsing and repair do not lose necessary evidence. 31. As a user whose raw samples are old, I want raw pruning to keep any boundary anchor still needed for durable derivation, so that pruning does not destroy unfinished evidence. 32. As a user after a repair rerun, I want no duplicate or fabricated derived history, so that repair is safe to retry. 33. As a user with old valid summaries, I want incomplete reconstruction to preserve those summaries, so that a repair does not replace valid history with less evidence. 34. As a user during a deliberate pause, I want paused time excluded from the usage habit, so that deliberate disables do not dilute projection evidence. 35. As a user whose counter interval crosses a pause, I want ambiguous paused bytes excluded from monitored totals, so that monitored usage is not overstated. 36. As a user whose collector was externally stopped, I want the gap treated as unexplained in-period time, so that it is not mistaken for a deliberate pause. 37. As a user after a controller reset, I want prior same-drive habit evidence browsable and handled under the existing re-warm gate, so that reset handling does not erase valid history. 38. As a user after a controller replacement, I want prior identity quarantined from projection horizons, so that a different drive never contributes to this drive's projection. 39. As a user with legacy day-only summaries, I want those summaries used only at their actual precision, so that Fenris does not invent local-day, subday, or partial-window evidence. 40. As a TUI user, I want a writes-only daily bar graph by default, so that the main graph tracks the metric the endurance projection consumes. 41. As a TUI user, I want 7-, 14-, 28-, and 90-day graph ranges with 14 days as default, so that history browsing and projection horizons use shared vocabulary. 42. As a TUI user, I want to select a day and drill into hourly bars, so that I can inspect the detail behind a daily total. 43. As a keyboard user, I want arrows, Enter, Esc/Backspace, and range-number keys to control the graph, so that the graph is fully navigable without a mouse. 44. As a mouse user in a Textual terminal, I want clicking a bar to select or drill, so that direct manipulation works where the framework supports it. 45. As a TUI user, I want the graph legend to distinguish allocated writes, unallocated writes, gaps, measured zero, partial days, and selection, so that graph shape never fabricates evidence. 46. As a TUI user, I want selected-day readout to show totals, evidenced hours, unallocated usage, coverage, and partial elapsed facts, so that the graph can be audited from text. 47. As a user on an 80×24 terminal, I want the default graph and hourly drill-down to fit, so that the polished UI remains usable in a common small terminal. 48. As a user below 80×24, I want a textual history summary and `graph needs ≥80×24`, so that the UI degrades honestly instead of showing stale or broken graph state. 49. As a TUI user, I want Amber, Nord, and High Contrast presets, so that the dashboard can match my readability preferences. 50. As a TUI user, I want Amber to be the default with an amber graph, so that the approved visual identity appears without setup. 51. As a TUI user, I want preset and reduced-motion preferences stored per unprivileged user, so that my display preferences survive restarts without affecting collection or projection. 52. As a CLI user, I want TUI display preferences to have no effect on `fenris status`, so that CLI facts remain objective. 53. As a TUI user, I want `t preset` and `m motion` controls with accessible clickable equivalents, so that changing display preferences is discoverable. 54. As a TUI user, I want vendor wear under Drive health, so that vendor-reported wear appears with other drive-health facts rather than settings. 55. As a TUI user, I want Settings to remain read-only and limited to device, baseline/provenance, retention, and display preferences, so that operational configuration is not mixed with health facts. 56. As a user without an endurance baseline, I want a clear withheld-estimate reason, so that I know what fact is missing before a lifespan estimate can appear. 57. As a user whose drive lacks write counters, I want a clear withheld-estimate reason, so that unsupported hardware is not shown as zero usage. 58. As a user during projection warm-up, I want `Building evidence — N of 14 days observed · Q qualifying`, so that I can see evidence progress without a fabricated countdown. 59. As a user with stale evidence, I want the estimate frozen at the last evidence endpoint with `estimate not updating — last sample X ago`, so that aging evidence is clear. 60. As a user with graph data before projection readiness, I want the graph to render while lifespan remains withheld, so that first data is not hidden behind the confidence gate. 61. As a user with a 7-day horizon cut by an ambiguous positive interval, I want that rate withheld while independent horizons remain visible, so that unavailable data does not erase valid data. 62. As a user with a zero-delta interval crossing a boundary, I want exact zero evidence used where justified, so that zero counter evidence is not treated like missing evidence. 63. As a user viewing scenario ranges, I want horizons anchored at the latest published usage-evidence endpoint, so that merely refreshing the dashboard does not dilute rates. 64. As a user with unknown daily shares, I want Supported confidence blocked where burst or habit checks cannot be established, so that confidence never silently assumes missing evidence passed. 65. As a maintainer, I want no plotting dependency added for the adopted graph, so that the implementation stays within approved dependencies. 66. As an implementer, I want direct observable proof paths, so that completion is based on behavior, not only a successful build. ## Implementation Decisions - Implement against the approved companion specification and TPH-1 through TPH-11 acceptance amendments. These newer criteria supersede only the old header/credit placement and refine status/history/projection behavior; they do not weaken dashboard continuity, paused-state, quit, auth, CLI parity, release-notes, or existing ADR contracts. - Keep the frozen redesign spec as historical baseline. The implementation may bring the approved companion spec, glossary amendments, and acceptance criteria into the implementation branch, but production behavior is governed by the approved handoff, not by re-opening Wayfinder decisions. - Preserve the existing architecture: privileged collection writes the observation store; TUI and `fenris status` are read-only; projections are recomputed on read; systemd and polkit remain the coordination and elevation surfaces. - Extend the observation-store model so retained usage intervals, byte-allocation completeness, unallocated usage, local-display-day browsing, partial represented spans, and projection numerator availability can be represented without interpolation. Use forward-only migrations and defensive newer-schema behavior consistent with existing store rules. - Keep full raw-sample retention at 14 days, but add the approved boundary-anchor exception so evidence needed for unfinished derivation is not pruned prematurely. Hour observations, day aggregates, and retained usage intervals remain long-lived observation history. - Make the collector own sample → usage interval → hour observation/day aggregate derivation. A successful collection run publishes a consistent validated result before reporting success; if derivation or validation fails, it writes no partial dependent history and preserves prior valid history. - Preserve measured interval totals exactly once. Do not proportionally split writes across hours or calendar days, assign them to endpoints, double-count interval totals and summaries, or convert missing evidence to zero. - Treat deliberately disabled time as outside the usage habit, not as an hour state. Intervals crossing a pause preserve original evidence but do not supply monitored totals unless the monitored share is established. - Keep controller segment boundaries hard: no deltas across reset/replacement boundaries; same-identity reset keeps eligible prior habit evidence subject to re-warm; identity change quarantines previous identity from every projection horizon. - Use the user's current system timezone only for browsing and labelling local display days. Projection evidence remains UTC. Regrouping after timezone changes must not rewrite measured history. - Update projection accounting so 7/28/90-day scenario windows end at the latest published usage-evidence endpoint and start at exact elapsed-second offsets. Do not round starts to UTC midnight and do not advance denominators merely because the TUI refreshes. - Withhold a rate when the monitored numerator is unavailable. Distinguish unknown rate from zero measured rate. Keep the endurance formula, cumulative endurance consumption operand, numeric thresholds, baseline tiers, confidence categories, and collector cadence unchanged. - Treat coverage and byte-allocation completeness as independent axes. A provisional UTC date can qualify by elapsed monitored coverage, but unknown daily totals cannot establish habit change, cannot pass burst/concentration checks, and cannot produce Supported confidence where required checks are unevaluable. - Implement the warm-up contract: first lifespan estimate after 14 represented UTC dates in the current controller segment with at least 12 qualifying; Supported still requires 14 qualifying dates and all other prerequisites. - Centralize the status lattice in the shared status composition layer consumed by TUI and CLI. The states are Monitoring, Collecting, Paused, Waiting, Interrupted, Error, Stale, and Unknown, with the approved precedence and Collecting overlay behavior. - Keep freshness, last outcome, boot enablement, and collection activity as separate facts. Use lightweight unit-state polling for status-strip transitions while keeping full store refresh on the existing cadence. - Implement the titlebox and maker-credit changes exactly: top titlebox with wolf/fallback, no service-strip maker credit, lifespan headline remains data rather than app title. - Move vendor wear to Drive health and keep Settings read-only for device selector, endurance baseline/provenance, retention facts, and display preferences. Do not add a custom colour editor. - Replace the old write-history sparkline with the approved writes-only daily bar graph and hourly drill-down. Implement it as a custom block-glyph renderable with keyboard and mouse navigation; do not add a plotting dependency. - Preserve global `p`, `r`, `c`, `d`, and `q` behavior. Add graph focus controls for selection, drill-down, back, and ranges. Add `t preset` and `m motion` controls with clickable equivalents where Textual supports them. - Implement the graph's state legend and readouts so allocated writes, unallocated measured writes, gaps, measured zero, partial days, and selection are textually distinguishable. - Implement 80×24 as the minimum graph layout target and a below-80×24 textual fallback. Resizing must clear hidden graph state rather than leaving stale visuals. - Add Amber, Nord, and High Contrast presets. Themes style chrome, graph, borders, accents, and muted text; status semantic colours/glyphs/text override theme styling. - Persist preset and reduced-motion choices as user-scoped TUI preferences in the approved XDG user config location. The file must not affect collection, projection, history evidence, helper state, package config, or CLI status. - Retain all prior dashboard clarity behavior except the explicitly superseded identity placement: continuity wording, paused block, quit rail, footer ownership, polkit banner, release notes, and TUI/CLI parity remain binding. ## Testing Decisions - Good tests assert external behavior: rendered TUI text/visible graph state, rendered `fenris status` output, observation-store rows visible to readers, and projection/status facts from fixture stores. Avoid tests that couple to widget internals, private helper layout, or implementation-only constant names when user-visible behavior can be asserted instead. - Primary seam: a synthetic observation store, fake unit/service state, and frozen clock flow through the shared status/history/projection composition into both the TUI and CLI status surfaces. This is the highest-value seam because it covers most user-visible behavior and enforces TUI/CLI parity. Prior art exists in the headless TUI tests, acceptance sweep, and status tests. - Collector publication seam: fake acquisition data and controlled clock feed a collection run, then a read-only connection observes samples, usage intervals, hour observations, day aggregates, freshness, and graph/projection facts. This covers the new publication invariant that collection success means dependent history was durably derived. Prior art exists in collector, monitoring-period, day-aggregate, legacy-migration, pruning, and store-migration tests. - Projection/evidence seam: fixture observation stores exercise exact evidence endpoints, unavailable numerator cases, qualifying-day progress, warm-up versus Supported, segment quarantine, legacy actual-precision summaries, and zero-versus-unknown rates through the public projection result and rendered reason lines. Prior art exists in projection and acceptance-sweep tests. - TUI interaction seam: headless Textual Pilot drives graph focus, range switching, day selection, hourly drill-down, back navigation, theme switching, reduced-motion toggle, titlebox rendering, and resize behavior. Assert visible text, selected readouts, and action results, not internal widget structure unless no user-visible seam exists. - CLI parity seam: render `fenris status` from the same synthetic states used by TUI tests and assert the approved status vocabulary, glyphs, precedence, reason lines, and absence of TUI-only title/preferences. Prior art exists in the current parity sweep and status render tests. - Preference seam: run the TUI under a temporary XDG config home, change preset and reduced-motion settings through public controls, restart, and verify persistence. Separately assert that CLI status and collector behavior are unchanged by preferences. - Store-fault/newer-schema seam: use malformed and newer-version stores to verify that store-dependent views are suppressed and the approved error wording appears in TUI and CLI. Prior art exists in store-fault and newer-schema tests. - Constrained-terminal seam: run headless terminal captures at 80×24 and below 80×24 to verify graph fit/fallback and preserved title/status/health/action context. - Direct proof examples from the companion spec should become fixture cases: first sample, first compatible pair, measured zero, cross-hour interval, cross-local-midnight interval, pause-crossing interval, stale frozen estimate, 12 qualifying plus 2 poor dates, unknown daily shares, controller reset/replacement, trusted legacy day-only summary, and an old-enough but boundary-cut horizon. - A build alone is not completion. The proof must include behavioral tests at the seams above plus the smallest necessary system probes for live systemd/polkit behavior already covered by existing acceptance categories. ## Out of Scope - Re-opening the approved Wayfinder decisions or changing the approved companion specification during implementation. - Editing the frozen redesign spec as part of implementation, except to carry the approved companion/criteria/glossary assets forward if the execution branch needs them. - Changing lifespan mathematics, numeric confidence thresholds, baseline precedence, controller-segment identity semantics, or collector cadence beyond the explicit UTC-accounting/evaluability amendments. - Fabricating, interpolating, proportionally splitting, endpoint-assigning, or zero-filling missing history. - Adding a read-throughput graph selector, custom colour editor, web GUI, alerts, notifications, telemetry, automatic vendor-data fetching, or broader application redesign. - Adding a plotting dependency for the usage-history graph. - Package publishing, release creation, installation-channel changes, or changelog/release-notes work beyond whatever ordinary implementation bookkeeping requires. - Runtime diagnosis of any deployed Fenris store or host. Source-level defects may be fixed, but this spec does not claim a deployed diagnosis. ## Further Notes - Approved handoff: [Approve the Fenris TUI polish specification and handoff](https://git.bongbetic.com/xavierk/Fenris/issues/70#issuecomment-5627). - Parent map: [Fenris TUI polish and hourly history](https://git.bongbetic.com/xavierk/Fenris/issues/65), closed as destination reached. - Approved asset branch: [wayfinder/tui-polish-hourly-history-spec](https://git.bongbetic.com/xavierk/Fenris/src/branch/wayfinder/tui-polish-hourly-history-spec), commit `d894ae2`. - Companion specification asset: [Fenris TUI polish and hourly history companion specification](https://git.bongbetic.com/xavierk/Fenris/src/branch/wayfinder/tui-polish-hourly-history-spec/docs/spec/fenris-tui-polish-hourly-history.md). - Acceptance amendments are TPH-1 through TPH-11 on the approved asset branch. Use the repository glossary vocabulary from the approved branch, especially Usage interval, Unallocated usage, Local display day, Byte-allocation completeness, and Qualifying day. - Current source inspection during planning found that normal collection writes samples without the complete derivation publication path and that projection/day aggregation currently lack the approved completeness-aware accounting. Those are implementation obligations, not runtime claims.
xavierk added the ready-for-agent label 2026-09-13 20:50:51 +00:00
Author
Owner

Resolution

Code review and targeted edge-case testing completed.

Standards review: 0 hard violations, 3 judgement-call smells (tui.py size, glyph duplication, dict returns). All within acceptable bounds for a Textual app.

Spec review: ~55/66 user stories verified implemented. Remaining ~11 edge cases now covered by new tests:

  • TPH-#34-36: Pause-crossing intervals preserve evidence correctly
  • TPH-#59: Staleness fact present when evidence is old
  • TPH-#62: Zero-delta intervals distinguished from gaps
  • TPH-#63: Horizon rates anchored at evidence endpoint
  • TPH-#64: Habit change detection with sustained rate change

Test results: 792 passed, 43 skipped. No regressions.

All acceptance criteria from the approved companion specification are now verified.

## Resolution Code review and targeted edge-case testing completed. **Standards review**: 0 hard violations, 3 judgement-call smells (tui.py size, glyph duplication, dict returns). All within acceptable bounds for a Textual app. **Spec review**: ~55/66 user stories verified implemented. Remaining ~11 edge cases now covered by new tests: - TPH-#34-36: Pause-crossing intervals preserve evidence correctly - TPH-#59: Staleness fact present when evidence is old - TPH-#62: Zero-delta intervals distinguished from gaps - TPH-#63: Horizon rates anchored at evidence endpoint - TPH-#64: Habit change detection with sustained rate change **Test results**: 792 passed, 43 skipped. No regressions. All acceptance criteria from the approved companion specification are now verified.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: xavierk/Fenris#72