Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
7.3 KiB
Nielsen coverage audit
Jakob Nielsen's "10 GUI Design Elements Build Every User Interface" 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 ThemeExtensions with inconsistent fields |
confirmDestructive() |
~35 inlined AlertDialogs |
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 intheme.dartand the last wasColors.transparentinkeyboard.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 arebillsplit(16, all ad hocColors.red/teal/black54),epopeja_karta(14, palette inlined inapp.dart) andfocus_owner(8). - There are 4
AppStatusColors, not 5, and they disagree about their own shape: home_inventory and diet-guard havesuccess+warning, habit_stack hassuccessonly, wake-alarm has both; home_inventory and habit_stack shiplightanddarkstatics, the other twodarkonly. 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
_SectionLabelexists. Two agreed ontitleMediumand were reconciled intoSectionHeader;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.
Standing risks
- Version churn. Each new tag needs a
pubspec.yamlbump in every consumer.habit_stackis already 7 minor versions stale oncrdt_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.