Skip to content

feat: add transparent halfcell - #311

Merged
NSPC911 merged 2 commits into
masterfrom
half-trans-cell
Jul 28, 2026
Merged

feat: add transparent halfcell#311
NSPC911 merged 2 commits into
masterfrom
half-trans-cell

Conversation

@NSPC911

@NSPC911 NSPC911 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

monkey patching with reference to my other pr lnqs/textual-image#101


by submitting this pull request, i agree that

  • i have run poe check to check for any style issues and fixed them
  • i have tested rovr (and also ran poe test if applicable) to make sure my changes do not break anything
  • cache, logs, dotfiles and/or others were not accidentally added to git's tracking history
  • my commits (or at least the pr title) follow the conventional commits format as much as possible

Summary by Sourcery

Add monkey patches to support transparent halfcell image rendering and cover the behavior with tests.

New Features:

  • Enable halfcell image rendering that correctly handles transparent pixels by mapping them to spaces or partial block characters.

Tests:

  • Add tests verifying transparent and semi-transparent halfcell pixels render as spaces or blocks without unintended background colors.

Summary by CodeRabbit

  • Bug Fixes
    • Improved half-cell image rendering for RGBA inputs, including correct scaling to terminal cell size.
    • Enhanced colour and styling handling for richer output.
    • Fully transparent halves now render as clean blank spaces, while partially opaque halves use the appropriate half-block glyphs.
  • Tests
    • Added automated coverage for transparent and partially transparent half-cell image rendering, verifying glyph selection and styling/background behaviour.

@sourcery-ai

sourcery-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a monkey-patched transparent-aware halfcell image implementation and tests to ensure transparent pixels render correctly in the terminal.

File-Level Changes

Change Details Files
Monkey-patch textual_image.halfcell.Image to support RGBA images with transparent pixels and map them to appropriate rich Segments.
  • Introduce a custom init that loads the image via PixelData, converts to RGBA, stores _image_data, and computes _render_size via ImageSize.
  • Implement a custom rich_console that scales the image to cell dimensions, iterates upper/lower pixel rows, and emits space/▀/▄ Segments based on alpha values, using foreground/background colors for non-transparent pixels.
  • Add a helper _map_pixel to convert RGBA tuples to rich Color objects and use grouped() and get_cell_size() utilities for image layout.
src/rovr/monkey_patches/_classes.py
Add tests verifying rendering behavior for fully and partially transparent halfcell images.
  • Add a helper that builds a small RGBA image from pixel tuples, renders it via HalfcellImage, and collects Segments from rich_console.
  • Verify that two fully transparent pixels render as a single space Segment with no style set.
  • Verify that a single transparent halfcell with an opaque lower pixel renders as a colored lower-half block without a background color.
tests/test_transparent_halfcell_image.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

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 Plus

Run ID: d2815a60-c800-4e33-9ea6-acec442eafda

📥 Commits

Reviewing files that changed from the base of the PR and between 45e8b9c and f154b82.

📒 Files selected for processing (1)
  • tests/test_transparent_halfcell_image.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_transparent_halfcell_image.py
📜 Recent review details
⏰ Context from checks skipped due to timeout. (13)
  • GitHub Check: format_and_alert
  • GitHub Check: Test on (windows-x64, Python 3.13)
  • GitHub Check: Test on (windows-x64, Python 3.14)
  • GitHub Check: Test on (windows-arm, Python 3.14)
  • GitHub Check: Test on (windows-arm, Python 3.13)
  • GitHub Check: Test on (macos-x64, Python 3.14)
  • GitHub Check: Test on (macos-x64, Python 3.13)
  • GitHub Check: Test on (macos-arm64, Python 3.14)
  • GitHub Check: format_and_push_changes
  • GitHub Check: Test on (windows-arm, Python 3.13)
  • GitHub Check: Test on (windows-x64, Python 3.14)
  • GitHub Check: Test on (windows-arm, Python 3.14)
  • GitHub Check: Test on (macos-arm64, Python 3.13)

📝 Walkthrough

Walkthrough

Changes

Transparent half-cell rendering

Layer / File(s) Summary
RGBA half-cell patch
src/rovr/monkey_patches/_classes.py
Half-cell images initialise through RGBA pixel data and render transparent or opaque upper and lower halves with corresponding Rich glyphs, colours, styles, and row terminators.
Transparency rendering validation
tests/test_transparent_halfcell_image.py
Added a rendering helper and tests for fully transparent pixels and single opaque half-cells without a background colour.

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

