utils/unified-design-system/DOCS-nielsen-audit.md
Krzysztof kuhy Rudnicki 7096c102cd
Some checks are pending
crdt_sync_dart tests / test (push) Waiting to run
crdt_sync_flutter tests / test (push) Waiting to run
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (push) Waiting to run
dependency freshness / dependencies on newest stable (push) Waiting to run
design_system tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
freedays pre-commit / pre-commit (push) Waiting to run
freedays tests / test (push) Waiting to run
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (push) Waiting to run
github_device_auth tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
music_theory tests / test (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
sync_settings_ui tests / test (push) Waiting to run
web_ui tests / test (push) Waiting to run
Re-point paths for the ~ reorganisation (home-tidy)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
2026-09-11 18:45:55 +02:00

155 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Nielsen coverage audit
Jakob Nielsen's ["10 GUI Design Elements Build Every User
Interface"](https://jakobnielsenphd.substack.com/p/gui-widgets) argues that
essentially every UI is assembled from ~10 standardized elements, and that the
value comes from *reusing* those conventions rather than re-inventing them.
Re-implementing the same button in eight repos is the private version of the
convention erosion he describes.
This file is the **audit grid**, not a build list. Shipping a widget nobody
imports is the failure mode to avoid — 183 imported agent definitions were
invoked zero times in 432 sessions. A component gets built only where **two or
more repos already contain a structurally similar implementation**.
Written 2026-08-14, alongside `design_system` v0.1.0.
**Phase 3 (web, `@kuhyx/web-ui`) is recorded in `phase3-record.md`** — including
why `FilterBar` was *not* extracted (structurally disjoint despite the shared
name) and the new categorical ramp. Read it before re-proposing any web
component.
## Scope
Fourteen Flutter repos, seven web repos, four Python/Tk guard apps. Out of
scope, verified rather than assumed: Unity (zero `.uxml`/`.uss` files exist
anywhere), games (their repeated identifiers are game constants like
`FORGE_MAX_C`, not UI), and the locker gate windows (their X-grab/input-hijack
invariants make the *window* non-reusable, though its leaf widgets are fair
game).
`~/src/testsAndMisc/pomodoro_app` and `horatio` no longer exist. **`workout_app`
does not exist either** — it is named in the source plan and in several skill
docs, but no directory under `~` matches `*workout*`. Anything citing it as a
consumer is stale.
## The grid
| # | Nielsen element | Flutter | Web | Python/Tk |
|---|---|---|---|---|
| 1 | Buttons | ⚠️ themed only | ❌ 7 vocabularies | ⚠️ 4 factories, see Phase 2 |
| 2 | Input fields / forms | ✅ `inputDecorationTheme` | ❌ | ⚠️ |
| 3 | Menus | ❌ not specced | ❌ | ❌ |
| 4 | Links | ❌ not specced | ❌ | n/a |
| 5 | Dialogs | ✅ `confirmDestructive()` | ❌ | ⚠️ |
| 6 | Alerts / notifications | ✅ `showToast`/`showError` | ❌ | ❌ |
| 7 | Icons | ❌ not specced | ❌ | ❌ |
| 8 | Checkboxes / radio buttons | ⚠️ themed only | ❌ | ❌ |
| 9 | Tabs | ❌ not specced | ❌ | ❌ |
| 10 | Search | ❌ not specced | ⚠️ `FilterBar` ×2 | n/a |
`components.html` specs 9 components but says nothing about **menus, links,
icons, radio buttons, tabs, or search** — six of Nielsen's ten. That is the
documentation gap; whether any of them deserves *code* still depends on the
two-repo extraction criterion, which most of them currently fail.
## What v0.1.0 closed
| Export | Replaced |
|---|---|
| `AppPalette` + scales | 10 divergent `theme.dart` copies; ~318 raw color literals |
| `buildLightTheme` / `buildDarkTheme` | the same, per repo |
| `AppStatusColors` | 4 hand-rolled `ThemeExtension`s with inconsistent fields |
| `confirmDestructive()` | ~35 inlined `AlertDialog`s |
| `showToast` / `showError` | ~38 raw `SnackBar` sites, zero helpers |
| `EmptyState` | 4 variants, one reusable |
| `SectionHeader` | 3 near-identical private widgets |
Proof consumers `todo` and `untools` are migrated, verified and pushed.
### Which exports have a live consumer
An extracted component with no importer is the failure mode this audit
exists to prevent, so the honest status per export:
| Export | Live consumers |
|---|---|
| tokens, palette, themes | todo, untools |
| `confirmDestructive` | todo (2 sites) |
| `showToast` / `showError` | todo (3 sites) |
| `SectionHeader` | untools (2 sites) |
| `EmptyState` | **none yet** — donor is home_inventory (3 sites), unmigrated |
| `AppStatusColors` | **none yet** — 4 donors, all unmigrated |
Palette/token consumers beyond the two proof apps, added the same day:
`focus_owner`, `kuhylog`, `epopeja_karta` (tokens only) and `billsplit`.
The last two ship ahead of their consumers. That is defensible (both have
real donors waiting, and `AppStatusColors` must exist for `showError` to read
the danger hue off the theme) but it is not *validated*, and it should be
called that way until home_inventory or one of the status-color donors
migrates.
### Corrections to the original plan, found by measuring
The plan's figures were mostly right but three were not, and the difference
changes what is worth doing next:
- **The "343 raw literals" are not spread through call sites.** They are
concentrated in the hand-copied theme files. todo had 35, *all 35* inside
`theme.dart`; untools had 36, of which 35 were in `theme.dart` and the last
was `Colors.transparent` in `keyboard.dart` — a sentinel, not a palette
value. Deleting the theme file removes essentially all of them. There was no
call-site sweep to do in either proof consumer, and the same is true of
home_inventory, habit_stack, diet-guard, wake-alarm, macro-cam and kuhylog,
which all have **zero** literals outside their token file. The repos with
genuine call-site leakage are `billsplit` (16, all ad hoc `Colors.red`/
`teal`/`black54`), `epopeja_karta` (14, palette inlined in `app.dart`) and
`focus_owner` (8).
- **There are 4 `AppStatusColors`, not 5**, and they disagree about their own
shape: home_inventory and diet-guard have `success` + `warning`, habit_stack
has `success` only, wake-alarm has both; home_inventory and habit_stack ship
`light` *and* `dark` statics, the other two `dark` only. The package's
version is a superset (`success`/`warning`/`danger`/`info`/`onStatus`), so
every consumer can adopt it without losing a field.
- **There are 3 section headers, not 5**, and no `_SectionLabel` exists. Two
agreed on `titleMedium` and were reconciled into `SectionHeader`;
`epopeja_karta`'s is a *different element* — `labelSmall` + primary color +
letter-spacing, i.e. an eyebrow, not a section title. It was deliberately
not folded in.
## Deferred clusters
Actionable later; each line is repo + path + size, so none of this needs
rediscovering.
### Flutter — large reconciliations
| Cluster | Where | Size |
|---|---|---|
| `GitHubMirrorScreen` ×5 | todo, diet-guard, +3 | ~1551 lines, 586 differing between two copies |
| `SettingsScreen` ×6 | fleet-wide | — |
| List tiles | ~20 classes fleet-wide | — |
| Filter sheets | todo, dufs-cloud, +1 | **name collision, not duplicates** — dufs-cloud's uses no chips at all; judge structurally |
| Steppers, badges | fleet-wide | — |
The five `GitHubMirrorScreen` copies each carry a near-verbatim "kept
app-local rather than folded into `sync_settings_ui`" comment. That decision
deserves revisiting **on its own**, not smuggled into a component extraction.
### Shipped work
Palette drift, Phase 2 (Python/Tk) and Phase 3 (TypeScript) are all done. The
decisions behind them — including two deliberate exceptions that must not be
"corrected" back, and the remaining Flutter adopters — are in
[`shipped-record.md`](shipped-record.md).
## Standing risks
- **Version churn.** Each new tag needs a `pubspec.yaml` bump in every
consumer. `habit_stack` is already **7 minor versions stale** on `crdt_sync`
(v0.3.0 vs v0.10.0); the same drift will hit this package.
- **Reconciliation is judgement.** Where copies diverged, a "best-of" choice
can regress a consumer that depended on its local variant. Migrating proof
consumers first exists to surface that early.