From cae0870fd0b1a35e4b6be7c8878c0a6d0732652f Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 14 Aug 2026 23:01:31 +0200 Subject: [PATCH] Add the Nielsen coverage audit alongside design_system v0.1.0 The grid maps Nielsen's 10 GUI elements to what actually exists per stack, and records the deferred clusters (GitHubMirrorScreen x5, SettingsScreen x6, ~20 list tiles, filter sheets) with repo, path and line count so none of it needs rediscovering. It also records three corrections the measurement turned up, because each one changes what is worth doing next: - The "343 raw color literals" are not spread through call sites; they are concentrated in the hand-copied theme files. todo had 35, all 35 inside theme.dart. Seven repos have zero literals outside their token file, so deleting it removes essentially all of them. The repos with genuine call-site leakage are billsplit, epopeja_karta and focus_owner. - There are 4 AppStatusColors, not 5, and they disagree about their own field sets; the package's version is a superset so nobody loses a field. - There are 3 section headers, not 5, and no _SectionLabel exists. epopeja_karta's is a different element (an eyebrow, not a section title) and was deliberately left out rather than force-fitted. Also records that workout_app, cited as a consumer in the plan and in several skill docs, does not exist on disk. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XS1m7b1piBtAL22USLW6jL --- unified-design-system/nielsen-audit.md | 201 +++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 unified-design-system/nielsen-audit.md diff --git a/unified-design-system/nielsen-audit.md b/unified-design-system/nielsen-audit.md new file mode 100644 index 0000000..54eaa7e --- /dev/null +++ b/unified-design-system/nielsen-audit.md @@ -0,0 +1,201 @@ +# 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. + +## 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). + +`~/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. + +### 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. + +### Flutter — palette drift to correct + +Standing decision (2026-08-14): every app uses **exactly** the frozen palette; +where copies disagree, the most popular wins. That is unambiguous — 9 of 10 +theme files use the gold `#B8862E` family. + +| Repo | Drift | Fix | +|---|---|---| +| `testsAndMisc/focus_owner` | **entirely off-palette**: cool `#5B9DD9` accent, `#1B1D21` field, `#D9776B` danger, `#E8EAED` text. Its doc comment claims it matches the other `com.kuhy.*` apps; it does not. Worse, the same six constants are **duplicated verbatim** in `main.dart:16-21` as `_kField`…`_kDanger` — the file says they were "lifted out of main.dart", but the originals were never deleted. | adopt `design_system`; delete both copies | +| `kuhylog` | the only repo using `ColorScheme.fromSeed` (seed `#3B82F6`, blue) — the opposite of the convention every other theme file explicitly documents. Also has `scoreColor(ColorScheme, int)`, which is `AppStatusColors`' job done as a function. | adopt `design_system` + `AppStatusColors` | +| `epopeja_karta` | no `theme.dart` at all; `ColorScheme` inlined in `app.dart:12-20` | adopt `design_system` | +| `billsplit` | `ColorScheme.fromSeed(seedColor: Colors.teal)` in `main.dart:31`, plus 16 ad hoc literals | adopt `design_system` | +| `diet-guard`, `macro-cam` | scales drifted from the six-repo consensus | adopt `design_system` | + +`focus_owner` is a live enforcement surface, so its visual change wants a +human look rather than a blind migration — it is the one item here that is +deferred for a reason beyond time. + +### Remaining Flutter adopters + +`home_inventory`, `dufs-cloud`, `habit_stack`, `diet-guard`, `macro-cam`, +`wake-alarm/phone_app`, `kuhylog`, `epopeja_karta`, `billsplit`, +`focus_owner`. Each drops its local `theme.dart` for the tag-pinned dep, as +todo and untools now do. + +### Phase 2 — Python/Tk (`~/utils/gatelock`), not started + +Not a new package: gatelock already owns the tokens (`LockConfig`), the Tk +plumbing and 4 consumers. Add composite widgets, which is where its scope +currently stops. + +1. `widgets.py::make_button` — canonical is diet-guard's + `_gatelock_buttons.py::make_button`, whose `variant` API picks text color + *from* the fill and so structurally prevents the `fg`-vs-`on_fill` contrast + bug. leetcode-guard's `_button` (no `` binding) and screen-locker's + inline `tk.Button` are the regressions being retired. +2. `widgets.py::heading()` / `row()` — from `leetcode_guard/_status_sections.py`. +3. **Export the existing `ScrollableSurface`** and delete + `leetcode_guard/status_view.py::_scrollable`, a hand-rolled reimplementation + that lost `takefocus` and the focus ring. Straight accessibility repair. +4. `widget_group.py::WidgetGroup` — the per-output fan-out reimplemented 4× + (~916 lines: screen-locker 173, leetcode-guard 105, diet-guard 309, + wake-alarm 329). Highest line-count win on this stack. + +Proof consumers: `diet-guard` and `leetcode-guard` (donor and worst offender). +Bump **only those two**; leave screen-locker and wake-alarm on their current +pin — Phase 2 is additive, and restarting live services is blast radius for no +gain. + +> **Verification gate — non-negotiable.** gatelock backs live systemd +> services. After any change: `/usr/bin/python3 -c "import gatelock"` against +> the real interpreter, not a dev venv. Do not touch the gate window's grab +> path. Then launch each gate under `xvfb-run -s "-screen 0 1366x768x24"` and +> confirm buttons render and **`` activates** them. + +### Phase 3 — TypeScript (`~/utils/web_ui`), not started + +Lowest confirmed duplication (2 components, 2 repos) and the only stack needing +a brand-new consumption mechanism, so it carries the worst effort-to-payoff +ratio — sequenced last on purpose. + +1. `tokens.css` — one `:root` block. Fixes the worst fork found: **7 repos + define 7 different vocabularies for the same palette** (`--surface-1` vs + `--panel` vs `--card`; `--text` vs `--ink` vs `--fg` vs `--bone`; spacing as + `--space-N` vs `--sp-N` vs `--sp-xs`). europe-county-map and iron-and-anvil + have **zero** custom properties. +2. `RangeSlider` — canonical is dufs-cloud's, whose pure + `fractionFromPointer(rect, clientX)` is testable without layout (jsdom has + none). awesome-mcp-explorer's copy has drifted 196 lines. +3. `FilterBar` + `filter-sort.ts` — reconcile dufs-cloud (174) vs + awesome-mcp-explorer (225). + +**Consumption mechanism (decided 2026-08-14):** a git dep +`"@kuhyx/web-ui": "github:kuhyx/utils#web_ui-v0.1.0"`, matching the Dart/Python +tag convention rather than introducing npm workspaces — but shipping a +**prebuilt `dist/`** (tsc → ESM + `.d.ts`) committed in the tag. Raw `.tsx` in +`node_modules` would need per-repo bundler config, since Vite does not +transpile dependencies by default; a committed `dist/` keeps consumers at zero +config. + +Proof consumers: `dufs-cloud` and `awesome-mcp-explorer`. + +## 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.