Skip to content

feat(cl2k): show which save locations the Poster Healer keeps up to date - #467

Merged
chodeus merged 2 commits into
developfrom
feat/poster-healer-coverage-panel
Aug 7, 2026
Merged

feat(cl2k): show which save locations the Poster Healer keeps up to date#467
chodeus merged 2 commits into
developfrom
feat/poster-healer-coverage-panel

Conversation

@chodeus

@chodeus chodeus commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

The Poster Healer already follows the CL2K maker's save locations automatically — PosterSelfHealConfig deliberately has no drive fields, and run() reads cl2k_maker.local_folders / .gdrive_uploads live on every run. But nothing surfaced that, so the only way to know what was actually covered was to read config.yml.

This adds GET /api/poster-self-heal/coverage and a read-only panel on the review page listing every scanned folder and Drive with the types it handles, plus a warning when a type reaches no Drive at all.

Related issue

N/A

Type of change

  • Bug fix (non-breaking)
  • New feature (non-breaking)
  • Breaking change (config migration, schema, endpoint, or UI behavior)
  • Docs only
  • Refactor / internal cleanup

Purely additive: one new GET, one new panel, no config schema change, no change to what the heal does.

The subtlety worth reviewing

The heal's scope is wider than the maker's routing, and the panel reports the former:

  • local_dirs_for() takes every local_folders entry with a non-empty path and ignores types. A row claiming no types saves nothing but is still scanned — and still healed.
  • drive_twins() collects every gdrive_uploads entry with a non-empty folder_id; types only decide which Drive receives which type, with a poster-Drive then first-Drive fallback.

cl2k_maker's own upload-status summary filters on and f.types. Copying that predicate here would have hidden a folder the healer really scans, so the panel would have lied. Instead the endpoint calls the module's own helpers, and local_dirs_for is extracted out of run() so the two can't drift.

Drives report heals_types — the types that actually resolve to them via twin_of — not the claimed types, so a fallback target shows what it genuinely receives.

Testing

  • ruff check . — clean
  • python -m pytest — 1571 passed (9 new)
  • npm run lint, npx prettier --check "src/**/*.{js,jsx,css,json,md}" (whole tree), npx stylelint "src/**/*.css" — all clean

New backend coverage in tests/test_api_poster_self_heal_coverage.py: the folder/Drive listing, that a types: [] folder is still listed, Drive dedupe + blank-id skipping, heals_types reflecting fallback rather than claims, unrouted_types when no Drive exists, an absent cl2k_maker returning an empty payload instead of a 500, and that config is re-read per request so a settings save needs no restart. Plus two unit tests pinning local_dirs_for's ordering/dedupe and its deliberate ignoring of types.

Rendered against a real four-way routed config, the payload is:

Kept up to date            4 folders · 4 Drives
  folder  Posters      /kometa/posters/CL2K/Chodeus                 saves ['poster']
  folder  Logos        /kometa/posters/Artwork/Chodeus/logos        saves ['logo']
  cloud   Posters      18Rv6LBp9YZWpnT2qt5wXELFNXj9GSa2s   heals ['poster']
  cloud   Logos        1NofIhs9MT8pklqmdYMwfcZBJzpTAv3-Q   heals ['logo']
unrouted_types: []

Screenshots

Not captured — the panel is a plain surface card in the existing page column (title + count pill + "Edit in Module Settings →", then one row per location with a mono path/ID and type pills). It follows the existing ReviewRow / ConfigBanner idiom rather than introducing new primitives.

Checklist

  • Config schema updated if applicable (backend/util/config.py) — N/A, reads existing cl2k_maker fields
  • Frontend builds cleanly (cd frontend && npm run build)
  • No new DAPS references introduced
  • Docs / wiki drafts updated if behavior or shape changed — endpoint added to the API module docstring
  • CHANGELOG entry added for user-visible changes — N/A, develop-only extension; release-please runs on main only

Notes for review: the endpoint is config-only and makes no rclone callslist_files() was deliberately avoided so a transient Drive/token failure can't make the panel silently report zero coverage. load_config() is called inside the handler, never bound at import, so coverage is never stale. All six touched files are develop-only, so the branch-isolation invariant holds.

Summary by CodeRabbit

  • New Features

    • Added a coverage view to the poster healer review page.
    • Displays configured local folders and Drive destinations with routed media types.
    • Highlights unrouted media types and supports unavailable or empty configuration states.
    • Links to relevant module settings for configuration updates.
    • Added live coverage reporting that reflects current configuration without performing external file operations.
  • Bug Fixes

    • Improved handling of duplicate or blank destinations and folders without assigned media types.
    • Coverage loading failures now fail gracefully without blocking the review page.

The healer follows the CL2K maker's save locations automatically — it stores no
drive config of its own — but nothing surfaced that, so the only way to know what
was covered was to read config.yml.

Add GET /api/poster-self-heal/coverage and a read-only panel on the review page
listing every scanned folder and Drive with the types it handles.

