Skip to content

fix(jellyfin-now-playing): size the progress bar to the text, not the panel - #232

Merged
ChuckBuilds merged 2 commits into
mainfrom
fix/jellyfin-content-width
Jul 31, 2026
Merged

fix(jellyfin-now-playing): size the progress bar to the text, not the panel#232
ChuckBuilds merged 2 commits into
mainfrom
fix/jellyfin-content-width

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Problem

The plugin occupied a full screen width in Vegas scroll mode regardless of how short the title was. _render_now_playing gave the text area every remaining column, then drew the progress bar across all of it:

text_x = poster_w + 2
text_w = width - text_x - 1     # all remaining width
...
bar_x2 = text_x + text_w - 1    # bar spans the whole lot

On a 512px panel a short episode name left a bar stretching across the display. And because a bar is drawn pixels rather than blank space, a ticker cannot trim it back — the plugin was contributing a full 512px per pass with most of it carrying no information.

Fix

The bar is now sized to the widest of the title and subtitle, with a 24px floor so a very short title still reads as a progress indicator.

The knock-on effect matters as much as the bar itself: the remainder becomes genuinely blank, so Vegas mode's trimming can reclaim it. On the test rig the segment went from ~512px to 106px.

Three details:

  • A marqueed title still gets a full-width bar. A title long enough to scroll measures wider than the area, so it pins the bar to full width — correct, because it genuinely fills it.
  • The poster is unaffected, since it is sized from panel height (2:3 portrait), not width.
  • Measurement failure falls back to the full-width bar. Losing the bar entirely would be worse than an over-wide one.

Set progress_bar_match_text false for the previous behaviour.

Testing

The safety harness cannot cover this, which is worth stating plainly: without a reachable Jellyfin server there is no session, so _render_now_playing never runs and the harness only ever sees the "Nothing Playing" screen. Relying on it here would give false confidence.

So this ships with focused unit tests in test_content_width.py (16 cases): widest-line selection, the floor, the area clamp, the marquee case, a missing session, empty strings, the config toggle, and measurement failure.

  • Safety harness: all sizes PASS.
  • check_module_collisions.py: OK across 41 plugins.
  • On hardware: renders and trims to 106px with no errors. In fairness, my rig had no active session while testing, so the 106px figure is the idle placeholder — the bar sizing itself is covered by the unit tests rather than live observation.

Companion lever

Core-side, ChuckBuilds/LEDMatrix#423 adds a per-plugin vegas_width_pct so a plugin can be asked to render at a fraction of the panel. Setting that to 45 for this plugin is complementary: it makes the layout compact, while this change stops the bar re-filling whatever width it is given.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ

Summary by CodeRabbit

  • New Features

    • Added an option to size the now-playing progress bar to the widest title or subtitle by default.
    • Added a setting to restore the previous full-width progress bar behavior.
  • Bug Fixes

    • Prevented short titles from causing unnecessarily wide progress bars.
    • Added minimum-width and available-space safeguards for improved display consistency.
  • Chores

    • Released Jellyfin Now Playing version 1.1.0.

… panel

_render_now_playing gave the text area every remaining column and then drew the
progress bar across all of it:

    text_w = width - text_x - 1
    bar_x2 = text_x + text_w - 1

So the rendered frame was full-width whatever the title length. On a 512px panel
a short episode name left a bar stretching across the display, and because a bar
is drawn pixels rather than blank space, Vegas mode could not trim it back — the
plugin was contributing a full screen width per pass.

The bar is now sized to the widest of the title and subtitle, with a 24px floor
so a very short title still reads as a progress indicator. That also makes the
remainder genuinely blank, so a ticker can reclaim it. A title long enough to be
marqueed measures wider than the area and still fills the bar, which is correct.

Set progress_bar_match_text false for the previous full-width bar.

The safety harness cannot cover this: without a reachable Jellyfin server there
is no session, so the now-playing frame is never rendered and the harness only
ever sees the "Nothing Playing" screen. Hence the focused unit tests in
test_content_width.py, which exercise the sizing rule directly.

Harness passes at all sizes; module collisions clean.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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: 16 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 Plus

Run ID: 5321df8c-1b8a-4a85-bfb3-cbd7766e2031

📥 Commits

Reviewing files that changed from the base of the PR and between 8b9bced and 45f0fb0.

📒 Files selected for processing (1)
  • plugins.json
📝 Walkthrough

Walkthrough

The Jellyfin Now Playing plugin adds configurable progress bar sizing based on the widest title or subtitle, enforces width bounds, updates version metadata to 1.1.0, and adds focused tests for the new behavior.

Changes

Jellyfin progress bar sizing

Layer / File(s) Summary
Configuration and release metadata
plugins/jellyfin-now-playing/config_schema.json, plugins/jellyfin-now-playing/manager.py, plugins/jellyfin-now-playing/CHANGELOG.md, plugins/jellyfin-now-playing/manifest.json, plugins.json
Adds the progress_bar_match_text setting and records the 1.1.0 release and updated plugin metadata.
Content-based progress bar rendering
plugins/jellyfin-now-playing/manager.py
Computes progress bar width from the widest title or subtitle, applies minimum and maximum bounds, and retains full-width fallback behavior.
Content width validation
plugins/jellyfin-now-playing/test_content_width.py
Tests sizing, bounds, empty or missing session data, configuration overrides, and measurement failures.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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 and accurately summarizes the main change: sizing the now-playing progress bar to text instead of the full panel.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/jellyfin-content-width

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.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 26 complexity

Metric Results
Complexity 26

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 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
plugins/jellyfin-now-playing/manager.py (2)

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

Remove the redundant int() cast.

Ruff RUF046 correctly identifies that round() with one argument already returns an integer; use fill_w = round(...) without changing behavior.

🤖 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 `@plugins/jellyfin-now-playing/manager.py` at line 545, Update the fill_w
assignment in the progress-bar calculation to remove the redundant int() cast
and assign the result of round((bar_w - 1) * progress) directly, preserving the
existing behavior.

Source: Linters/SAST tools


553-585: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the measurement fallback exception.

The fallback is useful, but except Exception can hide unrelated programming errors and silently force full-width bars. Catch a dedicated or known measurement failure, or have _text_width() return an explicit failure sentinel while preserving the existing fallback test.

🤖 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 `@plugins/jellyfin-now-playing/manager.py` around lines 553 - 585, Narrow the
exception handling in _content_width so only the known text-measurement failure
is caught, rather than masking unrelated programming errors. Use the dedicated
measurement exception from _text_width, or update _text_width to return its
existing failure sentinel and preserve the fallback to available for that
sentinel.

Source: Linters/SAST tools

🤖 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 `@plugins/jellyfin-now-playing/config_schema.json`:
- Line 51: Replace “drawn pixels” with “drawn as pixels” in the description at
plugins/jellyfin-now-playing/config_schema.json:51-51 and apply the same wording
correction at plugins/jellyfin-now-playing/CHANGELOG.md:9-9.

In `@plugins/jellyfin-now-playing/test_content_width.py`:
- Around line 19-42: Update the test module setup around make_plugin to load
this plugin’s manager.py via importlib under a unique module name/path instead
of importing bare manager. Replace the permanent sys.modules assignments for
src, src.plugin_system, and src.plugin_system.base_plugin with
patch.dict(sys.modules, ..., clear=False) scoped only around that import, then
obtain JellyfinNowPlayingPlugin from the uniquely loaded module.

---

Nitpick comments:
In `@plugins/jellyfin-now-playing/manager.py`:
- Line 545: Update the fill_w assignment in the progress-bar calculation to
remove the redundant int() cast and assign the result of round((bar_w - 1) *
progress) directly, preserving the existing behavior.
- Around line 553-585: Narrow the exception handling in _content_width so only
the known text-measurement failure is caught, rather than masking unrelated
programming errors. Use the dedicated measurement exception from _text_width, or
update _text_width to return its existing failure sentinel and preserve the
fallback to available for that sentinel.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

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 Plus

Run ID: 0267d86b-84d0-42ba-8e3e-a2662d8ad091

📥 Commits

Reviewing files that changed from the base of the PR and between 8d33894 and 8b9bced.

📒 Files selected for processing (6)
  • plugins.json
  • plugins/jellyfin-now-playing/CHANGELOG.md
  • plugins/jellyfin-now-playing/config_schema.json
  • plugins/jellyfin-now-playing/manager.py
  • plugins/jellyfin-now-playing/manifest.json
  • plugins/jellyfin-now-playing/test_content_width.py

"x-advanced": true,
"type": "boolean",
"default": true,
"description": "Size the progress bar to the widest of the title and subtitle instead of stretching it across the whole text area. On a wide panel a short title otherwise leaves a bar spanning the display, and because a bar is drawn pixels it cannot be trimmed back in Vegas scroll mode. A title long enough to scroll still fills the bar. Turn off for the original full-width bar."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the duplicated “drawn pixels” wording.

  • plugins/jellyfin-now-playing/config_schema.json#L51-L51: replace “drawn pixels” with “drawn as pixels”.
  • plugins/jellyfin-now-playing/CHANGELOG.md#L9-L9: apply the same wording correction.
📍 Affects 2 files
  • plugins/jellyfin-now-playing/config_schema.json#L51-L51 (this comment)
  • plugins/jellyfin-now-playing/CHANGELOG.md#L9-L9
🤖 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 `@plugins/jellyfin-now-playing/config_schema.json` at line 51, Replace “drawn
pixels” with “drawn as pixels” in the description at
plugins/jellyfin-now-playing/config_schema.json:51-51 and apply the same wording
correction at plugins/jellyfin-now-playing/CHANGELOG.md:9-9.

Comment on lines +19 to +42
if 'src' not in sys.modules:
src = types.ModuleType('src')
plugin_system = types.ModuleType('src.plugin_system')
base_plugin = types.ModuleType('src.plugin_system.base_plugin')

class _BasePlugin:
def __init__(self, *args, **kwargs):
pass

base_plugin.BasePlugin = _BasePlugin
plugin_system.base_plugin = base_plugin
src.plugin_system = plugin_system
sys.modules['src'] = src
sys.modules['src.plugin_system'] = plugin_system
sys.modules['src.plugin_system.base_plugin'] = base_plugin

TEXT_AREA = 300
TITLE_FONT = object()
SUBTITLE_FONT = object()


def make_plugin(title='', subtitle='', config=None, px_per_char=6, fail=False):
"""A plugin shell with a no-op __init__; only the sizing logic is exercised."""
from manager import JellyfinNowPlayingPlugin

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Isolate the plugin import from the global module cache.

Line 42 can import another plugin’s already-cached bare manager module, while Lines 19-33 permanently replace src modules for the rest of the test process. Load this plugin’s manager.py by a unique importlib module name/path and scope the fake src entries with patch.dict(sys.modules, ...) only during that import.

Otherwise collection order across plugins can make these tests exercise the wrong class or contaminate later core-plugin tests.

As per coding guidelines, top-level Python modules are loaded by bare name; tests must not rely on that shared module name across plugins.

🤖 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 `@plugins/jellyfin-now-playing/test_content_width.py` around lines 19 - 42,
Update the test module setup around make_plugin to load this plugin’s manager.py
via importlib under a unique module name/path instead of importing bare manager.
Replace the permanent sys.modules assignments for src, src.plugin_system, and
src.plugin_system.base_plugin with patch.dict(sys.modules, ..., clear=False)
scoped only around that import, then obtain JellyfinNowPlayingPlugin from the
uniquely loaded module.

Source: Coding guidelines

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Cannot run autofix: This PR has merge conflicts.

Please resolve the conflicts with the base branch and try again.

Alternatively, use @coderabbitai resolve merge conflict to automatically resolve the conflicts.

@ChuckBuilds
ChuckBuilds merged commit a0d613f into main Jul 31, 2026
4 checks passed
@ChuckBuilds
ChuckBuilds deleted the fix/jellyfin-content-width branch July 31, 2026 22:36
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