Skip to content

Add skin system: user-installable visual overlays for sports scoreboards - #419

Merged
ChuckBuilds merged 2 commits into
mainfrom
claude/plugin-visual-overlay-p7ure9
Jul 18, 2026
Merged

Add skin system: user-installable visual overlays for sports scoreboards#419
ChuckBuilds merged 2 commits into
mainfrom
claude/plugin-visual-overlay-p7ure9

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Pull Request

Summary

Adds a first-class skin system so users can restyle a sports scoreboard's visuals (live/recent/upcoming) without forking the plugin — the host plugin keeps doing data fetching, scheduling, caching, live-priority, and vegas mode, while a skin (~100 lines of drawing code in skins/<skin-id>/) replaces only the rendering. This is the anti-fork answer to community forks like the MLB scoreboard variant that lost duration/scheduling/vegas/caching support.

How it works: all sports scoreboards funnel rendering through three _draw_scorebug_layout call sites in src/base_classes/sports.py; those now go through _render_game(), which tries the configured skin first (ScoreboardSkin.render_<mode>(ctx, game) drawing onto a provided canvas) and falls back to the built-in layout on False, exception (3 strikes disables the skin for the session), or no skin. The existing extracted game dict is frozen as a versioned view-model contract, locked by tests. Includes a headless validator (scripts/validate_skin.py, fixtures, no hardware/network needed), a reference skin (skins/example-classic-baseball/), a web UI "Visual Skin" dropdown + GET /api/v3/skins, store support for registry entries with "type": "skin", and full docs (docs/SKIN_SYSTEM.md, docs/CREATING_SKINS.md with a ready-made Claude Code authoring prompt).

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor (no functional change)
  • Build / CI
  • Plugin work (link to the plugin)

Related issues

None — implements the "visual overlay / skin" idea for reusing plugin data + framework with custom layouts.

Test plan

  • Ran on a real Raspberry Pi with hardware
  • Ran in emulator mode (EMULATOR=true python3 run.py)
  • Ran the test suite (pytest)
  • Manually verified the affected code path in the web UI
  • Ran the dev preview server (scripts/dev_server.py)
  • N/A — documentation-only change

Details:

  • New test/test_skin_system.py: 52 tests covering discovery, manifest validation, API major-version gating, per-skin module namespacing/isolation, fallback semantics (decline / no skin / 3-strikes disable), game-dict copy protection, per-mode skin config resolution, view-model contract keys, plugin↔skin matching, schema enum injection, and the example skin rendering all modes at 128x32 / 64x32 / 128x64.
  • Full suite (EMULATOR=true pytest test/ --ignore=test/plugins): 1034 passed; the 4 remaining failures (test_save_double_sided_*, 2 state-reconciliation tests) were verified to fail on a clean checkout too — pre-existing, unrelated.
  • python scripts/validate_skin.py --skin example-classic-baseball --size 128x32 --size 64x32 --size 128x64: 9/9 renders pass; PNGs eyeballed.
  • With no skin configured, _render_game() is a straight passthrough to _draw_scorebug_layout — existing behavior verified unchanged by the suite.

Documentation

  • I updated README.md if user-facing behavior changed
  • I updated the relevant doc in docs/ if developer behavior changed (new docs/SKIN_SYSTEM.md, docs/CREATING_SKINS.md; pointers in docs/PLUGIN_DEVELOPMENT_GUIDE.md, CLAUDE.md, skins/README.md)
  • I added/updated docstrings on new public functions
  • N/A — no docs needed

Plugin compatibility

  • No plugin breakage expected
  • Some plugins will need updates — listed below
  • N/A — change doesn't touch the plugin system

The hook lands entirely in this repo's SportsCore base classes; monorepo sport plugins need zero changes (their _draw_scorebug_layout overrides become the fallback path). skin/skin_options are injected as always-allowed core properties in schema validation, so no plugin's config_schema.json needs touching. Important invariant introduced: the guaranteed view-model keys emitted by _extract_game_details_common (and sport extras) are now a published contract for skins — TestViewModelContract fails CI on renames.