🚥 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 matches the main change: adding transparent half-cell rendering support.
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.

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.

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • Monkey-patching halfcell.Image.__init__ and __rich_console__ directly could make this fragile against upstream changes; consider delegating to the original implementations where possible or wrapping/conditionally extending behavior instead of fully replacing it.
  • The custom __rich_console__ implementation always emits a trailing newline Segment("\n") after the last row; if this differs from the original halfcell.Image behavior, you may want to align it or make the extra newline configurable to avoid subtle layout issues when composing renderables.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Monkey-patching `halfcell.Image.__init__` and `__rich_console__` directly could make this fragile against upstream changes; consider delegating to the original implementations where possible or wrapping/conditionally extending behavior instead of fully replacing it.
- The custom `__rich_console__` implementation always emits a trailing newline `Segment("\n")` after the last row; if this differs from the original `halfcell.Image` behavior, you may want to align it or make the extra newline configurable to avoid subtle layout issues when composing renderables.

## Individual Comments

### Comment 1
<location path="tests/test_transparent_halfcell_image.py" line_range="31-36" />
<code_context>
+    assert segments[0].style is None
+
+
+def test_render_single_transparent_halfcell_without_background() -> None:
+    segments = _render_pixels([(0, 0, 0, 0), (255, 0, 0, 255)])
+
+    assert segments[0].text == "▄"
+    assert segments[0].style is not None
+    assert segments[0].style.bgcolor is None
</code_context>
<issue_to_address>
**suggestion (testing):** Add a complementary test for the case where the upper pixel is opaque and the lower pixel is fully transparent.

We already cover the `(upper alpha == 0, lower alpha > 0)` case via `test_render_single_transparent_halfcell_without_background`, which hits the "▄" branch. Please add the complementary `(upper alpha > 0, lower alpha == 0)` test to cover the "▀" branch without background—for example with pixel data `[(255, 0, 0, 255), (0, 0, 0, 0)]`, asserting the character is "▀", `style` is not `None`, and `bgcolor` is `None`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +31 to +36
def test_render_single_transparent_halfcell_without_background() -> None:
segments = _render_pixels([(0, 0, 0, 0), (255, 0, 0, 255)])

assert segments[0].text == "▄"
assert segments[0].style is not None
assert segments[0].style.bgcolor is None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add a complementary test for the case where the upper pixel is opaque and the lower pixel is fully transparent.

We already cover the (upper alpha == 0, lower alpha > 0) case via test_render_single_transparent_halfcell_without_background, which hits the "▄" branch. Please add the complementary (upper alpha > 0, lower alpha == 0) test to cover the "▀" branch without background—for example with pixel data [(255, 0, 0, 255), (0, 0, 0, 0)], asserting the character is "▀", style is not None, and bgcolor is None.

@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.

🧹 Nitpick comments (1)
tests/test_transparent_halfcell_image.py (1)

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

Cover the opaque-upper branch too.

This test only exercises upper[3] == 0. Add the inverse pixel order and assert "▀" with no background colour, so the separate branch at Line 61 remains protected.

Proposed test
+def test_render_opaque_upper_halfcell_without_background() -> None:
+    segments = _render_pixels([(255, 0, 0, 255), (0, 0, 0, 0)])
+
+    assert segments[0].text == "▀"
+    assert segments[0].style is not None
+    assert segments[0].style.bgcolor is None
🤖 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 `@tests/test_transparent_halfcell_image.py` around lines 31 - 36, Extend
test_render_single_transparent_halfcell_without_background with the inverse
pixel order, using an opaque upper pixel and transparent lower pixel, and assert
the rendered text is "▀" with a non-None style whose bgcolor remains None,
covering the opaque-upper branch.
🤖 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.

Nitpick comments:
In `@tests/test_transparent_halfcell_image.py`:
- Around line 31-36: Extend
test_render_single_transparent_halfcell_without_background with the inverse
pixel order, using an opaque upper pixel and transparent lower pixel, and assert
the rendered text is "▀" with a non-None style whose bgcolor remains None,
covering the opaque-upper branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 62c93537-337f-4070-8dc3-3c8a79d1ccc1

📥 Commits

