screen-locker/refactor_claude_todo.md
Krzysztof kuhy Rudnicki 0714c2264d Split _ui_flows.py into sick-day and lockout flow mixins
_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
2026-08-15 19:56:16 +02:00

101 lines
4.6 KiB
Markdown

# 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
<!-- BEGIN GENERATED VIOLATIONS -->
- **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.)_
<!-- END GENERATED VIOLATIONS -->
Exempt (do NOT split these):
- generated files — `*.g.dart`, `*.freezed.dart`, `*.gr.dart`, `**/l10n/generated/**`,
anything with a `GENERATED` header
- 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/*.sh` sourced by a thin entry script. Keep
`set -euo pipefail` in 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
oversized `CLAUDE.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:
1. Wire the shared gate `~/utils/scripts/check_file_length.sh` into this repo's
`.pre-commit-config.yaml` as a local hook. If the repo has no pre-commit
config, add a minimal one.
2. The hook checks **files in the commit** (not the whole tree), so unrelated
commits never break, and it **fails** — exit 1, not a warning.
3. No baseline file and no allowlist. Those are suppressions.
4. 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 --all` from 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 commit` **fail**.
- 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.