Checklist

  • My commits follow the message convention in CONTRIBUTING.md
  • I read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • I've not committed any secrets or hardcoded API keys
  • If this adds a new config key, the form in the web UI was verified (the form is generated from config_schema.json)

The web UI "Visual Skin" dropdown is injected into the served schema only (SchemaManager.inject_skin_selector); validation is deliberately never enum-restricted so a config referencing an uninstalled skin stays valid and rendering falls back to built-in.

Notes for reviewer

  • Design choice — skins live in skins/, not plugin dirs: plugin reinstall/update deletes the whole plugin directory (store_manager), so skins must live outside it to survive; it also lets one skin target multiple plugins (mlb + milb).
  • Per-mode selection: "skin" accepts a plain id (all modes) or {"live": "a", "recent": "built-in"} — one skin per mode per plugin, different skins across modes/plugins are fine.
  • Known sharp edge: a skin render that blocks (rather than raises) can't be forcibly timed out in Python; mitigations are a >150 ms runtime warning, the validator's dev-time budget, and the no-I/O rules in CREATING_SKINS.md. Skins are arbitrary Python at plugin trust level — stated plainly in the docs.
  • v1 scope: sports scoreboards only; skin_runtime is deliberately sports-agnostic so a future BasePlugin opt-in (weather/music skins) is a small follow-up. No animation API in v1 (one frame per render call).
  • Second opinion welcome on: the token-based plugin↔skin matching in skin_runtime.skins_for_plugin (used only for the UI dropdown; explicit targets.plugins is available for exact matching).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LrCusPasy1qeUN5anK3aA1


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added customizable visual skins for sports scoreboards, supporting live, recent, and upcoming views.
    • Added skin selection through plugin settings and the web interface.
    • Added skin discovery, installation, removal, previews, and an example classic baseball skin.
    • Added an API endpoint for listing available skins.
  • Documentation
    • Added guides for using, creating, validating, and publishing scoreboard skins.
  • Tests
    • Added broad coverage for skin loading, rendering, fallback behavior, configuration, and validation.

Skins restyle a scoreboard's live/recent/upcoming rendering while the
host plugin keeps doing data fetching, scheduling, caching, live
priority, and vegas mode — the anti-fork alternative for users who only
want a different layout.

- src/skin_system/: ScoreboardSkin API, SkinContext (canvas + adaptive
  layout + logo/font helpers), discovery/loading runtime with API major
  version gating and per-skin module namespacing
- src/base_classes/sports.py: _render_game() seam at the three
  _draw_scorebug_layout call sites; skin-first with built-in fallback,
  3-strikes session disable, slow-render warning; per-mode skin config
- scripts/validate_skin.py: headless multi-mode/multi-size validator
  with bundled per-sport fixtures (no hardware or network needed)
- skins/example-classic-baseball/: working reference skin
- Web UI: served-schema Visual Skin dropdown (validation never
  enum-restricted, so uninstalled skins can't invalidate configs) and
  GET /api/v3/skins
- Store: registry entries with type "skin" install to skins/
- docs/SKIN_SYSTEM.md (architecture), docs/CREATING_SKINS.md (author
  guide incl. Claude Code prompt), view-model contract locked by tests

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LrCusPasy1qeUN5anK3aA1
@codacy-production

codacy-production Bot commented Jul 16, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 169 complexity · 4 duplication

Metric Results
Complexity 169
Duplication 4

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ChuckBuilds, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: edc2435c-45a4-45be-8eee-ea98819668d0

📥 Commits

Reviewing files that changed from the base of the PR and between 1bbc4fc and 813cd61.

📒 Files selected for processing (13)
  • docs/CREATING_SKINS.md
  • docs/SKIN_SYSTEM.md
  • scripts/validate_skin.py
  • skins/README.md
  • skins/example-classic-baseball/skin.py
  • src/base_classes/sports.py
  • src/plugin_system/schema_manager.py
  • src/plugin_system/store_manager.py
  • src/skin_system/fixtures/football_upcoming.json
  • src/skin_system/fixtures/hockey_upcoming.json
  • src/skin_system/skin_runtime.py
  • test/test_skin_system.py
  • web_interface/blueprints/api_v3.py
📝 Walkthrough

Walkthrough

Adds an installable scoreboard skin system with a public rendering API, runtime discovery and loading, sports display integration with fallback behavior, configuration and store support, a baseball example skin, validation tooling, fixtures, tests, and documentation.

Changes

Scoreboard skin system

Layer / File(s) Summary
Skin contract and runtime
src/skin_system/*, src/skin_system/fixtures/*
Defines SkinContext and ScoreboardSkin, discovers and validates manifests, loads isolated skin modules, matches sport targets, builds rendering contexts, and adds fixture game data.
Sports rendering integration
src/base_classes/sports.py, test/test_skin_system.py
Routes live, upcoming, and recent displays through skins, supports per-mode selection, preserves built-in fallback, copies game data, tracks failures, and disables repeatedly failing skins.
Configuration and distribution
src/plugin_system/schema_manager.py, src/plugin_system/store_manager.py, web_interface/blueprints/api_v3.py, test/test_skin_system.py
Adds skin configuration validation and selectors, skin listing APIs, registry installation and removal, and matching tests.
Example skin and validation tooling
skins/example-classic-baseball/*, scripts/validate_skin.py, test/test_skin_system.py
Adds a reference baseball skin with live, recent, and upcoming renderers plus a headless validator that renders fixtures at multiple sizes and writes previews.
Documentation and repository support
docs/*, README.md, CLAUDE.md, skins/README.md, .gitignore
Documents skin architecture, authoring, installation, validation, trust requirements, and generated render output handling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DisplayLoop
  participant SportsCore
  participant SkinRuntime
  participant Skin
  participant BuiltInRenderer
  DisplayLoop->>SportsCore: display current game
  SportsCore->>SkinRuntime: resolve and load configured skin
  SkinRuntime-->>SportsCore: return skin context and instance
  SportsCore->>Skin: render live, recent, or upcoming mode
  Skin-->>SportsCore: handled or declined
  SportsCore->>BuiltInRenderer: render built-in layout on failure or decline
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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: introducing a user-installable skin system for sports scoreboards.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/plugin-visual-overlay-p7ure9

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.

@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: 13

🧹 Nitpick comments (3)
web_interface/blueprints/api_v3.py (1)

5314-5318: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use the existing discovery cache for this read endpoint.

force_refresh=True reparses every skin manifest on each unfiltered request despite directory-mtime cache invalidation already detecting installs and removals. Call discover_skins() normally to reduce Raspberry Pi disk I/O and CPU usage.

As per coding guidelines, “Optimize code for Raspberry Pi's limited RAM and CPU capabilities.”

🤖 Prompt for 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.

In `@web_interface/blueprints/api_v3.py` around lines 5314 - 5318, Update the
unfiltered branch in the endpoint around plugin_id to call
skin_runtime.discover_skins() without force_refresh=True, preserving the
existing skin_runtime.skins_for_plugin(plugin_id) behavior for filtered
requests.

Source: Coding guidelines

test/test_skin_system.py (1)

326-337: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the extractor’s returned mapping, not its source text.

String matching can pass when a key appears only in comments or unreachable code. Invoke representative concrete sport extractors and assert GUARANTEED_KEYS against their returned dictionaries.

As per coding guidelines, tests should validate individual component behavior in isolation.

🤖 Prompt for 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.

In `@test/test_skin_system.py` around lines 326 - 337, Replace the source-text
inspection in test_extractor_produces_guaranteed_keys with direct calls to
representative concrete sport extractor implementations, then assert every
GUARANTEED_KEYS entry exists in each returned mapping. Keep the test focused on
the extractor contract and validate each concrete component’s output
independently rather than inspecting SportsCore._extract_game_details_common
source.

Source: Coding guidelines

scripts/validate_skin.py (1)

40-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add type annotations to the FixtureHost contract.

Annotate __init__, _load_fonts, _load_and_resize_logo, and _draw_text_with_outline; this shim must remain aligned with the host interface consumed by build_context().

As per coding guidelines, “Use type hints for function parameters and return values.”

🤖 Prompt for 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.

In `@scripts/validate_skin.py` around lines 40 - 84, Add type annotations to
FixtureHost.__init__, _load_fonts, _load_and_resize_logo, and
_draw_text_with_outline, covering every parameter and return value. Use types
consistent with the host interface consumed by build_context(), including
appropriate image, font, mapping, optional, and drawing-context types.

Source: Coding guidelines

🤖 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 `@docs/CREATING_SKINS.md`:
- Around line 37-55: Make the skin.json manifest example valid JSON by removing
all inline // comments from the fenced json block. Keep the manifest fields and
values unchanged so the example can be copied and parsed directly.
- Around line 78-81: Update the logo-loading guidance around ctx.load_logo and
render_live so render hooks never trigger network I/O or blocking downloads.
Require logos to be preloaded before rendering, or provide and document a
strictly cache-only helper for render-time use; remove the “auto-downloaded”
recommendation and preserve the existing cache_key guidance.

In `@docs/SKIN_SYSTEM.md`:
- Around line 24-31: Add the text language identifier to the fenced architecture
diagram in docs/SKIN_SYSTEM.md lines 24-31, the package-layout fence in
docs/SKIN_SYSTEM.md lines 100-107, and the directory-layout fence in
skins/README.md lines 6-11. Do not alter the contents of these diagrams.

In `@scripts/validate_skin.py`:
- Around line 97-102: Update parse_size to reject zero or negative width and
height, raising a clear argparse.ArgumentTypeError before the skin is loaded. In
the --options parsing/validation flow near build_context, require the decoded
JSON value to be an object/dictionary and return a clear argparse error for
arrays, strings, or other non-object values before dict(...) is called.
- Around line 191-195: Update the output reporting in the render flow around
ctx.canvas.save and the corresponding Vegas-render reporting at lines 204-206 so
paths outside PROJECT_ROOT are supported. Replace unconditional
Path.relative_to(PROJECT_ROOT) usage with a fallback that prints the original
output path when it is not repository-relative, while preserving
repository-relative formatting for paths inside PROJECT_ROOT and ensuring valid
renders are not reported as failures.

In `@skins/example-classic-baseball/skin.py`:
- Around line 21-27: Validate the accent_color option during ClassicBaseballSkin
instantiation rather than in _accent, requiring a three-channel RGB sequence
with valid channel values and rejecting malformed strings or lengths. On invalid
input, log a clear error and fall back to the default (255, 200, 0), while
preserving _accent’s use of the validated option at render time.

In `@src/base_classes/sports.py`:
- Around line 302-304: Update the card-render exception handler in the relevant
render method to increment the skin’s _skin_failures count before returning
None. Reuse the existing three-strike disablement logic and ensure these
exceptions follow the same session-level skin disabling behavior as other skin
failures.

In `@src/plugin_system/schema_manager.py`:
- Around line 389-406: Update the skin schema construction in the surrounding
schema-enhancement method to detect object-valued current_value and preserve
per-mode configurations via a oneOf schema. Support both the existing global
string selector and an object containing live, recent, and upcoming selectors,
while retaining the current global enum/default behavior for non-object values.

In `@src/plugin_system/store_manager.py`:
- Around line 2279-2284: Centralize safe skin-ID validation and path-containment
checks in the skin install/uninstall flow. In src/plugin_system/store_manager.py
lines 2279-2284, validate the registry ID before constructing the target and
require its resolved parent to equal the resolved skins directory; in lines
2346-2353, validate the manifest ID and reject registry/manifest mismatches; in
lines 2364-2369, apply the same validation before uninstalling. Fail fast on
invalid IDs or containment violations.
- Around line 2282-2285: Update the skin installation flow around target and
_safe_remove_directory to stage the replacement in a temporary sibling directory
before touching the existing installation. Perform download, manifest JSON
parsing, and API compatibility validation against the staged directory, then
atomically swap it into target only after validation succeeds. Preserve the
current installation on any failure and roll back the swap if replacement fails,
allowing non-critical installation errors to degrade gracefully.

In `@src/skin_system/fixtures/football_upcoming.json`:
- Around line 7-9: Update the upcoming fixture score data to use pregame values.
In src/skin_system/fixtures/football_upcoming.json at lines 7-9, retain the
upcoming state and reset both scores at lines 14-20 to "0"; make the same score
reset in src/skin_system/fixtures/hockey_upcoming.json at lines 14-20 while
retaining its upcoming state at lines 7-9.

In `@src/skin_system/skin_runtime.py`:
- Around line 94-117: Update the discovery cache logic in the skin discovery
function to include each existing skin.json manifest’s modification time in the
cache fingerprint, not only the skins directory mtime. Recompute and compare
this fingerprint before returning _discovery_cache entries so in-place manifest
updates trigger rediscovery while preserving cached results when neither
directories nor manifests changed.
- Around line 174-220: Update the sibling-loading flow around the alias check
and entry-point execution so cached namespaced modules are always rebound to
their sibling bare names before importing the entry point; do not skip alias
creation merely because _skin_<skin_id>_<sibling> already exists. Track each
bare-name binding that was replaced or added, then restore the original
sys.modules entries in the cleanup after entry import, while preserving cached
module reuse and existing failure cleanup.

---

Nitpick comments:
In `@scripts/validate_skin.py`:
- Around line 40-84: Add type annotations to FixtureHost.__init__, _load_fonts,
_load_and_resize_logo, and _draw_text_with_outline, covering every parameter and
return value. Use types consistent with the host interface consumed by
build_context(), including appropriate image, font, mapping, optional, and
drawing-context types.

In `@test/test_skin_system.py`:
- Around line 326-337: Replace the source-text inspection in
test_extractor_produces_guaranteed_keys with direct calls to representative
concrete sport extractor implementations, then assert every GUARANTEED_KEYS
entry exists in each returned mapping. Keep the test focused on the extractor
contract and validate each concrete component’s output independently rather than
inspecting SportsCore._extract_game_details_common source.

In `@web_interface/blueprints/api_v3.py`:
- Around line 5314-5318: Update the unfiltered branch in the endpoint around
plugin_id to call skin_runtime.discover_skins() without force_refresh=True,
preserving the existing skin_runtime.skins_for_plugin(plugin_id) behavior for
filtered requests.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10d00a56-03a2-4b72-ad49-0fcbdffa18f4

📥 Commits

Reviewing files that changed from the base of the PR and between 66f9950 and 1bbc4fc.

⛔ Files ignored due to path filters (3)
  • skins/example-classic-baseball/preview.png is excluded by !**/*.png
  • src/skin_system/fixtures/placeholder_away.png is excluded by !**/*.png
  • src/skin_system/fixtures/placeholder_home.png is excluded by !**/*.png
📒 Files selected for processing (30)
  • .gitignore
  • CLAUDE.md
  • README.md
  • docs/CREATING_SKINS.md
  • docs/PLUGIN_DEVELOPMENT_GUIDE.md
  • docs/SKIN_SYSTEM.md
  • scripts/validate_skin.py
  • skins/README.md
  • skins/example-classic-baseball/skin.json
  • skins/example-classic-baseball/skin.py
  • src/base_classes/sports.py
  • src/plugin_system/schema_manager.py
  • src/plugin_system/store_manager.py
  • src/skin_system/__init__.py
  • src/skin_system/fixtures/baseball_live.json
  • src/skin_system/fixtures/baseball_recent.json
  • src/skin_system/fixtures/baseball_upcoming.json
  • src/skin_system/fixtures/basketball_live.json
  • src/skin_system/fixtures/basketball_recent.json
  • src/skin_system/fixtures/basketball_upcoming.json
  • src/skin_system/fixtures/football_live.json
  • src/skin_system/fixtures/football_recent.json
  • src/skin_system/fixtures/football_upcoming.json
  • src/skin_system/fixtures/hockey_live.json
  • src/skin_system/fixtures/hockey_recent.json
  • src/skin_system/fixtures/hockey_upcoming.json
  • src/skin_system/skin_base.py
  • src/skin_system/skin_runtime.py
  • test/test_skin_system.py
  • web_interface/blueprints/api_v3.py

Comment thread docs/CREATING_SKINS.md
Comment thread docs/CREATING_SKINS.md
Comment thread docs/SKIN_SYSTEM.md Outdated
Comment thread scripts/validate_skin.py Outdated
Comment thread scripts/validate_skin.py Outdated
Comment thread src/plugin_system/store_manager.py Outdated
Comment thread src/plugin_system/store_manager.py Outdated
Comment thread src/skin_system/fixtures/football_upcoming.json
Comment thread src/skin_system/skin_runtime.py Outdated
Comment thread src/skin_system/skin_runtime.py Outdated
- skin_runtime: cache the entry module so the 2nd/3rd load of the same
  skin (live/recent/upcoming hosts) doesn't re-execute it with unbound
  sibling aliases; rebind cached sibling modules to their bare names
  around entry import and restore prior bindings after; include per-
  manifest mtimes in the discovery cache fingerprint so in-place skin
  updates are picked up
- sports.py: count render_skin_card exceptions toward the 3-strike
  session disable
- store_manager: validate skin ids (pattern + resolved-path containment
  in skins/), reject registry/manifest id mismatches, and stage+validate
  downloads in a temp sibling before replacing an existing skin
- schema_manager: leave the schema untouched when the configured skin
  value is a per-mode mapping (a string dropdown could overwrite it)
- validate_skin.py: reject non-positive sizes and non-object --options
  at parse time; support --output-dir outside the repo; type annotations
- example skin: validate accent_color once at load with logged fallback
- fixtures: pregame 0-0 scores in football/hockey upcoming fixtures
- api /skins: rely on the self-invalidating discovery cache instead of
  force_refresh
- docs: valid JSON manifest example, load_logo caching semantics spelled
  out, language ids on fenced blocks
- tests: view-model contract test now exercises the real extractor;
  regression test for repeated same-skin loads with sibling modules

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LrCusPasy1qeUN5anK3aA1

Copy link
Copy Markdown
Owner Author

Pushed 813cd61 addressing the review. All findings fixed as suggested except two, resolved differently on purpose:

  • ctx.load_logo cache-only (docs/CREATING_SKINS.md): kept the download-on-first-miss behavior because it's exactly what the built-in renderer does for the same game via the same SportsCore._load_and_resize_logo helper — a skin is never worse than built-in here, and making it cache-only would mean missing logos never appear under skins while appearing under built-in. Docs now spell out the semantics (first call per team may download, all later calls are in-memory cache hits) instead of the misleading "auto-downloaded" phrasing.
  • oneOf schema for per-mode skin config (schema_manager): instead of a oneOf, the dropdown injection is now skipped entirely when the configured value is a per-mode mapping. The form generator renders plain enums reliably; a oneOf risks the form writing a string over the user's mapping. Per-mode configs stay editable via the raw JSON editor, and validation already accepts both shapes.

Also of note: the sibling-module finding was a real bug (the same skin loads three times, once per mode host) — fixed by caching the entry module and rebinding cached sibling aliases, with a regression test.


Generated by Claude Code

@ChuckBuilds
ChuckBuilds merged commit cdf03fb into main Jul 18, 2026
8 checks passed
@ChuckBuilds
ChuckBuilds deleted the claude/plugin-visual-overlay-p7ure9 branch July 18, 2026 15:07
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.

2 participants