mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 17:33:56 +01:00
341 lines -> a 19-line index plus three topic files under docs/design-audit/: the Python/Tkinter lock UI, the Flutter workout app, and the shared gatelock library. Findings are unchanged -- same 6 entries, same three section headers, verified by counting both before and after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EveB813BQ8abAP68VSqZ5Y
147 lines
8.8 KiB
Markdown
147 lines
8.8 KiB
Markdown
## Flutter app (stronglift_replacement/workout_app/)
|
||
|
||
Design-token entry point: `lib/main.dart:68-74` declares
|
||
`ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo,
|
||
brightness: Brightness.dark), useMaterial3: true)` — a real Material 3 seed
|
||
theme exists. However, **`Theme.of(context)` / `colorScheme` is referenced
|
||
zero times anywhere in `lib/screens/` or `lib/widgets/`** (confirmed via
|
||
repo-wide grep). The theme is declared but never consumed — every one of the
|
||
24 .dart files under `lib/` hardcodes colors directly instead.
|
||
|
||
- `Colors.*` literals: **~210 occurrences** across `screens/` and
|
||
`widgets/`, 27 distinct named colors. Most common: `Colors.white` (98×),
|
||
`Colors.grey.shade800` (18×), `Colors.redAccent` (12×),
|
||
`Colors.greenAccent` (11×), `Colors.grey.shade900` (10×).
|
||
- Raw hex `Color(0xFF...)` literals bypassing `Colors.*` entirely:
|
||
`manual_workout_screen.dart:173,201` (`Color(0xFFFF4444)`) and
|
||
`manual_workout_screen.dart:238,246` (`Color(0xFF88CCFF)`) — these are the
|
||
**exact same hex values** independently hardcoded on the Python/Tkinter
|
||
side (`#ff4444` and `#88ccff`, see e.g. `_sick_dialog.py:70`,
|
||
`_manual_workout_dialog.py:58`), coincidentally kept in sync only because
|
||
no one has touched either yet.
|
||
- `fontSize:`/`fontWeight:` literals: 72 occurrences; `history_screen.dart`
|
||
alone uses 12 distinct font sizes (9, 11, 12, 13, 14, 15, 16, 17, 18, 20,
|
||
22, 26).
|
||
|
||
### Violations
|
||
|
||
- **Rule 4 (everything deliberate / single source of truth)** — `main.dart`
|
||
builds a `ColorScheme.fromSeed`, but it is dead code from a theming
|
||
standpoint: no screen or widget reads it. Every screen re-derives its own
|
||
ad hoc dark palette from `Colors.grey.shade900/800/700` plus assorted
|
||
accent colors, so a single rebrand (e.g. changing the seed color) would
|
||
require manually editing all 24 files instead of one.
|
||
- **Rule 1 (near-black/white, not pure)** — `Colors.white` (pure `#FFFFFF`)
|
||
used 98 times for primary text/icons, e.g. `home_screen.dart:113,122,128,
|
||
211,248,267`, `settings_screen.dart:231,278,430`,
|
||
`history_screen.dart:209,410`. `Colors.black` also appears at
|
||
`rep_circle.dart:76` (`Colors.black87`, near-pure) as text-on-white in the
|
||
neutral rep-circle state. None of these are the theme's actual
|
||
near-white/near-black — they're Flutter's pure-value constants.
|
||
- **Rule 2 (saturate your neutrals)** — `Colors.grey.shade900/800/700/400`
|
||
are Material's standard zero-saturation grays (equal R=G=B), used as the
|
||
entire background/surface system in place of `colorScheme.surface`
|
||
(which, being seeded from indigo, *would* be saturated). Bypassing the
|
||
theme produces exactly the generic-gray outcome the theme was set up to
|
||
avoid.
|
||
- **Rule 9 (distinct brightness values)** — `Colors.redAccent` (errors) and
|
||
`Colors.orangeAccent`/`Colors.orange.shade800` (pending/resume state) are
|
||
both used as status-signal colors of similar brightness in adjacent UI
|
||
(e.g. `home_screen.dart:222` orangeAccent for active-session text next to
|
||
`Colors.greenAccent` for success at line 200 — three status colors of
|
||
comparable weight competing in one card).
|
||
- **Rule 18 (container brightness limits, dark ≤~12%)** — The repeated
|
||
`Scaffold(backgroundColor: Colors.grey.shade900)` /
|
||
`AppBar(backgroundColor: Colors.grey.shade800)` pairing (Material
|
||
`shade900`=`#212121`, luma 12.9%; `shade800`=`#424242`, luma 25.9% — a
|
||
~13-point jump) appears in every screen's `Scaffold`/`AppBar`:
|
||
`home_screen.dart:104,106`, `settings_screen.dart:275,277`,
|
||
`history_screen.dart:206,208`, `workout_screen.dart:495,498`,
|
||
`manual_workout_screen.dart:154,156`. This exceeds the ~12% dark-interface
|
||
guideline every time it's used.
|
||
- **Rule 20 (body text ≥16px)** — Extensive small body text below 16px:
|
||
`fontSize: 9` at `history_screen.dart:612`, `fontSize: 11` (11 occurrences,
|
||
e.g. `home_screen.dart:311`, `history_screen.dart:333,486,612`,
|
||
`settings_screen.dart:388`), `fontSize: 12` (14 occurrences),
|
||
`fontSize: 13` (7 occurrences) — well under the 16px minimum for
|
||
supporting/secondary text throughout.
|
||
- **Rule 22 (button padding: horizontal ≈2x vertical)** — `home_screen.dart:
|
||
242` and `:261` set `padding: const EdgeInsets.symmetric(vertical: 14)`
|
||
on full-width `ElevatedButton`s with no `horizontal:` argument, which
|
||
defaults `horizontal` to `0.0` — the inverse of "horizontal ≈2x vertical".
|
||
- **Rule 26 (no shadows in dark interfaces)** — The app declares
|
||
`brightness: Brightness.dark` (`main.dart:71`), yet `rep_circle.dart:
|
||
105-111` adds an explicit `BoxShadow(color: Colors.black26, blurRadius: 4,
|
||
offset: Offset(0, 2))` to the rep-tracking circle — the one shadow in the
|
||
whole app, in a UI this rule says shouldn't have any.
|
||
|
||
### Not applicable
|
||
|
||
- Rule 5 (optical alignment) — standard Material icons/widgets are already
|
||
optically centered by the framework; no custom asymmetric glyphs.
|
||
- Rule 6 (letter-spacing/line-height by size) — the only `letterSpacing`
|
||
uses found (`home_screen.dart:311` → 1.1, `history_screen.dart:333` → 1.3,
|
||
`settings_screen.dart:388` → 1.4) are all on the same small `fontSize: 11`
|
||
eyebrow-label text, consistently given positive tracking as the rule
|
||
recommends for small text — no counter-example at larger sizes to check
|
||
against, so marked N/A rather than a forced Pass.
|
||
- Rule 7 (border contrast) — `Border.all(color: color.withValues(alpha:
|
||
0.4))` (`settings_screen.dart:491`) derives border color from the same
|
||
status color as the fill, by design (a tinted pill), not a
|
||
container-edge border this rule targets.
|
||
- Rule 8 (align with something else) — standard `Column`/`Row`/`ListView`
|
||
layouts; no cross-screen alignment grid to check.
|
||
- Rule 12 (order by visual weight) — screens are single-column flows
|
||
(cards stacked top-to-bottom); no left/right or center/edge weight
|
||
arrangement to evaluate.
|
||
- Rule 13 (12-column grid) — mobile single-column layouts throughout; no
|
||
multi-column grid system in use.
|
||
- Rule 14 (space between contrast edges) — cannot be verified statically
|
||
without rendering the widget tree.
|
||
- Rule 15 (closer elements lighter) — `Card`s at `shade800` sit on
|
||
`shade900` backgrounds (lighter-on-darker as they "lift"), which is
|
||
directionally correct, but see Rule 18 above for the magnitude issue.
|
||
- Rule 17 (simple on complex) — flat-colored cards/containers throughout;
|
||
nothing complex to conflict with.
|
||
- Rule 19 (outer padding ≥ inner padding) — spot-checked
|
||
`home_screen.dart:143` (`EdgeInsets.all(24)` outer) vs. its nested
|
||
`_WorkoutCard` internal padding (`EdgeInsets.all(16)`,
|
||
`home_screen.dart:188`) — outer ≥ inner, consistent with the rule; not
|
||
exhaustively checked across all 24 files.
|
||
- Rule 21 (line length ~70 chars) — Flutter `Text` wraps by default at
|
||
widget width; no unbounded-line risk equivalent to the Tk surface.
|
||
- Rule 23 (two typefaces max) — no custom `fontFamily` set anywhere; the
|
||
whole app uses the Material/system default typeface. Trivially compliant.
|
||
- Rule 24 (nest corners) — `BorderRadius.circular(8)` is reused verbatim
|
||
across unrelated containers (`home_screen.dart:301`,
|
||
`calendar_widget.dart:63`, `history_screen.dart:402,643,705,772`,
|
||
`settings_screen.dart:490,548,587`) but none of these visibly nest inside
|
||
another rounded container in the surrounding code, so there's no
|
||
outer/inner radius relationship to check.
|
||
- Rule 25 (avoid adjacent hard divides) — only one `Divider` found
|
||
(`exercise_tile.dart:115`), not adjacent to another border/divide.
|
||
- Rule 27 (don't mix depth techniques) — the app consistently uses flat
|
||
`Container`/`Card` fills with no shadow except the single instance flagged
|
||
under Rule 26; effectively one depth style (none) throughout, so no
|
||
mixing to flag.
|
||
- Rule 28 (lower icon contrast next to text) — icons consistently use
|
||
`Colors.white54`/`Colors.white38`/`Colors.white70` (already
|
||
lower-contrast than adjacent full-white/near-white text) rather than full
|
||
`Colors.white`, e.g. `exercise_tile.dart:150,160,167,177,248,252`,
|
||
`history_screen.dart:331,486,507` — this one is actually already followed
|
||
reasonably consistently. Pass.
|
||
|
||
### Notes
|
||
|
||
- Rule 4 is the dominant finding for this surface too: an unused
|
||
`ColorScheme.fromSeed` plus ~210 ad hoc `Colors.*`/`Color(0x...)` literals
|
||
means every other color/contrast rule (1, 2, 9, 18) is failing as a direct
|
||
consequence of the same root cause, not independently. Routing all 24
|
||
files through `Theme.of(context).colorScheme` (or a small shared
|
||
`AppColors`/`AppTextStyles` class) would be the highest-leverage single
|
||
fix.
|
||
- The coincidental hex overlap between `Color(0xFFFF4444)`/`Color(0xFF88CCFF)`
|
||
(Flutter) and `#ff4444`/`#88ccff` (Python/Tkinter) shows the two UI
|
||
surfaces are visually related by convention only — there is no shared
|
||
token file either could import from, on either language side.
|