Compare commits

..
Author SHA1 Message Date
xavierk 2eb4c48fb6 research: evaluate Python TUI frameworks 2026-08-31 13:45:19 +05:30
2 changed files with 69 additions and 127 deletions
-127
View File
@@ -1,127 +0,0 @@
# NVMe endurance signals and projection constraints
Research for [Verify NVMe endurance signals and projection constraints](https://git.bongbetic.com/xavierk/Fenris/issues/5).
## Decision
Fenris can defensibly project **when a host-write endurance baseline would be consumed if the observed usage habit continues**. It cannot predict SSD failure.
Use lifetime **Data Units Written (DUW)** as the write counter, a model-and-capacity-specific **rated-TBW override** as the preferred baseline, and wall-clock observation history as the rate denominator. Keep **Percentage Used** as a separate manufacturer wear signal; only use it for a coarse, explicitly labeled implied baseline when no rated baseline exists. Treat **Power On Hours** as context, not elapsed calendar time. Express projection confidence as categorical evidence backed by visible facts, never as an accuracy percentage.
This decision removes two unsupported assumptions in the current calculation: inferring endurance as `capacity × 600` when Percentage Used is zero, and presenting `DUW / Percentage Used` as non-estimated endurance ([current Fenris calculation](https://git.bongbetic.com/xavierk/Fenris/src/commit/91db519148a6359f6968b758a76ce30b480aaae0/fenris.py#L261-L270)).
## What the NVMe signals support
| Signal | Standard semantics and precision | Defensible Fenris use |
|---|---|---|
| **Percentage Used** | An unsigned one-byte, vendor-specific estimate based on actual use and the manufacturer's prediction of NVM life. `100` means estimated endurance consumed but may not mean subsystem failure; values may exceed 100, and values above 254 are represented as 255. It is updated once per power-on hour while the controller is not asleep ([NVM Express Base Specification 2.0e, SMART / Health Information](https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2.0e-2024.07.29-Ratified.pdf); [official libnvme field documentation](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L88-L101)). | Preserve the raw integer. Do not clamp at 100; render 255 as `≥255%`, not an exact value. Do not call 100 a failure point. Zero is too coarse to establish zero wear or infer a baseline. |
| **Data Units Written** | A 128-bit cumulative count of host-written 512-byte data units, excluding metadata, reported in thousands and rounded upward. For the NVM command set, Write logical blocks count; Write Uncorrectable and Write Zeroes do not. Zero means the counter is not reported ([official libnvme structure and semantics](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L15-L23), [field definition](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L122-L138)). | Store the raw integer and derive `reported_host_bytes = DUW × 512,000`. Call it **reported host writes**, not physical NAND writes or exact bytes. Treat zero as unsupported/ambiguous unless later positive samples prove support. |
| **Power On Hours** | Integer power-on hours; the controller may omit time powered in a non-operational power state ([official libnvme field definition](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L159-L162)). | Display as drive context and use changes as a diagnostic. Do not use it as exact active time, idle time, powered-off time, or the denominator of a calendar-life projection. |
The SMART / Health log describes controller-level lifetime information; Fenris should therefore bind a history segment to a stable controller identity and avoid implying filesystem-level or physical-NAND-write precision ([NVM Express Base Specification 2.0e](https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2.0e-2024.07.29-Ratified.pdf)).
### DUW quantization
Let `q = 512,000 bytes`, raw counter `U_t`, and reported cumulative host writes `W_t = qU_t`. Since each cumulative endpoint is rounded upward, the reported interval delta is:
```text
ΔW = q(U_b - U_a)
```
Its error from endpoint quantization alone is less than one quantum: `|ΔW - actual interval writes| < 512,000 bytes`. Thus a zero hourly delta does not prove no writes below that resolution, and “exact bytes written” is not defensible. This bound follows directly from the standard's upward-rounded cumulative representation ([official libnvme DUW definition](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L122-L138)).
## Endurance baseline precedence
Use these sources in order:
1. **Verified rated-TBW override** for the exact manufacturer, model, and capacity, with source URL and document revision.
2. **Unverified manual override**, visibly labeled as user-supplied.
3. **Implied endurance from Percentage Used**, visibly labeled as a coarse heuristic.
4. Otherwise, **projection unavailable**. Never synthesize TBW from capacity alone.
Manufacturer TBW values are model- and capacity-specific: Samsung, for example, rates the 1 TB 990 PRO at 600 TBW and the 2 TB model at 1,200 TBW, and states that its warranty is limited by the stated period or TBW, whichever comes first ([Samsung 990 PRO data sheet, pp. 3–4](https://download.semiconductor.samsung.com/resources/data-sheet/Samsung_NVMe_SSD_990_PRO_Datasheet_Rev.1.0.pdf)). Samsung's warranty treats crossing TBW as a warranty-limit condition, not as a predicted failure event ([Samsung SSD Limited Warranty, sections A–B](https://download.semiconductor.samsung.com/resources/warranty/SAMSUNG_SSD_Limited_Warranty_English_US.pdf)). Fenris must therefore call rated TBW an endurance/warranty baseline rather than physical end of life.
Store an override as bytes plus provenance. If the input is labeled TBW, define it explicitly as decimal terabytes:
```text
E_rated = entered_TBW × 10^12 bytes
R_rated = max(E_rated - W_t, 0)
```
Keep rated-budget consumption and manufacturer Percentage Used separate; disagreement is useful evidence, not a reason to blend them into a fabricated wear percentage.
### Implied endurance constraints
Only for `1 ≤ p ≤ 254`:
```text
E_implied = 100W_t / p
R_implied = max(E_implied - W_t, 0)
```
This assumes the vendor's Percentage Used estimate is proportional to host writes, which NVMe does **not** require: the field is explicitly vendor-specific and based on the manufacturer's life prediction ([NVM Express Base Specification 2.0e](https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2.0e-2024.07.29-Ratified.pdf); [libnvme documentation](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L88-L101)). Do not compute it for 0 or saturated 255. Label it **implied from vendor wear estimate**, show few significant digits, and do not promote it until multiple wear increments make the estimate less dominated by one-percentage-point quantization. At low values it is intrinsically unstable: changing `p` from 1 to 2 halves the result.
## Usage-adjusted projection
For a selected valid wall-clock history interval from `a` to `b`:
```text
rate = q(U_b - U_a) / elapsed_wall_clock_seconds
projected_seconds = remaining_baseline_bytes / rate (rate > 0)
```
If the rate is zero, report **no finite projection from this history**, not infinity. Required wording should be equivalent to:
> Estimated time until the selected host-write endurance baseline is consumed, if future write usage resembles the observed usage habit. This is not a predicted hardware-failure date.
Use wall-clock elapsed time because powered-off and idle periods are part of the observed usage habit, whereas Power On Hours may exclude non-operational powered states ([libnvme Power On Hours definition](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L159-L162)). Deliberately disabled monitoring must be excluded or marked unknown by lifecycle records; a SMART counter pair can recover aggregate writes across a collector gap but cannot reveal when within that gap the writes occurred.
## History and changing habits
Persist interval observations rather than pretending every delta belongs to a clock-hour bucket:
- Compute deltas only within one controller-identity segment and only when DUW is monotonic. A decrease is a segment boundary or data fault, never a delta to clamp to zero.
- Preserve both endpoints, elapsed wall time, counter delta, and gap/monitoring state. Writes across a gap cannot be assigned exactly to individual hours; any proportional allocation must be labeled estimated.
- Use daily aggregates for habit evidence and retain hourly intervals for display/diagnostics. Hourly samples are time-dependent, so raw sample count is not independent evidence; NIST warns that autocorrelation can invalidate standard `s/√N` uncertainty calculations and other statistical conclusions ([NIST Autocorrelation Plot](https://www.itl.nist.gov/div898/handbook/eda/section3/eda331.htm)).
- Compare descriptive recent, medium, and longer horizons (for example 7, 28, and 90 days) and expose their projection spread as a **scenario range**, not a statistical confidence interval.
- Flag a changing habit when recent and earlier daily-rate windows diverge materially for a sustained period. Prefer the recent sustained regime for the headline projection while retaining the older regime as comparison. This is necessary because a stationary time series has stable mean, variance, and autocorrelation structure; trend, changing variance, and seasonality violate that assumption ([NIST Stationarity](https://www.itl.nist.gov/div898/handbook/pmc/section4/pmc442.htm)).
- Before treating days as interchangeable, account for weekly or other periodic patterns; NIST describes seasonality as regular periodic behavior that must be addressed in a time-series model ([NIST Seasonality](https://www.itl.nist.gov/div898/handbook/pmc/section4/pmc443.htm)).
Exact horizon lengths and change thresholds are product guardrails to validate later, not statistically guaranteed constants.
## Projection confidence
Use four categorical states:
- **Unavailable** — no applicable baseline, unsupported DUW, identity/counter discontinuity, or no positive usable rate.
- **Warming up** — too little history to represent ordinary usage cycles.
- **Limited evidence** — implied or unverified baseline, short/incomplete history, substantial horizon spread, stale observations, or a recent habit change.
- **Supported evidence** — verified baseline, multiple representative usage cycles, good interval coverage, current observations, and stable rates across relevant horizons.
Always show the contributing facts, for example:
> Supported evidence · verified manufacturer TBW · 42 calendar days · 96% interval coverage · 6 weekly cycles · recent and 28-day rates agree
Do not display “82% confidence” or “95% accurate.” NIST defines confidence level through the long-run coverage of an interval procedure, not as the probability that this particular estimate is correct ([NIST Confidence Limits](https://www.itl.nist.gov/div898/handbook/eda/section3/eda352.htm)). A future statistical rate interval would cover rate-estimation uncertainty only; it would not validate the endurance baseline or guarantee that habits remain unchanged.
## Required disclosures
1. This is an endurance projection, not a predicted hardware-failure date.
2. Percentage Used is vendor-specific; 100 means estimated endurance consumed but may not mean failure, it can exceed 100, and 255 is saturated ([NVMe definition](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L88-L101)).
3. Rated TBW can be a warranty/endurance threshold with separate time and eligibility terms, not a failure threshold ([Samsung warranty](https://download.semiconductor.samsung.com/resources/warranty/SAMSUNG_SSD_Limited_Warranty_English_US.pdf)).
4. DUW is upward-rounded host writes excluding metadata and selected commands, not exact physical NAND writes ([NVMe definition](https://github.com/linux-nvme/libnvme/blob/ad61ac8a319ad0823c1c9861eecbf66125f8b9a1/doc/man/nvme_smart_log.2#L122-L138)).
5. Projection quality depends on baseline provenance, history duration and completeness, recentness, stability, and representative usage cycles; future workload and firmware behavior remain outside the observed evidence.
6. Gaps can preserve an aggregate counter delta without preserving hourly timing; unexplained and deliberately disabled periods must be distinguished.
## Newly surfaced questions
Carry these to the next Wayfinder session rather than expanding this ticket:
- Should rated-budget and manufacturer Percentage Used projections appear side by side when they disagree?
- Which provenance fields are mandatory for a TBW override: URL, revision, model, capacity, region, and entry date?
- What exact warming-up, coverage, horizon, and changing-habit thresholds should the projection-model specification adopt?
- How should powered-off periods, deliberately disabled monitoring, and unexplained gaps be represented separately?
- Which stable controller identity prevents observation history from crossing a drive replacement?
- Should a detected recent regime automatically replace the long-term rate or require acknowledgement?
- Should the TUI expose multi-horizon scenarios only, or also a model-based statistical rate interval?
- How should unsupported DUW, saturated Percentage Used, and counter discontinuities appear in the TUI?
+69
View File
@@ -0,0 +1,69 @@
# Python TUI frameworks for Fenris
_Research snapshot: 2026-08-31. Planning only; no product implementation._
## Decision
Use **Textual** for Fenris's later TUI prototype and, if the prototype checks below pass, for the persistent TUI.
Textual is the best fit for Fenris's keyboard-first Overview, Usage History, Drive Health, and Settings views because one framework supplies flexible grid/horizontal/vertical layouts, dashboard and form widgets, background workers, and a headless interaction driver ([layout](https://textual.textualize.io/guide/layout/), [widget gallery](https://textual.textualize.io/widget_gallery/), [workers](https://textual.textualize.io/guide/workers/), [testing](https://textual.textualize.io/guide/testing/)). Its principal costs are the largest direct dependency set in this shortlist and only a compact `Sparkline` as built-in charting; detailed historical plots would need a custom widget or the first-party `textual-plotext` integration ([PyPI metadata](https://pypi.org/pypi/textual/json), [Sparkline](https://textual.textualize.io/widgets/sparkline/), [`textual-plotext`](https://github.com/Textualize/textual-plotext)).
This recommendation is **conditional on raising Fenris's Python floor from its currently documented Python 3.7+ to Python 3.9+**: Textual 8.2.8 and Urwid 4.0.13 require Python 3.9+, while prompt_toolkit 3.0.53 requires Python 3.10+ ([Fenris README](../../README.md#you-need), [Textual metadata](https://pypi.org/pypi/textual/json), [Urwid metadata](https://pypi.org/pypi/urwid/json), [prompt_toolkit metadata](https://pypi.org/pypi/prompt-toolkit/json)). If retaining Python 3.7 is mandatory, none of the current versions evaluated here qualifies.
**Fallback:** choose **Urwid** if prototype evidence shows that explicit low-level terminal/display control or a smaller direct dependency set matters more than Textual's higher-level layouts, forms, workers, and test driver ([display modules](https://urwid.org/manual/displaymodules.html), [package metadata](https://pypi.org/pypi/urwid/json)).
## Scope and method
The shortlist covers maintained, full-screen-capable Python projects with first-party application/widget documentation: Textual, Urwid, and prompt_toolkit. The evaluation uses official documentation, repository release APIs, and PyPI package metadata. Versions, Python floors, dependencies, and release activity are a point-in-time snapshot and should be rechecked when dependencies are locked.
## Comparison
| Criterion | Textual 8.2.8 | Urwid 4.0.13 | prompt_toolkit 3.0.53 |
|---|---|---|---|
| **Terminal compatibility** | PyPI classifies Linux, macOS, and Windows 10/11 support; this exceeds Fenris's Linux-only scope ([metadata](https://pypi.org/pypi/textual/json)). | Offers pure-Python raw and OS curses displays. The manual compares UTF-8, color, mouse, and external-event-loop capabilities; curses is described as broadly terminal-compatible, while raw supports 88/256/24-bit color and external loops ([display modules](https://urwid.org/manual/displaymodules.html)). | Provides full-screen applications and platform-specific POSIX/Windows test input; official input docs describe cross-platform one-key-at-a-time input and Windows behavior ([full-screen apps](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/full_screen_apps.html), [input](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/asking_for_input.html), [testing](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/unit_testing.html)). |
| **Responsive layout** | Vertical, horizontal, and grid layouts support fractional, percentage, and automatic sizing, nesting, spans, overflow, docking, and runtime layout changes ([layout](https://textual.textualize.io/guide/layout/)). | `Pile`, `Columns`, `GridFlow`, `Overlay`, `ListBox`, padding, and flow/box/fixed sizing provide procedural composition ([widgets](https://urwid.org/manual/widgets.html)). | `VSplit`, `HSplit`, `FloatContainer`, `ConditionalContainer`, and scrollable panes compose full-screen regions, but the reviewed guide does not document breakpoint-style responsiveness ([full-screen apps](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/full_screen_apps.html)). |
| **Dashboard, charts, history** | Built-ins include `DataTable`, `ProgressBar`, `Digits`, and `Sparkline`; Sparkline summarizes reactive numerical data into bars according to available widget width ([gallery](https://textual.textualize.io/widget_gallery/), [Sparkline](https://textual.textualize.io/widgets/sparkline/)). `textual-plotext` adds a `PlotextPlot` wrapper for richer plotting ([repository](https://github.com/Textualize/textual-plotext)). | Includes `BarGraph`, `GraphVScale`, and `ProgressBar`; richer history views still require composition or custom rendering ([graph widgets](https://urwid.org/reference/widget.html#graph-widgets)). | The documented reusable widget set supports text areas, buttons, frames, dialogs, and menus, but does not list a chart widget; Fenris charts would therefore be custom controls/rendering ([widget reference](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/reference.html#module-prompt_toolkit.widgets)). |
| **Forms and keyboard use** | Built-ins include `Input`, `MaskedInput`, `Select`, checkbox/radio/selection controls, switches, buttons, and text areas ([gallery](https://textual.textualize.io/widget_gallery/)). `Input` supports validation on change, blur, or submit and emits results with its messages ([Input](https://textual.textualize.io/widgets/input/)). | Supplies `Edit`, `Button`, `CheckBox`, `RadioButton`, focus handling, and keyboard/mouse event support; application code owns more form orchestration ([widgets](https://urwid.org/manual/widgets.html)). | Strong key-binding, editing, completion, history, suggestion, and validation facilities; full-screen reusable components include `TextArea` and `Button` ([input](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/asking_for_input.html), [full-screen apps](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/full_screen_apps.html)). |
| **Async/background updates** | Async workers run coroutines concurrently; thread workers cover blocking APIs. Workers support cancellation and exclusivity, while thread-originated UI updates use `call_from_thread()` or thread-safe messages ([workers](https://textual.textualize.io/guide/workers/)). | Supports Select, asyncio, Twisted, GLib, Tornado, and ZMQ event loops plus alarms and watched files; executor use varies by loop ([main loop](https://urwid.org/manual/mainloop.html)). | Uses asyncio natively and supports `Application.run_async()` inside an existing event loop ([asyncio](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/asyncio.html)). |
| **Testability** | `App.run_test()` runs headlessly and returns a `Pilot` that can press keys, click, wait for queued messages, and run at specified terminal sizes ([testing](https://textual.textualize.io/guide/testing/)). | The public manuals reviewed do not expose a comparable application pilot; tests would be built around widgets, rendering, callbacks, and event-loop seams ([widgets](https://urwid.org/manual/widgets.html), [main loop](https://urwid.org/manual/mainloop.html)). | Official testing guidance supplies platform-specific pipe input, `DummyOutput`, and app sessions for asserting results or data changes, while warning against brittle stdout-byte assertions ([unit testing](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/unit_testing.html)). |
| **Direct dependency cost** | Five required distributions: `markdown-it-py[linkify]`, `mdit-py-plugins`, `platformdirs`, `pygments`, and `rich`; syntax-tree packages are optional-extra dependencies ([metadata](https://pypi.org/pypi/textual/json)). | Two required distributions: `wcwidth` and `typing-extensions`; curses, GLib, Tornado, Trio, Twisted, ZMQ, serial, and LCD integrations are optional extras ([metadata](https://pypi.org/pypi/urwid/json)). | One required distribution: `wcwidth` ([metadata](https://pypi.org/pypi/prompt-toolkit/json)). |
| **Python floor** | `>=3.9,<4.0` ([metadata](https://pypi.org/pypi/textual/json)). | `>=3.9.0` ([metadata](https://pypi.org/pypi/urwid/json)). | `>=3.10` ([metadata](https://pypi.org/pypi/prompt-toolkit/json)). |
| **Project health signal** | Five non-draft releases from 8.2.4 through 8.2.8 were published between 2026-04-19 and 2026-06-30 ([release API](https://api.github.com/repos/Textualize/textual/releases?per_page=5)). | Five releases from 4.0.9 through 4.0.13 were published between 2026-08-14 and 2026-08-25 ([release API](https://api.github.com/repos/urwid/urwid/releases?per_page=5)). | Release 3.0.53 was published 2026-07-26, following 3.0.52 on 2025-08-27 ([release API](https://api.github.com/repos/prompt-toolkit/python-prompt-toolkit/releases?per_page=5)). |
All three show recent release activity, so maintenance does not eliminate a candidate. Textual wins on integrated application-level capabilities rather than on release recency alone.
## Fit to the four views
- **Overview:** use Textual layout containers with `Digits`, labels, progress indicators, and compact sparklines for monitoring status, usage-adjusted theoretical lifespan, projection confidence, and recent activity ([layout](https://textual.textualize.io/guide/layout/), [gallery](https://textual.textualize.io/widget_gallery/)).
- **Usage History:** prototype a `DataTable` plus `Sparkline` first. Add `textual-plotext` only if user testing establishes a need for axes, multiple series, or richer plots, keeping plotting out of the core dependency decision until then ([DataTable](https://textual.textualize.io/widgets/data_table/), [Sparkline](https://textual.textualize.io/widgets/sparkline/), [`textual-plotext`](https://github.com/Textualize/textual-plotext)).
- **Drive Health:** use a table and status/progress widgets for NVMe attributes; wording must continue to distinguish endurance projection from a hardware-failure prediction ([gallery](https://textual.textualize.io/widget_gallery/), [parent-map destination and terminology](https://git.bongbetic.com/xavierk/Fenris/issues/1)).
- **Settings:** Textual's selection, boolean, and validated text-entry controls cover a keyboard-only form without inventing controls ([gallery](https://textual.textualize.io/widget_gallery/), [Input validation](https://textual.textualize.io/widgets/input/)).
The TUI should consume snapshots from the independent collector rather than run NVMe collection in its UI loop; the ticket's parent map makes that independent-collector architecture part of the destination ([parent map](https://git.bongbetic.com/xavierk/Fenris/issues/1)). Textual workers remain useful for non-blocking database/IPC reads and cancellable refreshes ([workers](https://textual.textualize.io/guide/workers/)).
## Prototype acceptance checks
The later prototype should verify:
1. Complete keyboard-only operation and predictable focus order across all four views.
2. Reflow or deliberate scrolling at 80×24 and a representative wide terminal; Textual's test runner accepts explicit terminal sizes ([testing](https://textual.textualize.io/guide/testing/)).
3. Smooth refresh while database/IPC reads are delayed, plus clean cancellation and shutdown ([workers](https://textual.textualize.io/guide/workers/)).
4. Headless tests for view switching, settings validation, stale/error states, and collector disappearance ([testing](https://textual.textualize.io/guide/testing/), [Input](https://textual.textualize.io/widgets/input/)).
5. Readable behavior in the actual Linux terminal matrix, including SSH and tmux/screen if those are required; framework platform labels alone do not establish every terminal's behavior.
6. Usage History performance with realistic observation volume and gaps.
7. A table/text equivalent for every chart so observation gaps and projection confidence are not encoded by color or graphics alone.
8. Installation with Fenris's chosen packaging mechanism and the agreed Python floor.
## Newly surfaced questions
Do not open follow-up tickets during this research resolution; carry these into the next Wayfinder session:
- May the redesign raise Fenris's minimum Python version from 3.7 to 3.9?
- What is the minimum supported terminal contract: modern color terminals only, or also Linux virtual console, monochrome/16-color, SSH, tmux, and screen?
- Is a table plus sparkline sufficient for Usage History, or are axes, multiple series, zoom, and interval selection required?
- Should richer plotting be an optional install extra?
- What is the small-terminal policy: reflow, hide secondary content, scroll, or reject below a documented size?
- Which accessibility targets are required, including screen readers, reduced color, high contrast, and reduced animation?
- Does the TUI read observation storage directly or use an IPC snapshot API from the collector?
- Which settings are user-owned versus system/collector-owned, and which changes require privilege escalation?
- Are semantic interaction assertions sufficient, or are deterministic visual snapshots also required?