Skip to content

EPIC 9B: plumb BLIT_RECT and optimize logs scroll rendering#221

Merged
RtlZeroMemory merged 4 commits intomainfrom
epic9b-blit-rect
Feb 26, 2026
Merged

EPIC 9B: plumb BLIT_RECT and optimize logs scroll rendering#221
RtlZeroMemory merged 4 commits intomainfrom
epic9b-blit-rect

Conversation

@RtlZeroMemory
Copy link
Owner

@RtlZeroMemory RtlZeroMemory commented Feb 26, 2026

Summary

This PR implements EPIC 9B end-to-end support for BLIT_RECT and wires it through Rezi's drawlist pipeline, then applies it to logsConsole scroll rendering to preserve the scrolled region and redraw only exposed strips.

1) Vendor bump (Zireael)

  • Vendored latest Zireael include/ and src/ into packages/native/vendor/zireael.
  • Updated vendor pin:
    • packages/native/vendor/VENDOR_COMMIT.txt -> 9e9ea6f54e3b83b60d6457b06646d5c488b94156 (v1.3.11)
  • Verified native build succeeds (@rezi-ui/native).

2) Drawlist API + writer plumbing

  • Added new builder API:
    • builder.blitRect(srcX, srcY, w, h, dstX, dstY)
  • Updated drawlist types and builder implementation so BLIT_RECT is encoded with the generated writer/opcode expected by Zireael.

3) Renderer integration (real widget)

  • Implemented logsConsole incremental scroll blit in WidgetRenderer:
    • Tracks per-logsConsole scroll snapshots.
    • Builds blit plans for scroll-only commits when geometry/content invariants hold.
    • Emits blitRect for preserved region.
    • Marks only newly exposed strip + scrollbar damage (not full viewport).
  • Added damage-aware logsConsole rendering path so strip/scrollbar clips redraw only intersecting rows/scrollbar rows.

4) Tests

Added/updated tests for golden framebuffer equivalence and blit behavior:

  • renderer.partial.test.ts

    • scroll 1 row/frame for N frames
    • multi-row scroll
    • alternating directions
    • verifies final framebuffer parity vs baseline full rendering
    • verifies blit emission in partial path
  • renderer.scrollBlit.benchmark.test.ts

    • benchmark-harness assertions for bytes/frame, op counts, and time/frame deltas
  • Updated all DrawlistBuilder test doubles/mocks to implement new blitRect API.

  • Updated upstream renderPackets builder/test implementations to satisfy the new DrawlistBuilder interface after rebasing onto latest main.

Perf Results (logsConsole scroll benchmark)

Measured with benchmark harness logic (viewport 120x40, entries 2400):

Scenario Full bytes/frame Partial bytes/frame Delta Full ops/frame Partial ops/frame Delta Full ms/frame Partial ms/frame Delta
one-row 14708 3104.0 -78.9% 205 56.0 -72.7% 0.3382 0.0828 -75.5%
multi-row 14708 3692.9 -74.9% 205 63.8 -68.9% 0.1837 0.0875 -52.4%
alternating 14708 4462.0 -69.7% 205 74.0 -63.9% 0.2000 0.0733 -63.3%

BLIT_RECT ops emitted in partial mode across these scenarios.

Validation Run

  • npx tsc -b packages/testkit/tsconfig.json --pretty false && npx tsc -b packages/core/tsconfig.json --pretty false
  • npm -w @rezi-ui/native run build:native
  • node --test --test-concurrency=1 packages/core/dist/renderer/__tests__/renderer.partial.test.js packages/core/dist/renderer/__tests__/renderer.scrollBlit.benchmark.test.js

All above passed.

Summary by CodeRabbit

  • New Features

    • Added support for rectangular "blit" copy operations in the renderer.
    • Incremental logs-console rendering with preserved per-console scroll state.
  • Performance

    • More efficient logs rendering via partial-frame updates and scroll-aware damage clipping.
  • Tests & Benchmarks

    • New tests and a benchmark harness validating blit behavior and scroll/partial-render improvements.

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d01f334 and e8d6cd5.

