Skip to content

Refactor bucket from audit (#531): parser decomposition, ViewFileService split, controller/config decoupling#543

Open
nitrobass24 wants to merge 6 commits into
developfrom
refactor/refactor-bucket-531
Open

Refactor bucket from audit (#531): parser decomposition, ViewFileService split, controller/config decoupling#543
nitrobass24 wants to merge 6 commits into
developfrom
refactor/refactor-bucket-531

Conversation

@nitrobass24
Copy link
Copy Markdown
Owner

Fixes the 🏗️ Architecture / refactor bucket of tracking issue #531 (3 issues) — the final audit bucket. All changes are strictly behavior-preserving: existing test assertions pass unchanged; new focused tests cover each extracted unit.

Note

Intentional multi-issue grouping (per the maintainer's request). #524 and #525 are large architectural refactors done as contained, behavior-preserving subsets, with the deeper splits tracked as follow-ups (#541, #542) — a smaller correct refactor beats a risky big one.

How this was built

A multi-agent workflow (investigate → sequential implement → adversarial review) with the verify phase explicitly checking behavior preservation and that no existing test was weakened to make the refactor pass. I then ran the full Python + Angular suites and pyright/tsc myself and fixed the gaps the agents' ruff/ng test runs missed (below).

Issues

# Stack What landed
#523 Python Decomposed the 412-line __parse_jobs (complexity 48) into focused _parse_*/_skip_* helpers driven by a thin dispatch loop, and hoisted the ~17 per-call-compiled regexes to module constants. Every function ≤ 12 (__parse_jobs now ~5; # noqa: C901 removed). Byte-identical parse output.
#524 Angular Extracted ViewFileSelectionService (selection/check/shift/range) and a tested view-file-capabilities.ts (one source of truth for status→capability). ViewFileService slimmed accordingly.
#525 both Moved Command/Action/ICallback/CommandProcessWrapper + the concurrency cap to a shared commands.py (removed the lazy import + controller_cls plumbing); extracted sync_persist into an injected PersistSync; moved option disable-rule metadata onto the option definitions; made config access genuinely typo-safe.

Review findings → resolution (the parts I fixed after the agents)

  • Refactor: decompose LftpJobStatusParser.__parse_jobs (412 lines, complexity 48) #523 pyright: the new helpers lacked param type annotations — CI's strict pyright would fail. Annotated them (re.Match[str], etc.). The agents had run ruff (which doesn't type-check), not pyright.
  • Refactor: backend Controller/CommandPipeline coupling & frontend service conventions #525 AC#4 was a type-safety regression: the agent removed the as unknown as casts by adding index signatures (Config extends Record<…>), which collapses keyof to string and lets typos compile — the opposite of the goal. I removed the index signatures so ConfigValuePath is a real typo-checked union, with a single localized cast at the genuinely-dynamic sites. This surfaced a latent bug: the options list references lftp.remote_python_path (a real backend key) that the Lftp model never declared — now added.

Deferred → follow-ups

Test plan

  • ✅ Python: 900 unit tests pass (the lone failure, test_scan_file_with_latin_chars, is a pre-existing macOS-only Errno 92 that passes on CI's Linux runner). ruff check + C901 (≤12) clean; pyright (strict) clean across controller/, lftp/, web/.
  • ✅ Angular: 540 tests pass (41 files; +new specs for capabilities, selection, commands, persist-sync, disable-rules); ng lint --max-warnings 0 + tsc clean.
  • No existing test assertion was modified to make a refactor pass.

Closes #523. Closes #524. Closes #525.

🤖 Generated with Claude Code

nitrobass24 and others added 5 commits June 2, 2026 20:40
Hoist the 17 per-call-compiled regexes in __parse_jobs to module-level
re.compile() constants (compiled once at import, not per parse() call) and
split the ~410-line, complexity-48 method into focused helpers driven by a
thin ordered dispatch loop:

- _parse_pget_header_block, _parse_mirror_header, _parse_mirror_fl_header,
  _parse_filename_chunk, _build_chunk_transfer_state (header/data parsing)
- _skip_mirror_noise, _skip_chunk_header, _skip_chmod_block, _skip_noise_line
  (match-and-ignore leaf helpers)
- _is_valid_first_line, _is_orphan_progress_line (guard helpers)
- _dispatch_job_line + __parse_jobs (thin loop)

Strictly behavior-preserving: every compiled pattern is byte-identical to the
original (verified), load-bearing branch order and lookahead/pop semantics on
the shared lines list are unchanged, and the pget-vs-filename name-comparison
asymmetry is preserved. The shared regex fragment strings move to module scope
(mangling-safe) with the class attributes aliasing them, so _size_to_bytes,
_eta_to_seconds, and the out-of-scope __parse_queue are unaffected.

Every function now measures cyclomatic complexity <= 12 (max is 10); the
# noqa: C901 on __parse_jobs is removed (now 5). __parse_queue is left as-is.

The existing 66 tests pass unchanged (no assertions modified); adds 34 focused
helper tests including a compile-once identity regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…leService (#524)

Split the ViewFileService god service incrementally and behavior-preservingly:

- Extract the status->capability state machine into a pure, separately-tested
  module view-file-capabilities.ts. The four deletion/extraction allow-lists that
  were duplicated copy-paste arrays are now named constants (single source of
  truth): QUEUEABLE_STATUSES, STOPPABLE_STATUSES, LOCAL_ACTION_STATUSES (shared
  verbatim by isExtractable + isLocallyDeletable), REMOTELY_DELETABLE_STATUSES,
  VALIDATABLE_STATUSES. Pure mapState() + deriveCapabilities() reproduce the exact
  switch (incl. DEFAULT->STOPPED-when-both-sizes>0 and unknown->DEFAULT) and the
  exact capability predicates, keyed off RAW nullable sizes for isValidatable /
  validateTooltip. createViewFile now calls them and spreads the result; its
  signature, percentDownloaded math, pairName resolution, and ViewFile shape are
  byte-for-byte identical.

- Extract ViewFileSelectionService owning checkedSet, lastCheckedKey, and the
  checked$ stream. It is input-driven (callers pass display-order key lists) with
  no back-reference to ViewFileService, so the dependency stays acyclic
  (view-file-sort.service already injects ViewFileService). ViewFileService keeps
  toggleCheck/shiftCheck/checkAll/uncheckAll/checked$ as thin delegations;
  updateCheckedState still re-spreads only flipped rows (issue #521 identity);
  buildViewFromModelFiles uses selection.isChecked + selection.pruneRemoved.

ViewFileService's public API and exports are unchanged, so file-list.component,
file-options.component, view-file-sort.service and app.config need no edits.
Existing view-file.service.spec.ts passes with zero assertion changes; added
view-file-capabilities.spec.ts (34) and view-file-selection.service.spec.ts (16).

DEFERRED: the ViewFileCommandService split (createAction/queue/.../bulk*). That
slice straddles diffing-owned prevModelFiles and selection-owned check state;
a clean extraction needs a ModelFile-resolver + checked-snapshot threaded in or a
back-reference, both larger seams than a strictly behavior-preserving pass
warrants here. Recommend as a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ons (#525)

Address the four strictly behavior-preserving smells in #525:

(a) Move Command/Action/ICallback, CommandProcessWrapper, and
MAX_CONCURRENT_COMMAND_PROCESSES into controller/commands.py. Controller
re-exports them as class attributes (identity preserved), and CommandPipeline
imports them directly, removing the TYPE_CHECKING + lazy 'from .controller import
Controller' imports and the controller_cls plumbing threaded through dispatch.

(b) Extract sync_persist into a PersistSync collaborator (controller/persist_sync.py)
injected into both CommandPipeline and ModelUpdater at construction, removing the
placeholder lambda and post-hoc re-patch in Controller.__init__. ModelUpdater keeps
a thin sync_persist_to_all_builders() delegating to PersistSync.sync().

(c) Move conditional-disable metadata (disabledWhen/overrideNote) onto the option
definitions in options-list.ts; the component applies one generic applyDisableRules
transform. Static wrappers retained so existing specs pass unchanged.

(d) Type config access: ConfigValuePath is derived from the Config model, IOption.valuePath
is typed against it, and the 'as unknown as' casts in config.service.ts and
settings-page.component.ts are removed via a typed getConfigValue accessor and config
section index signatures.

(e) Documented the Angular mutating-service result/error contract in CLAUDE.md and
confirmed IntegrationsService conforms; the ConfigService/AutoQueueService tap migration
is explicitly deferred (not behavior-preserving against unchanged specs).

New focused tests: test_commands.py, test_persist_sync.py, options-list.spec.ts,
settings-page.disable-rules.spec.ts. All safety-net suites pass unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #523 decomposition added helpers without parameter type annotations; CI's
strict pyright (Python Type Check) requires them. Annotate the regex-match
params (re.Match[str]) and add an assert where the caller guarantees a non-None
match. No behavior change; 100 parser tests still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…525 review)

Review found #525's AC#4 "removed the casts" by adding index signatures
(Config extends Record<...>, sections extends ConfigSection), which collapses
keyof to string and lets typos compile — a type-safety regression, the opposite
of the AC. Remove the index signatures so Config/sections keep their specific
keys and ConfigValuePath becomes a real typo-checked discriminated union; keep a
single localized record cast at the genuinely-dynamic access sites
(ConfigService.set, getConfigValue).

This immediately surfaced a latent bug the index signature was masking: the
options list references ['lftp','remote_python_path'] (a real backend key,
config.py:268) but the Lftp model never declared it. Added the field + default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Warning

Review limit reached

@nitrobass24, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 57 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ea32d3e9-2fc8-4022-b0a7-cf0d1e3cdd9c

📥 Commits

Reviewing files that changed from the base of the PR and between ff4e4b5 and 00046d8.

📒 Files selected for processing (22)
  • CLAUDE.md
  • src/angular/src/app/models/config.ts
  • src/angular/src/app/pages/settings/options-list.spec.ts
  • src/angular/src/app/pages/settings/options-list.ts
  • src/angular/src/app/pages/settings/settings-page.component.ts
  • src/angular/src/app/pages/settings/settings-page.disable-rules.spec.ts
  • src/angular/src/app/services/files/view-file-capabilities.spec.ts
  • src/angular/src/app/services/files/view-file-capabilities.ts
  • src/angular/src/app/services/files/view-file-selection.service.spec.ts
  • src/angular/src/app/services/files/view-file-selection.service.ts
  • src/angular/src/app/services/files/view-file.service.ts
  • src/angular/src/app/services/settings/config.service.spec.ts
  • src/angular/src/app/services/settings/config.service.ts
  • src/python/controller/command_pipeline.py
  • src/python/controller/commands.py
  • src/python/controller/controller.py
  • src/python/controller/model_updater.py
  • src/python/controller/persist_sync.py
  • src/python/lftp/job_status_parser.py
  • src/python/tests/unittests/test_controller/test_commands.py
  • src/python/tests/unittests/test_controller/test_persist_sync.py
  • src/python/tests/unittests/test_lftp/test_job_status_parser.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/refactor-bucket-531

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The #525 edit left command_pipeline.py not ruff-format clean (CI's Python Lint
runs ruff format --check). Format-only; tests unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant