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

7.3 KiB
Raw Blame History

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 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.

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.