📒 Files selected for processing (8)
  • packages/core/src/app/widgetRenderer.ts
  • packages/core/src/drawlist/__tests__/writers.gen.test.ts
  • packages/core/src/drawlist/__tests__/writers.gen.v6.test.ts
  • packages/core/src/drawlist/builder.ts
  • packages/core/src/renderer/__tests__/persistentBlobKeys.test.ts
  • packages/core/src/renderer/__tests__/renderer.partial.test.ts
  • packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts
  • packages/ink-compat/src/runtime/createInkRenderer.ts

📝 Walkthrough

Walkthrough

Adds a new blitRect draw command to the DrawlistBuilder API, implements emit/handler paths, updates renderer and renderTree to support damageRect-aware logs console incremental rendering, and updates tests/benchmarks to exercise scroll-blit behavior.

Changes

Cohort / File(s) Summary
Drawlist API & Emitters
packages/core/src/drawlist/types.ts, packages/core/src/drawlist/builderBase.ts, packages/core/src/drawlist/builder.ts
Add blitRect(srcX,srcY,w,h,dstX,dstY) to DrawlistBuilder, validation/clamping in base, and concrete appendBlitRectCommand that encodes via writeBlitRect.
Renderer: Logs scroll blit + damage-aware editor
packages/core/src/app/widgetRenderer.ts, packages/core/src/renderer/renderToDrawlist/renderTree.ts, packages/core/src/renderer/renderToDrawlist/widgets/editors.ts
Introduce per-console scroll blit plans/snapshots, inject scroll blit damage into incremental pipeline, pass damageRect into renderEditorWidget, and add damageRect-aware rendering that limits logs rows and scrollbar drawing to damaged regions.
Render packet & drawlist forwarding
packages/core/src/renderer/renderToDrawlist/renderPackets.ts
Add RenderPacketRecorder.blitRect(...) which invalidates packet and forwards blit to underlying builder.
Ink-compat renderer changes
packages/ink-compat/src/runtime/createInkRenderer.ts
Add blitRect InkRenderOp, RecordingDrawlistBuilder blitRect emitter, internal blitGridRect copy logic, and opsToText handling for blitRect.
Tests / Test stubs / Benchmarks
packages/core/src/**/__tests__/* (many), packages/core/src/testing/renderer.ts, packages/core/src/renderer/__tests__/renderer.partial.test.ts, packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts
Add no-op blitRect implementations across numerous test builders; extend RecordedOp union and tests to include blitRect scenarios; add a benchmark harness to compare FULL vs PARTIAL scroll-blit metrics and new sequence-based test helpers.
Writers tests
packages/core/src/drawlist/__tests__/writers.gen.test.ts, packages/core/src/drawlist/__tests__/writers.gen.v6.test.ts
Reorder imports to include writeBlitRect in import lists (test-only import ordering changes).

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant WR as WidgetRenderer
    participant Planner as LogsScrollPlanner
    participant Damage as DamageTracker
    participant Builder as DrawlistBuilder

    App->>WR: commit(updates)
    WR->>Planner: buildLogsScrollBlitPlans()
    Planner->>Damage: inject scroll blit damage rects
    WR->>Damage: track incremental damage
    WR->>Builder: incremental render start
    Builder->>Builder: clipDamageRectsWithoutMerge()
    Builder->>Builder: blitRect(...)  // scroll blit ops injected
    Builder->>Builder: drawImage()/other ops
    WR->>Planner: snapshotLogsScrollState()
    WR->>App: return frame
Loading
sequenceDiagram
    participant Editor as renderEditorWidget
    participant Console as Logs Console
    participant Damage as damageRect
    participant Builder as DrawlistBuilder

    Editor->>Console: receive damageRect
    Console->>Damage: compute visible row range
    alt damageRect present
        Console->>Builder: blitRect() for scrolled region
        Console->>Builder: drawImage() for glyphs in damageRect
    else
        Console->>Builder: drawImage() for full content
    end
    Builder->>Builder: encode commands
    Editor->>Builder: return
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through buffers, soft and quick,

With blitRect claws and a little trick.
Logs now scroll and dance with cheer,
Damage trimmed and frames appear.
A rabbit’s joy — fewer pixels to pick. 🎨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'EPIC 9B: plumb BLIT_RECT and optimize logs scroll rendering' directly summarizes the main changes: implementing BLIT_RECT support and optimizing logs scroll rendering, matching the PR's core objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch epic9b-blit-rect

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

Copy link

@coderabbitai coderabbitai bot left a comment

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)
packages/native/vendor/zireael/src/unicode/zr_grapheme.h (1)

24-25: LGTM! Formatting-only change in vendored file.

The whitespace normalization in the struct field declarations is cosmetic and does not affect functionality or the API surface.

Since this is vendored code, consider verifying that the formatting matches the upstream commit (a5d99908b59efe680f97480c4bc0be31d4c3ae22) to ensure consistency with the vendor source.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/native/vendor/zireael/src/unicode/zr_grapheme.h` around lines 24 -
25, This change only normalizes whitespace in the vendored struct fields
(symbols: len and off) and should be left as-is only if it exactly matches
upstream; verify the formatting in zr_grapheme.h matches upstream commit
a5d99908b59efe680f97480c4bc0be31d4c3ae22 and, if it does not, revert or adjust
the whitespace to match that upstream version so the vendored file remains
identical to upstream.
packages/native/vendor/zireael/src/core/zr_framebuffer.c (1)

228-235: Minor redundancy: zr_fb_links_reset(dst) is called twice.

The explicit zr_fb_links_reset(dst) at line 229 is redundant because zr_fb_links_clone_from(dst, src) (line 231) already calls zr_fb_links_reset(dst) unconditionally as its first operation (see line 186).

Since this is vendored code and the overhead is negligible (two O(1) length resets), this may be intentional for clarity or defensive coding. No action required, just noting for awareness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/native/vendor/zireael/src/core/zr_framebuffer.c` around lines 228 -
235, Remove the redundant zr_fb_links_reset(dst) call before calling
zr_fb_links_clone_from(dst, src); zr_fb_links_clone_from already unconditionally
resets the destination as its first step, so keep the single reset inside
zr_fb_links_clone_from and delete the explicit zr_fb_links_reset(dst) invocation
to avoid duplication while preserving behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts`:
- Around line 253-257: The strict assertion comparing partial.timePerFrameMs and
full.timePerFrameMs is flaky; relax it by introducing a small tolerance and
assert that partial.timePerFrameMs is sufficiently less than full.timePerFrameMs
(e.g., partial.timePerFrameMs <= full.timePerFrameMs + margin or
partial.timePerFrameMs < full.timePerFrameMs * (1 - relativeTol)), and include
scenario.name in the error message; update the assertion around
partial.timePerFrameMs / full.timePerFrameMs in
renderer.scrollBlit.benchmark.test.ts to use a margin/relative tolerance
variable (epsilon/margin) instead of a bare `<` comparison.

In `@packages/native/vendor/zireael/include/zr/zr_drawlist.h`:
- Line 2: Update the protocol docs to remove V2 support: edit
docs/protocol/zrdl.md to stop listing "ZRDL v1/v2", remove the V2-specific
opcode documentation (e.g., BLIT_RECT) and any examples or tables that mention
v2; edit docs/protocol/versioning.md to delete or change the
ZR_DRAWLIST_VERSION_V2 symbol and any language saying the engine "accepts v1/v2"
so it only documents v1 compatibility and the current accepted version; ensure
any cross-references or version compatibility rules are adjusted to reflect V2
removal.

---

Nitpick comments:
In `@packages/native/vendor/zireael/src/core/zr_framebuffer.c`:
- Around line 228-235: Remove the redundant zr_fb_links_reset(dst) call before
calling zr_fb_links_clone_from(dst, src); zr_fb_links_clone_from already
unconditionally resets the destination as its first step, so keep the single
reset inside zr_fb_links_clone_from and delete the explicit
zr_fb_links_reset(dst) invocation to avoid duplication while preserving
behavior.

In `@packages/native/vendor/zireael/src/unicode/zr_grapheme.h`:
- Around line 24-25: This change only normalizes whitespace in the vendored
struct fields (symbols: len and off) and should be left as-is only if it exactly
matches upstream; verify the formatting in zr_grapheme.h matches upstream commit
a5d99908b59efe680f97480c4bc0be31d4c3ae22 and, if it does not, revert or adjust
the whitespace to match that upstream version so the vendored file remains
identical to upstream.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd264a0 and 0f2929a.

📒 Files selected for processing (50)
  • packages/core/src/__tests__/stress/stress.large-trees.test.ts
  • packages/core/src/app/__tests__/partialDrawlistEmission.test.ts
  • packages/core/src/app/__tests__/rawRender.test.ts
  • packages/core/src/app/__tests__/widgetRenderer.integration.test.ts
  • packages/core/src/app/widgetRenderer.ts
  • packages/core/src/drawlist/builder.ts
  • packages/core/src/drawlist/builderBase.ts
  • packages/core/src/drawlist/types.ts
  • packages/core/src/renderer/__tests__/focusIndicators.test.ts
  • packages/core/src/renderer/__tests__/persistentBlobKeys.test.ts
  • packages/core/src/renderer/__tests__/recipeRendering.test-utils.ts
  • packages/core/src/renderer/__tests__/renderer.border.test.ts
  • packages/core/src/renderer/__tests__/renderer.clip.test.ts
  • packages/core/src/renderer/__tests__/renderer.damage.test.ts
  • packages/core/src/renderer/__tests__/renderer.partial.perf.test.ts
  • packages/core/src/renderer/__tests__/renderer.partial.test.ts
  • packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts
  • packages/core/src/renderer/__tests__/renderer.scrollbar.test.ts
  • packages/core/src/renderer/renderToDrawlist/renderTree.ts
  • packages/core/src/renderer/renderToDrawlist/widgets/editors.ts
  • packages/core/src/testing/renderer.ts
  • packages/core/src/theme/__tests__/theme.switch.test.ts
  • packages/core/src/ui/__tests__/themed.test.ts
  • packages/native/vendor/VENDOR_COMMIT.txt
  • packages/native/vendor/zireael/include/zr/zr_drawlist.h
  • packages/native/vendor/zireael/include/zr/zr_metrics.h
  • packages/native/vendor/zireael/include/zr/zr_version.h
  • packages/native/vendor/zireael/src/core/zr_config.c
  • packages/native/vendor/zireael/src/core/zr_cursor.h
  • packages/native/vendor/zireael/src/core/zr_damage.h
  • packages/native/vendor/zireael/src/core/zr_drawlist.c
  • packages/native/vendor/zireael/src/core/zr_engine_present.inc
  • packages/native/vendor/zireael/src/core/zr_event_pack.c
  • packages/native/vendor/zireael/src/core/zr_event_pack.h
  • packages/native/vendor/zireael/src/core/zr_framebuffer.c
  • packages/native/vendor/zireael/src/core/zr_metrics.c
  • packages/native/vendor/zireael/src/core/zr_placeholder.c
  • packages/native/vendor/zireael/src/platform/win32/zr_win32_conpty_test.c
  • packages/native/vendor/zireael/src/platform/win32/zr_win32_conpty_test.h
  • packages/native/vendor/zireael/src/unicode/zr_grapheme.h
  • packages/native/vendor/zireael/src/unicode/zr_unicode_data.h
  • packages/native/vendor/zireael/src/unicode/zr_unicode_pins.h
  • packages/native/vendor/zireael/src/unicode/zr_utf8.h
  • packages/native/vendor/zireael/src/unicode/zr_width.h
  • packages/native/vendor/zireael/src/unicode/zr_wrap.h
  • packages/native/vendor/zireael/src/util/zr_arena.h
  • packages/native/vendor/zireael/src/util/zr_ring.h
  • packages/native/vendor/zireael/src/util/zr_string_builder.h
  • packages/native/vendor/zireael/src/util/zr_string_view.h
  • packages/native/vendor/zireael/src/util/zr_vec.h
💤 Files with no reviewable changes (3)
  • packages/native/vendor/zireael/src/unicode/zr_utf8.h
  • packages/native/vendor/zireael/src/unicode/zr_unicode_pins.h
  • packages/native/vendor/zireael/src/util/zr_string_view.h

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f2929a1c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/renderer/renderToDrawlist/widgets/editors.ts (1)

849-860: ⚠️ Potential issue | 🟠 Major

Text rendering can corrupt the scrollbar column in damage-only updates.

Line 849 clips text drawing to contentRect, and Line 932 computes text width from contentRect.w. When hasScrollbar is true and scrollbar rows are skipped for this damage pass, text writes can land in the scrollbar column and leave stale visuals. Clip/render text against textRect instead.

Proposed fix
-      builder.pushClip(contentRect.x, contentRect.y, contentRect.w, contentRect.h);
-
       const startIndex = Math.max(0, props.scrollTop);
       const endIndex = Math.min(filtered.length, startIndex + contentRect.h);

       const hasScrollbar = contentRect.h > 0 && filtered.length > contentRect.h;
       const textRect: Rect = {
         x: contentRect.x,
         y: contentRect.y,
         w: hasScrollbar ? clampNonNegative(contentRect.w - 1) : contentRect.w,
         h: contentRect.h,
       };
+      builder.pushClip(textRect.x, textRect.y, textRect.w, textRect.h);
       const shouldRenderTextRows = !damageRect || rectIntersects(damageRect, textRect);
@@
-          const remaining = contentRect.w - (x - contentRect.x);
+          const remaining = textRect.w - (x - textRect.x);

Also applies to: 932-942, 948-986

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/renderer/renderToDrawlist/widgets/editors.ts` around lines
849 - 860, The text rendering currently clips to contentRect and computes widths
from contentRect.w which allows glyphs to paint into the scrollbar column during
damage-only passes; change the clipping and rendering to use the computed
textRect instead: replace builder.pushClip(contentRect.x, contentRect.y,
contentRect.w, contentRect.h) with a clip using textRect (x,y,w,h) and ensure
all text measurement and draw calls that reference contentRect.w or contentRect
are switched to use textRect (and respect hasScrollbar) so text never writes
into the scrollbar column during damage-only updates; update any places that
compute text width or call the text draw routine to read from textRect.w/h and
use textRect for clipping.
🧹 Nitpick comments (1)
packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts (1)

77-101: Binary parsing assumes fixed layout — add defensive checks or comments.

parseDrawlistStats reads specific offsets (16, 20, 24) from the drawlist header. If the protocol format changes, this will silently produce incorrect results.

Add version/magic validation or document assumptions
 function parseDrawlistStats(drawlist: Uint8Array): Readonly<{ cmdCount: number; blitCount: number }> {
   if (drawlist.byteLength < HEADER_SIZE) {
     return Object.freeze({ cmdCount: 0, blitCount: 0 });
   }

   const dv = new DataView(drawlist.buffer, drawlist.byteOffset, drawlist.byteLength);
+  // Header layout (v1 protocol):
+  // offset 16: cmdOffset (u32)
+  // offset 20: cmdBytes (u32)
+  // offset 24: cmdCount (u32)
   const cmdOffset = dv.getUint32(16, true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts`
around lines 77 - 101, parseDrawlistStats currently assumes a fixed header
layout and blindly reads offsets 16, 20, 24 which can silently break if the
protocol changes; update the function to validate the header before parsing by
checking for a magic/version field (or explicit minimum HEADER_SIZE) at the
start, ensure the DataView reads for cmdOffset/cmdBytes/cmdCount are within
bounds (e.g., header region) and fail fast if magic/version mismatches,
out-of-range offsets are detected, or HEADER_SIZE is too small, and add a short
comment documenting the expected header layout and meaning of OP_BLIT_RECT so
future changes must update these checks (refer to parseDrawlistStats,
HEADER_SIZE, OP_BLIT_RECT and the offsets 16/20/24).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/app/widgetRenderer.ts`:
- Around line 2423-2428: buildLogsScrollBlitPlans uses rects from rectById and
calls builder.blitRect(...) without checking ancestor clipping; update
buildLogsScrollBlitPlans (and the other similar sites around the mentions) to
compute the visible intersection between the geometry.contentRect returned by
resolveLogsConsoleRenderGeometry(...) and the current clip stack (or ancestor
clip rect) before emitting builder.blitRect: if the intersection has
non-positive width/height skip emitting the blit, otherwise pass the
intersection (or wrap the blit with clip begin/end) so blitRect never copies
pixels outside the widget’s visible region. Ensure the same guard is applied at
the other occurrences you noted (the blocks around lines shown) to use
contentRect intersected with ancestor clip.

In `@packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts`:
- Around line 12-13: Replace the hardcoded HEADER_SIZE = 64 with an import of
HEADER_SIZE from the canonical builderBase export while leaving OP_BLIT_RECT =
14 as-is (it cannot be imported from generated writers); update the top of the
test file to import HEADER_SIZE from the builderBase module and use that
constant in place of the literal so the test stays synchronized with the
canonical definition, keeping OP_BLIT_RECT unchanged.

---

Outside diff comments:
In `@packages/core/src/renderer/renderToDrawlist/widgets/editors.ts`:
- Around line 849-860: The text rendering currently clips to contentRect and
computes widths from contentRect.w which allows glyphs to paint into the
scrollbar column during damage-only passes; change the clipping and rendering to
use the computed textRect instead: replace builder.pushClip(contentRect.x,
contentRect.y, contentRect.w, contentRect.h) with a clip using textRect
(x,y,w,h) and ensure all text measurement and draw calls that reference
contentRect.w or contentRect are switched to use textRect (and respect
hasScrollbar) so text never writes into the scrollbar column during damage-only
updates; update any places that compute text width or call the text draw routine
to read from textRect.w/h and use textRect for clipping.

---

Nitpick comments:
In `@packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts`:
- Around line 77-101: parseDrawlistStats currently assumes a fixed header layout
and blindly reads offsets 16, 20, 24 which can silently break if the protocol
changes; update the function to validate the header before parsing by checking
for a magic/version field (or explicit minimum HEADER_SIZE) at the start, ensure
the DataView reads for cmdOffset/cmdBytes/cmdCount are within bounds (e.g.,
header region) and fail fast if magic/version mismatches, out-of-range offsets
are detected, or HEADER_SIZE is too small, and add a short comment documenting
the expected header layout and meaning of OP_BLIT_RECT so future changes must
update these checks (refer to parseDrawlistStats, HEADER_SIZE, OP_BLIT_RECT and
the offsets 16/20/24).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f2929a and d01f334.

📒 Files selected for processing (25)
  • packages/core/src/__tests__/stress/stress.large-trees.test.ts
  • packages/core/src/app/__tests__/partialDrawlistEmission.test.ts
  • packages/core/src/app/__tests__/rawRender.test.ts
  • packages/core/src/app/__tests__/widgetRenderer.integration.test.ts
  • packages/core/src/app/widgetRenderer.ts
  • packages/core/src/drawlist/builder.ts
  • packages/core/src/drawlist/builderBase.ts
  • packages/core/src/drawlist/types.ts
  • packages/core/src/renderer/__tests__/focusIndicators.test.ts
  • packages/core/src/renderer/__tests__/persistentBlobKeys.test.ts
  • packages/core/src/renderer/__tests__/recipeRendering.test-utils.ts
  • packages/core/src/renderer/__tests__/renderPackets.test.ts
  • packages/core/src/renderer/__tests__/renderer.border.test.ts
  • packages/core/src/renderer/__tests__/renderer.clip.test.ts
  • packages/core/src/renderer/__tests__/renderer.damage.test.ts
  • packages/core/src/renderer/__tests__/renderer.partial.perf.test.ts
  • packages/core/src/renderer/__tests__/renderer.partial.test.ts
  • packages/core/src/renderer/__tests__/renderer.scrollBlit.benchmark.test.ts
  • packages/core/src/renderer/__tests__/renderer.scrollbar.test.ts
  • packages/core/src/renderer/renderToDrawlist/renderPackets.ts
  • packages/core/src/renderer/renderToDrawlist/renderTree.ts
  • packages/core/src/renderer/renderToDrawlist/widgets/editors.ts
  • packages/core/src/testing/renderer.ts
  • packages/core/src/theme/__tests__/theme.switch.test.ts
  • packages/core/src/ui/__tests__/themed.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/core/src/renderer/tests/renderer.damage.test.ts
  • packages/core/src/renderer/renderToDrawlist/renderTree.ts
  • packages/core/src/renderer/tests/renderer.partial.perf.test.ts
  • packages/core/src/app/tests/rawRender.test.ts
  • packages/core/src/renderer/tests/recipeRendering.test-utils.ts
  • packages/core/src/theme/tests/theme.switch.test.ts
  • packages/core/src/tests/stress/stress.large-trees.test.ts
  • packages/core/src/renderer/tests/renderer.border.test.ts

@RtlZeroMemory RtlZeroMemory merged commit fa62449 into main Feb 26, 2026
7 checks passed
@RtlZeroMemory RtlZeroMemory deleted the epic9b-blit-rect branch February 28, 2026 04:18
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