Reviewing files that changed from the base of the PR and between b9f73f4 and 45e8b9c.

📒 Files selected for processing (2)
  • src/rovr/monkey_patches/_classes.py
  • tests/test_transparent_halfcell_image.py
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Sourcery review
  • GitHub Check: format_and_alert
  • GitHub Check: Test on (windows-arm, Python 3.13)
  • GitHub Check: Test on (windows-arm, Python 3.14)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python 3.12 or newer is required.
Use double quotes, four-space indentation, and Ruff as the formatter; there is no strict line-length limit.
Use absolute imports only; do not use relative imports.
All functions must have type annotations; use ty for type checking rather than mypy.
Do not use if TYPE_CHECKING blocks.
Use CamelCase for classes, snake_case for functions and variables, UPPER_CASE for constants, and a leading underscore for private names.
Use specific exceptions instead of bare except:; define custom exceptions in rovr/classes/exceptions.py. Use contextlib.suppress for expected errors.

Files:

  • tests/test_transparent_halfcell_image.py
  • src/rovr/monkey_patches/_classes.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Tests use pytest; test files follow the test_*.py naming convention and shared fixtures belong in conftest.py.

Files:

  • tests/test_transparent_halfcell_image.py
src/rovr/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/rovr/**/*.py: For custom Textual keybindings, inherit from widgets with inherit_bindings=False.
Use @on(EventType) decorators or on_event methods for Textual event handling.
Use @work for asynchronous background work, adding thread=True for blocking tasks.
When modifying widgets from a thread, use call_from_thread, call_next, call_later, or call_after_refresh; reactive variables and properties are exempt.
Do not define functions in the same file as multiprocessing.ProcessPoolExecutor or multiprocessing.Process; place worker functions in a separate module to avoid configuration reload and stdout corruption.
Textual event handlers do not need to call super(); call prevent_default() when an event should not bubble.
Using subprocess.run(shell=True) is allowed because command execution is controlled by the user through configuration.

Files:

  • src/rovr/monkey_patches/_classes.py
🧠 Learnings (8)
📚 Learning: 2025-12-11T03:10:54.344Z
Learnt from: NSPC911
Repo: NSPC911/rovr PR: 167
File: src/rovr/footer/process_container.py:346-346
Timestamp: 2025-12-11T03:10:54.344Z
Learning: In Python/Textual code, when calling checker_wrapper() or any other method that starts workers from within a work(thread=True) decorated method, do not wrap the call in call_from_thread. Starting workers is thread-safe in Textual and the work decorator handles threading. Only UI updates (e.g., DOM modifications) from worker threads require call_from_thread. Apply this guideline to Python files across the codebase where worker-start calls occur; do not wrap such calls with call_from_thread.

Applied to files:

  • tests/test_transparent_halfcell_image.py
  • src/rovr/monkey_patches/_classes.py
📚 Learning: 2026-01-24T02:10:57.940Z
Learnt from: NSPC911
Repo: NSPC911/rovr PR: 203
File: src/rovr/first_launch.py:273-295
Timestamp: 2026-01-24T02:10:57.940Z
Learning: In the rovr codebase (Python/Textual), App.notify() is thread-safe and may be called directly from worker threads decorated with work(thread=True) without wrapping in call_from_thread(). Only DOM modifications require call_from_thread from worker threads. Apply this guideline to Python files across the codebase (not just a single file) and avoid unnecessary call_from_thread() wrappers when invoking App.notify() from worker threads.

Applied to files:

  • tests/test_transparent_halfcell_image.py
  • src/rovr/monkey_patches/_classes.py
📚 Learning: 2026-01-30T13:54:15.110Z
Learnt from: NSPC911
Repo: NSPC911/rovr PR: 207
File: src/rovr/first_launch.py:96-107
Timestamp: 2026-01-30T13:54:15.110Z
Learning: In Python projects using the Textual framework, async workers decorated with work are automatically cancelled when the parent screen/widget is dismissed or unmounted. Do not add manual cancellation in cleanup/dismiss handlers for these workers; rely on Textual to cancel them. Verify this behavior against the project’s Textual version before relying on it.

Applied to files:

  • tests/test_transparent_halfcell_image.py
  • src/rovr/monkey_patches/_classes.py