Coverage is derived from the module's own local_dirs_for/drive_twins rather than
re-deriving it in the API layer, because the heal's scope is WIDER than the
maker's routing: a location claiming no types saves nothing but is still scanned.
Filtering on types (as the maker's own summary does) would under-report. Drives
show the types that actually resolve to them, so a fallback target reports what
it really receives rather than what it claims.

Also warns when no Drive receives a type, since those files heal locally only and
anyone syncing the Drive keeps the stale names.

local_dirs_for is extracted from run() so the endpoint and the scan cannot drift.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6e8454cb-0a3a-4f9e-a883-58bd3f703bab

📥 Commits

Reviewing files that changed from the base of the PR and between 07158d3 and e69d678.

📒 Files selected for processing (2)
  • backend/api/poster_self_heal.py
  • tests/test_api_poster_self_heal_coverage.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/api/poster_self_heal.py

📝 Walkthrough

Walkthrough

Adds a live poster self-heal coverage endpoint and displays folder, Drive, routed-type, and unrouted-type data on the review page. Refactors local-folder extraction into a reusable helper and adds backend and resolver tests.

Changes

Poster self-heal coverage

Layer / File(s) Summary
Local folder resolution
backend/modules/poster_self_heal.py, tests/test_poster_self_heal_resolver.py
local_dirs_for returns ordered, nonblank, deduplicated paths. PosterSelfHeal.run() uses the helper. Tests cover duplicate paths, blank paths, ordering, and empty type lists.
Coverage endpoint and routing data
backend/api/poster_self_heal.py, tests/test_api_poster_self_heal_coverage.py
Adds GET /api/poster-self-heal/coverage. The endpoint reads live CL2K configuration, resolves Drive targets, filters invalid destinations, reports unrouted types, and returns unavailable data when CL2K is absent.
Coverage review panel
frontend/src/utils/api/posterSelfHeal.js, frontend/src/pages/poster/PosterHealReviewPage.jsx
Adds an uncached coverage request and renders folder, Drive, empty, unavailable, and unrouted-type states below the page header.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PosterHealReviewPage
  participant posterSelfHealAPI
  participant CoverageEndpoint
  participant CL2KConfiguration
  PosterHealReviewPage->>posterSelfHealAPI: Request coverage
  posterSelfHealAPI->>CoverageEndpoint: GET /poster-self-heal/coverage
  CoverageEndpoint->>CL2KConfiguration: Read live configuration
  CL2KConfiguration-->>CoverageEndpoint: Folders and Drive routes
  CoverageEndpoint-->>posterSelfHealAPI: Coverage JSON
  posterSelfHealAPI-->>PosterHealReviewPage: Display coverage data
Loading

Possibly related PRs

  • chodeus/chub#466: This PR extends the same poster self-heal Drive and image-type routing logic.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: showing Poster Healer save locations and their synchronization coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/poster-healer-coverage-panel

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/api/poster_self_heal.py`:
- Around line 83-91: Update local_dirs_for() so folders contains at most one row
per path, matching the deduplicated scanned set and scanned_count. Track emitted
paths while iterating local_folders or otherwise construct folders from unique
scanned paths, preserving the existing normalized name, path, and types fields.
- Around line 68-71: Update the configuration-loading path in the endpoint
around load_config() to catch ConfigError and return a configuration-unavailable
response with HTTP status 503, instead of allowing the global handler to produce
500. Preserve the existing cl2k_maker availability response for successfully
loaded configurations.

In `@frontend/src/pages/poster/PosterHealReviewPage.jsx`:
- Around line 77-90: Update the PosterHealReviewPage coverage effect to create
an AbortController, pass its signal through posterSelfHealAPI.coverage(options),
and abort it during cleanup while retaining the active state-update guard.
Extend apiCore.makeRequest to combine the caller-provided signal with its
timeout signal instead of replacing it, and ensure posterSelfHealAPI.coverage
forwards its options.

In `@tests/test_api_poster_self_heal_coverage.py`:
- Around line 130-138: Update test_coverage_rereads_config_each_request to have
load_config return a replaceable holder, then assign a new _Cfg instance
containing the added BGS drive before the second request instead of mutating
cfg.cl2k_maker.gdrive_uploads. Keep the existing first-request and
second-request assertions to verify the handler reads the replacement
configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20734b36-bb81-4748-910a-635820b51fe2

📥 Commits

Reviewing files that changed from the base of the PR and between c2c3ee9 and 07158d3.

📒 Files selected for processing (6)
  • backend/api/poster_self_heal.py
  • backend/modules/poster_self_heal.py
  • frontend/src/pages/poster/PosterHealReviewPage.jsx
  • frontend/src/utils/api/posterSelfHeal.js
  • tests/test_api_poster_self_heal_coverage.py
  • tests/test_poster_self_heal_resolver.py

Comment thread backend/api/poster_self_heal.py
Comment thread backend/api/poster_self_heal.py Outdated
Comment thread frontend/src/pages/poster/PosterHealReviewPage.jsx
Comment thread tests/test_api_poster_self_heal_coverage.py
Two local_folders rows sharing a path are ONE scanned location, but the
response emitted a row each — so folders could disagree with scanned_count,
and the panel keys on path. Emit the first claimer only.

Also replace the config object rather than mutating it in the reload test: a
handler that captured the config once would have passed the mutation check,
since it would be holding the very list under mutation.
@chodeus
chodeus merged commit 9bff93b into develop Aug 7, 2026
12 checks passed
@chodeus
chodeus deleted the feat/poster-healer-coverage-panel branch August 7, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant