_ui_flows.py was 370 lines. The sick-day flow and the lockout/phone-penalty flow are both self-contained, so each moves to a sibling module: _ui_flows_sick.py SickDayFlowMixin (justification, countdown, debt) _ui_flows_lockout.py LockoutFlowMixin (lockout timer, no-phone penalty) UIFlowsMixin composes both rather than exposing them as separate mixins, so neither ScreenLocker nor scripts/verify_screen_fits.py changes its bases -- and ScreenLocker's ancestor count (already at pylint's max-parents limit) stays where it was. Pure move: 975 tests still pass at 100% branch coverage, pylint holds at 8.70/10, and `screen_lock --status` renders the real week unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EveB813BQ8abAP68VSqZ5Y
4.6 KiB
Refactor TODO — enforce the 250-line file cap
This file is a ready-to-use prompt. Paste it to Claude, or open this repo and say "do refactor_claude_todo". It is self-contained: everything needed to execute is below. Generated 2026-08-14 from a measured survey of every repo.
Goal
Every file in this repo must be at most 250 lines — source, tests, and
prose (.md/.txt/.rst/.tex) alike — and must stay that way forever,
enforced by a gate that fails the commit, not by a note anyone can ignore.
Why: a file that cannot be read in one piece forces re-reads and partial edits, which is the single largest avoidable cost in an LLM-assisted workflow. Aim by churn, not size alone — refactoring pays where code is read and changed often (Fowler, refactoring economic benefit).
Scope in this repo
- 41 files currently exceed 250 lines.
- 6,753 lines over the cap in total (the work left to do); longest file is 897 lines.
ROI = lines x commits in the last year. Work top-down; a long file nobody edits has near-zero payoff and should not be first.
| lines | commits/yr | kind | file |
|---|---|---|---|
| 771 | 12 | code | stronglift_replacement/workout_app/lib/screens/settings_screen.dart |
| 781 | 10 | code | stronglift_replacement/workout_app/lib/services/storage_service.dart |
| 897 | 8 | code | stronglift_replacement/workout_app/lib/screens/history_screen.dart |
| 323 | 22 | code | screen_locker/tests/conftest.py |
| 480 | 12 | code | stronglift_replacement/workout_app/test/screens/settings_screen_test.dart |
| 633 | 9 | code | stronglift_replacement/workout_app/lib/screens/workout_screen.dart |
| 740 | 7 | code | stronglift_replacement/workout_app/test/services/workout_sync_service_test.dart |
| 370 | 14 | code | screen_locker/_ui_flows.py |
| 380 | 13 | code | screen_locker/_phone_verification.py |
| 759 | 5 | code | stronglift_replacement/workout_app/test/services/storage_service_test.dart |
| 415 | 9 | code | stronglift_replacement/workout_app/lib/services/workout_sync_service.dart |
| 357 | 9 | code | screen_locker/_manual_workout_dialog.py |
| 384 | 8 | code | screen_locker/_shutdown.py |
| 383 | 8 | code | stronglift_replacement/workout_app/lib/screens/home_screen.dart |
| 396 | 7 | code | screen_locker/status_view.py |
(26 further files over 250 lines not listed — re-run python3 scripts/refresh_refactor_todo.py for the current set.)
Exempt (do NOT split these):
- generated files —
*.g.dart,*.freezed.dart,*.gr.dart,**/l10n/generated/**, anything with aGENERATEDheader - markup —
.html,.css,.scss - data files —
.json,.yaml,.csv, wordlists and other data-ish.txt(mean line length under 25 chars)
How to split
- Python — extract cohesive helpers into sibling modules; keep the public API and imports stable.
- Shell — split into
lib/*.shsourced by a thin entry script. Keepset -euo pipefailin each. - Dart / TypeScript — extract widgets/components into their own files.
- Tests — split by test-group into sibling files
(
foo.test.ts->foo.parsing.test.ts,foo.render.test.ts). Coverage must not drop. - Docs — split into topic files under
docs/with an index. For an oversizedCLAUDE.md, move detail into referenced docs so the always-loaded part shrinks.
Do not game the cap: no one-lining, no deleting tests, no moving code into
an exempt extension, no # noqa-style suppressions.
Make it permanent (required — this is the point)
A refactor without a gate silently regrows. Before this task is done:
- Wire the shared gate
~/utils/scripts/check_file_length.shinto this repo's.pre-commit-config.yamlas a local hook. If the repo has no pre-commit config, add a minimal one. - The hook checks files in the commit (not the whole tree), so unrelated commits never break, and it fails — exit 1, not a warning.
- No baseline file and no allowlist. Those are suppressions.
- If this repo has CI (
.github/workflows), add the same check there so it also fails on push.
Done condition
bash ~/utils/scripts/check_file_length.sh --allfrom this repo root exits 0.- The repo's own test suite and coverage bar are still green.
pre-commit run --files <changed files>passes.- A deliberately over-250-line test file, staged, makes
git commitfail. - For a deployed daemon/app: the entry point still actually runs.
Verify
Run the suite, then actually run the locker entry point — this repo deploys from the working tree, so a broken import is a live outage.