📚 Learning: 2026-02-27T09:15:49.243Z
Learnt from: NSPC911
Repo: NSPC911/rovr PR: 229
File: src/rovr/functions/system_clipboard.py:143-143
Timestamp: 2026-02-27T09:15:49.243Z
Learning: In Python files that display error messages in toast/notification UI (e.g., ClipboardError and similar exceptions), escape unescaped square brackets in the message so the toast system does not interpret them as markup. Specifically render the content with double backslashes before brackets, e.g. "\\[{type(exc).__name__}]" in the toast description. This guideline applies to all Python files that format and show exception details in toasts across the codebase.

Applied to files:

  • tests/test_transparent_halfcell_image.py
  • src/rovr/monkey_patches/_classes.py
📚 Learning: 2026-03-01T10:35:17.682Z
Learnt from: NSPC911
Repo: NSPC911/rovr PR: 233
File: tests/test_functions_path.py:21-21
Timestamp: 2026-03-01T10:35:17.682Z
Learning: In Python test files (using the Textual framework), use an assertion like Static(id=val, classes=val) to verify that the value contains only characters valid for Textual IDs and classes. The test should confirm initialization succeeds without raising BadIdentifier (do not test attribute storage). This pattern is applicable to tests that validate Textual-compatible identifier strings returned by utilities (e.g., compress/decompress). Apply this guideline to test files under the tests/ directory (e.g., tests/**/*.py) where relevant.

Applied to files:

  • tests/test_transparent_halfcell_image.py
📚 Learning: 2025-12-11T03:11:24.387Z
Learnt from: NSPC911
Repo: NSPC911/rovr PR: 167
File: src/rovr/screens/fd_search.py:205-209
Timestamp: 2025-12-11T03:11:24.387Z
Learning: In src/rovr/screens/fd_search.py, FileSearch.FILTER_TYPES should be implemented as a class attribute (not an instance attribute) to persist filter state across all modal instances within the same session. This enables remember/restore of filter settings when the modal is dismissed and reopened. Ensure this behavior is intentional, document the session lifecycle implications, and include tests verifying persistence across modal reopenings, as well as proper reset or isolation when a new session starts. If applicable, consider thread-safety and potential memory growth due to long-lived state.

Applied to files:

  • src/rovr/monkey_patches/_classes.py
📚 Learning: 2025-12-11T03:11:27.833Z
Learnt from: NSPC911
Repo: NSPC911/rovr PR: 167
File: src/rovr/screens/fd_search.py:205-209
Timestamp: 2025-12-11T03:11:27.833Z
Learning: In src/rovr/screens/fd_search.py, document that the FileSearch class uses a class-level FILTER_TYPES dict to persist filter settings across multiple modal invocations within a single application session. Ensure this pattern is intentional by clarifying the lifecycle of the shared state (lifetime of the process/session) and the implications of mutable shared state (thread-safety, potential memory retention). Consider alternatives (per-instance state, dedicated session storage, explicit reset method) and add tests to verify persistence, correctness after modal reopenings, and safe reset behavior. Also mention any framework or runtime constraints that affect shared-class state.

Applied to files:

  • src/rovr/monkey_patches/_classes.py
📚 Learning: 2026-01-03T14:17:18.132Z
Learnt from: lazysegtree
Repo: NSPC911/rovr PR: 184
File: src/rovr/core/preview_container.py:280-283
Timestamp: 2026-01-03T14:17:18.132Z
Learning: In src/rovr/core/preview_container.py, mypy may not recognize that pdfinfo_from_path's poppler_path parameter accepts None even though the library signature is Optional[Union[str, pathlib.PurePath]]. If you encounter a type error, explicitly cast the argument to Optional[Union[str, pathlib.PurePath]] before passing it (e.g., from typing import cast; pdfinfo_from_path(path, poppler_path=cast(Optional[Union[str, pathlib.PurePath]], None)) or cast the argument to the expected type). This documents the typing limitation and provides a repeatable workaround for similar cases.

Applied to files:

  • src/rovr/monkey_patches/_classes.py
🔇 Additional comments (2)
src/rovr/monkey_patches/_classes.py (1)

1-15: LGTM!

Also applies to: 28-42, 45-71

tests/test_transparent_halfcell_image.py (1)

1-29: LGTM!

@NSPC911
NSPC911 merged commit 438df3b into master Jul 28, 2026
32 checks passed
@NSPC911
NSPC911 deleted the half-trans-cell branch July 28, 2026 15:33
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