Skip to content

Fix post-refactor regressions and bump native vendor to Zireael #103#223

Merged
RtlZeroMemory merged 20 commits intomainfrom
perf/ink-compat-optimized-renderer
Feb 27, 2026
Merged

Fix post-refactor regressions and bump native vendor to Zireael #103#223
RtlZeroMemory merged 20 commits intomainfrom
perf/ink-compat-optimized-renderer

Conversation

@RtlZeroMemory
Copy link
Owner

@RtlZeroMemory RtlZeroMemory commented Feb 27, 2026

Summary

  • resolve refactor regressions across drawlist builder, renderer damage/clip traversal, and widget renderer exit-animation scheduling
  • fix stale/dist parity issues by rebuilding core dist outputs and updating affected fixtures/tests
  • add native vendor integrity guardrails (scripts/check-native-vendor-integrity.mjs + CI/test coverage)
  • sync packages/native/vendor/zireael to Zireael PR head 268e21f and update VENDOR_COMMIT.txt
  • include native no-partial-effects/link-growth regression coverage in packages/native/src/lib.rs

Validation

  • npm -w @rezi-ui/core run build
  • node scripts/run-tests.mjs --scope all . . (4597/4597 pass)
  • cargo test in packages/native
  • npm -w @rezi-ui/native run test:native:smoke

Dependency

Summary by CodeRabbit

  • New Features

    • Unified drawlist builder, native resource lifecycle APIs, frame-audit diagnostics, and a new interactive/headless Regression Dashboard example.
  • Improvements

    • CI now runs a native-vendor integrity check.
    • Consolidated theming to packed color representation for consistent styling.
    • Enhanced auditing/telemetry hooks and widespread builder/test standardization.
  • Documentation

    • Expanded vendor, native resource lifecycle, drawlist protocol, and developer build guidance.

RtlZeroMemory and others added 12 commits February 26, 2026 11:27
- Add concurrency group to ci.yml and codeql.yml to cancel in-progress
  runs on re-push (biggest win for runner contention)
- Extract lint/typecheck/codegen/portability/unicode into a dedicated
  `checks` job that gates the matrix — lint failures caught in ~2 min
  instead of after the full 15-min pipeline
- Dynamic matrix: 5 runners on PRs (Linux × Node 18/20/22, macOS × 22,
  Windows × 22), full 3×3 on push to main
- Remove redundant lint/typecheck/codegen/biome-install steps from each
  matrix cell and the bun job
- Remove duplicate docs job (already handled by docs.yml)

Net effect on PRs: 13 jobs → 9, ~44% fewer runners, fast-fail on
static checks, stale runs cancelled automatically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ized-renderer

# Conflicts:
#	packages/core/src/abi.ts
#	packages/core/src/app/__tests__/partialDrawlistEmission.test.ts
#	packages/core/src/app/createApp.ts
#	packages/core/src/app/rawRenderer.ts
#	packages/core/src/app/widgetRenderer.ts
#	packages/core/src/drawlist/__tests__/builder.build-into.test.ts
#	packages/core/src/drawlist/__tests__/builder.cursor.test.ts
#	packages/core/src/drawlist/__tests__/builder.round-trip.test.ts
#	packages/core/src/drawlist/__tests__/writers.gen.test.ts
#	packages/core/src/drawlist/builder.ts
#	packages/core/src/drawlist/builder_v1.ts
#	packages/core/src/drawlist/builder_v2.ts
#	packages/core/src/drawlist/index.ts
#	packages/core/src/drawlist/types.ts
#	packages/core/src/index.ts
#	packages/core/src/renderer/renderToDrawlist/widgets/renderCanvasWidgets.ts
#	packages/node/src/__tests__/config_guards.test.ts
#	packages/node/src/backend/nodeBackend.ts
#	packages/node/src/backend/nodeBackendInline.ts
@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 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 4cbcb8b and ef53eef.

📒 Files selected for processing (29)
  • examples/regression-dashboard/src/main.ts
  • packages/core/src/__tests__/drawlistDecode.ts
  • packages/core/src/__tests__/integration/integration.dashboard.test.ts
  • packages/core/src/__tests__/integration/integration.file-manager.test.ts
  • packages/core/src/__tests__/integration/integration.form-editor.test.ts
  • packages/core/src/__tests__/integration/integration.reflow.test.ts
  • packages/core/src/__tests__/integration/integration.resize.test.ts
  • packages/core/src/app/rawRenderer.ts
  • packages/core/src/app/widgetRenderer.ts
  • packages/core/src/drawlist/__tests__/builder.alignment.test.ts
  • packages/core/src/drawlist/__tests__/builder.limits.test.ts
  • packages/core/src/drawlist/__tests__/builder.reset.test.ts
  • packages/core/src/drawlist/__tests__/builder.round-trip.test.ts
  • packages/core/src/drawlist/__tests__/builder.string-cache.test.ts
  • packages/core/src/drawlist/__tests__/builder.string-intern.test.ts
  • packages/core/src/drawlist/__tests__/builder_v6_resources.test.ts
  • packages/core/src/drawlist/__tests__/writers.gen.test.ts
  • packages/core/src/renderer/__tests__/persistentBlobKeys.test.ts
  • packages/core/src/renderer/__tests__/renderer.text.test.ts
  • packages/core/src/renderer/renderToDrawlist/overflowCulling.ts
  • packages/core/src/theme/validate.ts
  • packages/core/src/widgets/__tests__/style.inheritance.test.ts
  • packages/create-rezi/templates/starship/src/main.ts
  • packages/node/src/backend/nodeBackend.ts
  • packages/node/src/backend/nodeBackendInline.ts
  • packages/node/src/frameAudit.ts
  • packages/node/src/worker/engineWorker.ts
  • scripts/check-native-vendor-integrity.mjs
  • scripts/frame-audit-report.mjs

📝 Walkthrough

Walkthrough

Consolidates drawlist builders into a single builder, introduces DEF_STRING/DEF_BLOB resource ops and shared decoders, migrates colors to packed Rgb24, adds optional frame-audit instrumentation and native vendor integrity CI checks, and adds a regression-dashboard example with tests.

Changes

Cohort / File(s) Summary
CI & Vendor Integrity
\.github/workflows/ci.yml, package.json, scripts/check-native-vendor-integrity.mjs, docs/dev/build.md, docs/dev/repo-layout.md, packages/native/README.md
Adds npm script check:native-vendor, inserts CI step to run it, adds script that validates packages/native/vendor/VENDOR_COMMIT.txt vs repo gitlink and documents vendoring requirements.
Drawlist Builder & Writers
packages/core/src/drawlist/builder.ts, packages/core/src/drawlist/builderBase.ts, packages/core/src/drawlist/writers.gen.ts, packages/core/src/drawlist/index.ts, packages/core/src/drawlist/writers.gen.ts
Unifies versioned builders into createDrawlistBuilder, introduces planning/write pipeline, DEF_STRING/DEF_BLOB/FREE resource model, 1-based resource IDs, new APIs (e.g., blitRect, text perf counters moved/changed), and many validation/size checks.
Shared Decoders & Tests
packages/core/src/__tests__/drawlistDecode.ts, packages/core/src/drawlist/__tests__/*, packages/core/src/*/__tests__/*, packages/core/src/renderer/__tests__/*
Adds centralized parse helpers (opcodes, parseCommandHeaders, parseInternedStrings, parseDrawTextCommands, parseBlobById) and updates numerous tests to import and use them, removing locally duplicated parsers.
Renderer / App / Backend Integration
packages/core/src/app/createApp.ts, packages/core/src/app/rawRenderer.ts, packages/core/src/app/widgetRenderer.ts, packages/node/src/backend/nodeBackend.ts, packages/node/src/backend/nodeBackendInline.ts
Removes per-version drawlist plumbing, accepts unified DrawlistBuilder, wires optional beginFrame path and frame-audit hooks, adjusts renderer lifecycle and in-flight/accepted/completed handling; Node backend exposes BEGIN_FRAME marker and integrates audit lifecycle.
Frame Audit / Telemetry
packages/core/src/perf/frameAudit.ts, packages/node/src/frameAudit.ts, packages/node/src/worker/engineWorker.ts, packages/node/src/...
Introduces optional frame-audit tooling (fingerprinting, opcode histograms, NDJSON emission), native/worker audit integration, dump helpers and environment gates; many internal hooks and emitters added.
Render internals & Widgets
packages/core/src/renderer/renderToDrawlist/*, packages/core/src/renderer/renderToDrawlist/widgets/*, packages/core/src/renderer/renderToDrawlist/overflowCulling.ts
Deferred clip push, unified DrawlistBuilder usage across render-to-drawlist widgets, new overflow-culling utility, simplified blob handling and canvas/image flows.
Color & Theme Migration
packages/core/src/theme/*.ts, packages/core/src/theme/resolve.ts, packages/core/src/theme/validate.ts, packages/create-rezi/templates/starship/src/theme.ts, packages/core/src/widgets/*
Migrates color model from {r,g,b} objects to packed 24-bit Rgb24, updates validation/resolution, theme helpers, and many tests/templates to use pack/unpack helpers.
Examples: Regression Dashboard
examples/regression-dashboard/*
Adds a new terminal regression-dashboard example: state management, telemetry stream, keybindings, theme, UI renderer, tests, README and package.json.
Public API & Exports
packages/core/src/abi.ts, packages/core/src/index.ts
Adds ZR_DRAWLIST_VERSION alias, consolidates drawlist builder exports to single createDrawlistBuilder/DrawlistBuilder types, updates style export types to Rgb24.
Misc & Tests
many tests across packages/core/src/**/__tests__, packages/ink-compat, templates, docs
Massive test updates: switch to unified builder, use shared decoders, adopt packed colors, add/adjust tests for resource ops, text-run blobs, audit behaviors; small template and docs updates.

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant Renderer
    participant Backend
    participant Worker
    rect rgba(200,200,255,0.5)
    App->>Renderer: render frame -> build drawlist (createDrawlistBuilder)
    Renderer->>Renderer: compute fingerprint (drawlistFingerprint)
    Renderer->>Backend: beginFrame / submit drawlist (BEGIN_FRAME or publish)
    Backend->>Worker: transfer or SAB publish (frame payload)
    Worker->>Backend: ack/accept (accepted/completed)
    Backend->>Renderer: accepted/completed event (frame lifecycle)
    Renderer->>App: emit frame-audit events (emitFrameAudit)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐇 I hopped through bytes and strings now one,

packed colors shining like a sun,
resources penned with DEFs in line,
audits hum a fingerprint sign,
CI guards the native vine — carrots, done.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.07% 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 PR title 'Fix post-refactor regressions and bump native vendor to Zireael #103' clearly and specifically identifies the main changes: addressing refactor-related bugs and updating a native vendor dependency.

✏️ 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 perf/ink-compat-optimized-renderer

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

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: 47375c9ea7

ℹ️ 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: 9

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (4)
packages/core/src/widgets/__tests__/graphicsWidgets.test.ts (2)

471-483: ⚠️ Potential issue | 🟡 Minor

Avoid raw VNode literals; use ui.image() in this test case.

The same test file already uses ui.image({...}) on line 461. The raw { kind, props } object on line 473 is inconsistent with that pattern and violates the widget-construction invariant. All widget construction should go through the ui.*() factory functions.

♻️ Proposed fix
-    const bytes = renderBytes(
-      {
-        kind: "image",
-        props: {
-          src: "bad-bytes" as unknown as Uint8Array,
-          width: 20,
-          height: 4,
-          alt: "Broken image",
-        },
-      },
-      () => createDrawlistBuilder(),
-    );
+    const bytes = renderBytes(
+      ui.image({
+        src: "bad-bytes" as unknown as Uint8Array,
+        width: 20,
+        height: 4,
+        alt: "Broken image",
+      }),
+      () => createDrawlistBuilder(),
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/widgets/__tests__/graphicsWidgets.test.ts` around lines 471
- 483, The test "image invalid source falls back without DRAW_IMAGE" is
constructing a raw VNode literal; replace that literal with the widget factory
call ui.image(...) so the test uses the same invariant as other tests (see
ui.image used on line 461). Update the renderBytes invocation to pass ui.image({
src: "bad-bytes" as unknown as Uint8Array, width: 20, height: 4, alt: "Broken
image" }) instead of the { kind: "image", props: { ... } } object and keep the
createDrawlistBuilder() argument unchanged.

114-147: ⚠️ Potential issue | 🟠 Major

Use createTestRenderer() as the test entrypoint.

This helper directly wires commit/layout/render, but test files in this repo are required to render through the testing module API.

#!/bin/bash
# Verify this file uses low-level rendering pipeline instead of createTestRenderer.
rg -n -C2 'createTestRenderer|commitVNodeTree|renderToDrawlist|layout\(' packages/core/src/widgets/__tests__/graphicsWidgets.test.ts

As per coding guidelines **/__tests__/**/*.test.{ts,tsx}: Test with createTestRenderer() from the testing module.

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

In `@packages/core/src/widgets/__tests__/graphicsWidgets.test.ts` around lines 114
- 147, The test uses the low-level pipeline functions (renderBytes calling
commitVNodeTree, layout, renderToDrawlist and builder.build) instead of the
required testing API; replace the custom renderBytes path with the testing
helper createTestRenderer() so the test renders via the module entrypoint:
locate the renderBytes helper and update tests to call createTestRenderer() (or
refactor renderBytes to wrap createTestRenderer()) so commit/layout/render are
driven by createTestRenderer rather than directly invoking commitVNodeTree,
layout, or renderToDrawlist; ensure the viewport and terminalProfile parameters
are passed through the createTestRenderer invocation to preserve existing
behavior.
packages/ink-compat/src/translation/colorMap.ts (1)

4-22: ⚠️ Potential issue | 🟠 Major

Explicit black (0) is suppressed as unset; all parse paths must normalize to near-black.

The migration creates multiple code paths that produce rgb(0, 0, 0): named color "black" (line 5), hex formats "#000"/"#000000" (lines 79–94), "rgb(0, 0, 0)" literals (lines 96–109), and ansi256(0) (line 114). All pack to value 0, which is the sentinel for "unset/terminal default" in ink-compat. In styleToSgr (render.ts lines 1220, 1231), falsy checks on fg and bg skip emitting color SGR codes entirely, so explicit black disappears on output.

Normalize all explicit-black inputs to near-black (e.g., 0x010101) to preserve rendering intent and avoid collision with the unset sentinel.

Suggested approach
+const EXPLICIT_BLACK: Rgb24 = rgb(1, 1, 1);
+
+function normalizeExplicitBlack(value: Rgb24 | undefined): Rgb24 | undefined {
+  return value === 0 ? EXPLICIT_BLACK : value;
+}

Then apply to the return points in parseColorInner (e.g., after line 65, line 86, line 94, line 109) and in decodeAnsi256Color (e.g., line 114).

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

In `@packages/ink-compat/src/translation/colorMap.ts` around lines 4 - 22, Named
"black" and other explicit-black parse paths currently produce rgb(0,0,0) which
collides with the unset sentinel; update the color normalization so any
explicit-black result is mapped to near-black (0x010101) before returning.
Concretely: adjust NAMED_COLORS (symbol) or apply a post-parse normalization in
parseColorInner (all return points that return rgb(0,0,0) from named, hex,
rgb-literal branches) to convert {r:0,g:0,b:0} to {r:1,g:1,b:1}, and do the same
in decodeAnsi256Color for the case that returns black (ansi256 0) so callers
like styleToSgr will see a non-falsy color and emit SGR codes.
packages/core/src/drawlist/__tests__/builder.cursor.test.ts (1)

1-10: ⚠️ Potential issue | 🟡 Minor

Documentation comment inconsistency with code.

The JSDoc comment at line 5 mentions "Correct v2 header version" but the test at line 49 verifies ZR_DRAWLIST_VERSION_V1. The comment should be updated to reflect the current version.

📝 Suggested fix
 /**
- * Unit tests for DrawlistBuilder SET_CURSOR command encoding.
+ * Unit tests for DrawlistBuilder SET_CURSOR command encoding (ZRDL v1).
  *
  * Verifies:
- *   - Correct v2 header version
+ *   - Correct v1 header version
  *   - SET_CURSOR command byte layout matches C struct
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/drawlist/__tests__/builder.cursor.test.ts` around lines 1 -
10, Update the top JSDoc to match the actual assertion: change the header note
from "Correct v2 header version" to "Correct v1 header version" (or otherwise
reflect V1) so it aligns with the test that checks ZR_DRAWLIST_VERSION_V1 in
builder.cursor.test.ts; locate the JSDoc block at the top of the file and edit
the sentence to reference v1 header version.
🟡 Minor comments (12)
packages/core/src/perf/perf.ts-247-249 (1)

247-249: ⚠️ Potential issue | 🟡 Minor

Use a null-prototype dictionary for dynamic counter keys.

Building counters with {} can behave unexpectedly for special keys. Use Object.create(null) for safer key/value materialization.

🛡️ Proposed fix
-    const counters: Record<string, number> = {};
+    const counters = Object.create(null) as Record<string, number>;
     for (const [name, value] of this.counters) counters[name] = value;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/perf/perf.ts` around lines 247 - 249, The current dynamic
materialization uses a plain object for counters which can collide with
inherited properties; change the local counters map in the method that reads
this.counters to use a null-prototype object (create with Object.create(null)
and cast to Record<string, number> as needed) instead of {} so keys are safe,
then populate it from this.counters and return as before with
Object.freeze(phases) and Object.freeze(counters); update the declaration of the
local counters variable referenced in this function to use the null-prototype
creation.
packages/core/src/widgets/__tests__/style.merge-fuzz.test.ts-25-29 (1)

25-29: ⚠️ Potential issue | 🟡 Minor

attrs value correctness is no longer asserted.

After Line [28] made attrs optional, Line [195] only checks type. That allows packed-attrs regressions to pass while boolean field assertions still succeed. Please restore value-level attrs verification with an independent expected-attrs calculation.

Based on learnings: Changes to drawlist generation code are a danger zone requiring extra care and full test suite verification.

Also applies to: 176-178, 194-200

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

In `@packages/core/src/widgets/__tests__/style.merge-fuzz.test.ts` around lines 25
- 29, The test removed a value-level assertion for the packed attrs field after
making MutableResolvedTextStyle.attrs optional, which allows regressions to slip
by; restore an independent expected-attrs calculation in the fuzz test and
assert equality against the actual attrs value (not just its type). Locate the
assertions that check resolved text style boolean fields and the
MutableResolvedTextStyle shape (references: MutableResolvedTextStyle and the
test block that computes expected resolved style) and add code to compute
expectedAttrs from the boolean flags the same way production does, then assert
expectedAttrs === actual.attrs (or treat undefined as 0 if the code packs that
way) in addition to the existing boolean and type checks. Ensure this explicit
packed-attrs verification is added in the same test cases that validate boolean
fields so regressions in drawlist/packing are caught.
packages/create-rezi/templates/starship/src/main.ts-629-649 (1)

629-649: ⚠️ Potential issue | 🟡 Minor

Polling loop can overlap async debug queries.

setInterval schedules pump() regardless of whether the prior query finished, so slow queries can overlap and race on shared debug state.

Suggested fix (in-flight guard)
-  const pump = async () => {
-    if (!debugController) return;
+  let pumpInFlight = false;
+  const pump = async () => {
+    if (!debugController || pumpInFlight) return;
+    pumpInFlight = true;
     try {
       const records = await debugController.query({ maxRecords: 256 });
       if (records.length === 0) return;
       for (const record of records) {
         if (record.header.recordId <= debugLastRecordId) continue;
         debugLastRecordId = record.header.recordId;
         snapshotDebugRecord(record);
       }
     } catch (error) {
       debugSnapshot("runtime.debug.query.error", {
         message: error instanceof Error ? error.message : String(error),
         route: currentRouteId(),
       });
+    } finally {
+      pumpInFlight = false;
     }
   };

As per coding guidelines: all TypeScript changes must follow docs/dev/code-standards.md, including async cancellation/error-handling guardrails.

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

In `@packages/create-rezi/templates/starship/src/main.ts` around lines 629 - 649,
The polling loop started by debugTraceTimer can overlap calls to pump because
setInterval fires regardless of pump completion; fix by adding an in-flight
guard or switching to a self-scheduling async loop so only one pump runs at a
time: introduce a boolean flag (e.g., debugPumpInFlight) or a mutex checked/set
at the start of pump and cleared on exit (including in catch/finally), or
replace the setInterval with an async function that awaits pump then uses
setTimeout to schedule the next run; ensure you reference and update the same
shared state variables used in pump (debugController, debugLastRecordId,
snapshotDebugRecord) and still clear the timer/stop the loop when debugging is
cancelled.
packages/core/src/app/__tests__/rawRender.test.ts-37-39 (1)

37-39: ⚠️ Potential issue | 🟡 Minor

Mirror buildInto() state transitions with build() in the stub.

makeStubBuilder().buildInto() always succeeds and never flips built, so this test can miss accidental double-build behavior in RawRenderer.

🔧 Suggested patch
     buildInto() {
-      return { ok: true, bytes } as const;
+      if (built)
+        return { ok: false, error: { code: "ZRDL_INTERNAL", detail: "built twice" } } as const;
+      built = true;
+      return { ok: true, bytes } as const;
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/app/__tests__/rawRender.test.ts` around lines 37 - 39, The
stub builder's buildInto() always succeeds and never flips the internal built
state, so update the test stub created by makeStubBuilder() so buildInto()
mirrors build()'s state transitions: have buildInto() check the same built flag
and return a failure result when already built, and set built = true on
successful first call (or simply delegate to the existing build() implementation
if present). This ensures RawRenderer double-build behavior is exercised; locate
makeStubBuilder, build(), and buildInto() in the test file and make buildInto
follow build's logic (respecting the built flag and returning the same ok/error
shape).
packages/core/src/perf/frameAudit.ts-95-97 (1)

95-97: ⚠️ Potential issue | 🟡 Minor

Tighten command-stream validation with alignment checks.

cmdStreamValid can currently report true for non-4-byte-aligned command streams. Add alignment checks on cmdOffset, cmdBytes, off, and size to keep audit validity consistent with drawlist format invariants.

Suggested fix
-  if (cmdOffset < 0 || cmdBytes < 0 || cmdOffset + cmdBytes > bytes.byteLength) {
+  if (
+    cmdOffset < 0 ||
+    cmdBytes < 0 ||
+    (cmdOffset & 3) !== 0 ||
+    (cmdBytes & 3) !== 0 ||
+    cmdOffset + cmdBytes > bytes.byteLength
+  ) {
     return Object.freeze({ histogram: Object.freeze({}), valid: false });
   }
@@
-    if (size < 8 || off + size > end) {
+    if ((off & 3) !== 0 || (size & 3) !== 0 || size < 8 || off + size > end) {
       return Object.freeze({ histogram: Object.freeze(hist), valid: false });
     }

Also applies to: 103-110, 115-115

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

In `@packages/core/src/perf/frameAudit.ts` around lines 95 - 97, The validation
branch that sets cmdStreamValid needs 4-byte alignment checks: ensure cmdOffset
and cmdBytes are non-negative, cmdOffset + cmdBytes <= bytes.byteLength, and
both cmdOffset and cmdBytes are 4-byte aligned (e.g. cmdOffset % 4 === 0 &&
cmdBytes % 4 === 0); if any fail return the same frozen invalid result.
Likewise, in the other blocks that validate draw commands (the checks using off
and size around lines handling individual commands), add alignment checks for
off and size (off % 4 === 0 && size % 4 === 0) in addition to their existing
bounds checks and return the frozen invalid result when misaligned so
cmdStreamValid remains consistent with the drawlist format invariants.
packages/core/src/renderer/__tests__/renderer.text.test.ts-165-167 (1)

165-167: ⚠️ Potential issue | 🟡 Minor

Guard OP_FREE_BLOB payload reads with a command-size check.

u32(bytes, off + 8) is executed without validating payload size; malformed frames can throw unexpectedly during parsing.

💡 Suggested fix
     if (cmd.opcode === OP_FREE_BLOB) {
-      const blobId = u32(bytes, off + 8);
-      if (blobId > 0) textRunBlobsByIndex[blobId - 1] = Object.freeze({ segments: Object.freeze([]) });
+      if (cmd.size >= 12) {
+        const blobId = u32(bytes, off + 8);
+        if (blobId > 0) {
+          textRunBlobsByIndex[blobId - 1] = Object.freeze({ segments: Object.freeze([]) });
+        }
+      }
       continue;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/renderer/__tests__/renderer.text.test.ts` around lines 165
- 167, The code reads u32(bytes, off + 8) for OP_FREE_BLOB without ensuring the
command payload is large enough; guard this read by checking the command
size/available bytes before calling u32. Before calling u32(bytes, off + 8)
inside the OP_FREE_BLOB branch, verify that cmd.size (or the available bytes
length from off) is at least 12 (off + 8 + 4) and only then compute blobId and
touch textRunBlobsByIndex; otherwise skip the operation or treat it as a
malformed frame. This uses the existing symbols OP_FREE_BLOB, cmd, bytes, off,
u32, and textRunBlobsByIndex to locate and fix the check.
packages/core/src/widgets/__tests__/basicWidgets.render.test.ts-70-76 (1)

70-76: ⚠️ Potential issue | 🟡 Minor

Fail fast on DRAW_TEXT decode/header desync.

Line 75 currently falls back to "" when decoded[drawTextIndex] is missing, which can mask parser drift and make tests pass for the wrong reason.

Suggested fix
-      const decodedCmd = decoded[drawTextIndex];
+      const decodedCmd = decoded[drawTextIndex];
+      assert.ok(decodedCmd, `missing decoded DRAW_TEXT at index ${String(drawTextIndex)}`);
       drawTextIndex += 1;
@@
-        text: decodedCmd?.text ?? "",
+        text: decodedCmd.text,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/widgets/__tests__/basicWidgets.render.test.ts` around lines
70 - 76, The test currently masks a desync by defaulting to an empty string when
decoded[drawTextIndex] is missing; instead, update the DRAW_TEXT handling in the
test so it fails fast: check that decoded[drawTextIndex] is defined (use the
existing variables decoded and drawTextIndex) and throw a clear error or assert
with context (e.g., include off and drawTextIndex or the expected header/size)
if it's undefined, rather than falling back to "". This ensures parser/header
desyncs are surfaced immediately in basicWidgets.render tests.
packages/core/src/drawlist/__tests__/builder.alignment.test.ts-129-133 (1)

129-133: ⚠️ Potential issue | 🟡 Minor

Use the returned blobIndex instead of hardcoding 0 in drawTextRun.

This test computes blobIndex but ignores it on Line 133, which makes the assertion more fragile than needed.

Suggested fix
 const blobIndex = b.addBlob(new Uint8Array([9, 8, 7, 6]));
 assert.equal(blobIndex, 0);
+if (blobIndex === null) throw new Error("expected blob index");
 b.clear();
 b.drawText(0, 0, "x");
-b.drawTextRun(2, 1, 0);
+b.drawTextRun(2, 1, blobIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/drawlist/__tests__/builder.alignment.test.ts` around lines
129 - 133, The test computes a blobIndex via b.addBlob(...) but then hardcodes 0
when calling b.drawTextRun(2, 1, 0); update the call to use the computed
blobIndex instead of 0 (i.e., call b.drawTextRun(2, 1, blobIndex)) so the test
uses the actual returned value from addBlob and is not fragile; verify this
change in the test that uses addBlob, blobIndex, drawText, and drawTextRun.
examples/regression-dashboard/src/screens/overview.ts-60-61 (1)

60-61: ⚠️ Potential issue | 🟡 Minor

Use state timestamps instead of Date.now() during render.

Using wall-clock time in render makes the screen non-deterministic and can cause noisy/flickery update-rate output.

🛠️ Proposed fix
-  const uptimeSec = Math.max(1, Math.floor((Date.now() - state.startedAtMs) / 1000));
+  const elapsedMs = Math.max(0, state.lastUpdatedMs - state.startedAtMs);
+  const uptimeSec = Math.max(1, Math.floor(elapsedMs / 1000));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/regression-dashboard/src/screens/overview.ts` around lines 60 - 61,
Replace the use of Date.now() with the renderable timestamp from state so the UI
is deterministic: compute uptimeSec as Math.max(1, Math.floor((state.nowMs -
state.startedAtMs) / 1000)) (using state.nowMs instead of Date.now()), and then
compute updateRate from state.tick / uptimeSec; ensure you reference the
existing symbols uptimeSec, updateRate, state.startedAtMs and state.nowMs so the
component uses the state-provided time source.
packages/core/src/__tests__/drawlistDecode.ts-263-269 (1)

263-269: ⚠️ Potential issue | 🟡 Minor

Add explicit DEF payload bounds checks in command decoders.

These branches currently rely on subarray(...), which truncates silently on malformed lengths. That can mask protocol corruption in tests.

🔧 Suggested patch
       case OP_DEF_STRING: {
+        if (cmd.size < 16) {
+          throw new Error(`DEF_STRING too small at offset ${String(cmd.offset)} size=${String(cmd.size)}`);
+        }
         const id = u32(bytes, cmd.offset + 8);
         const byteLen = u32(bytes, cmd.offset + 12);
         const dataStart = cmd.offset + 16;
         const dataEnd = dataStart + byteLen;
+        if (dataEnd > cmd.offset + cmd.size) {
+          throw new Error(
+            `DEF_STRING payload overflow at offset ${String(cmd.offset)} (len=${String(byteLen)}, size=${String(cmd.size)})`,
+          );
+        }
         strings.set(id, cloneBytes(bytes.subarray(dataStart, dataEnd)));
         break;
       }
@@
       case OP_DEF_BLOB: {
+        if (cmd.size < 16) {
+          throw new Error(`DEF_BLOB too small at offset ${String(cmd.offset)} size=${String(cmd.size)}`);
+        }
         const id = u32(bytes, cmd.offset + 8);
         const byteLen = u32(bytes, cmd.offset + 12);
         const dataStart = cmd.offset + 16;
         const dataEnd = dataStart + byteLen;
+        if (dataEnd > cmd.offset + cmd.size) {
+          throw new Error(
+            `DEF_BLOB payload overflow at offset ${String(cmd.offset)} (len=${String(byteLen)}, size=${String(cmd.size)})`,
+          );
+        }
         blobs.set(id, cloneBytes(bytes.subarray(dataStart, dataEnd)));
         break;
       }

Also applies to: 316-322

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

In `@packages/core/src/__tests__/drawlistDecode.ts` around lines 263 - 269, The
OP_DEF_STRING decoder branch (and the similar branch around lines 316-322)
currently uses subarray and can silently truncate malformed payloads; update the
decoder to validate payload bounds before slicing: compute id = u32(bytes,
cmd.offset + 8), byteLen = u32(bytes, cmd.offset + 12) and dataStart =
cmd.offset + 16, then check that byteLen is non-negative and that dataEnd =
dataStart + byteLen is <= bytes.length (and >= dataStart) and that cmd.offset +
16 is within the buffer; if any check fails, throw a descriptive error (rather
than calling subarray), otherwise use cloneBytes(bytes.subarray(dataStart,
dataEnd)) and set the string. Ensure the same explicit bounds checks are applied
to the other DEF payload decoder(s) referenced.
packages/core/src/runtime/commit.ts-1788-1800 (1)

1788-1800: ⚠️ Potential issue | 🟡 Minor

Remove debug tracing code before merge.

The comment explicitly states "Temporary debug: trace commit matching (remove after investigation)". This debug infrastructure adds runtime overhead on every commitNode call (globalThis property lookup and type cast) even when disabled. Given that this is the core reconciliation hot path, this should be removed before merging to main.

🗑️ Proposed fix: remove debug code
-    // Temporary debug: trace commit matching (remove after investigation)
-    const commitDebug = globalThis as Record<string, unknown> & {
-      __commitDebug?: unknown;
-      __commitDebugLog?: string[] | undefined;
-    };
-    if (commitDebug.__commitDebug) {
-      const debugLog = commitDebug.__commitDebugLog;
-      if (debugLog) {
-        debugLog.push(
-          `commitNode(${String(instanceId)}, ${vnode.kind}, prev=${prev ? `${prev.vnode.kind}:${String(prev.instanceId)}` : "null"})`,
-        );
-      }
-    }
-
     // Leaf nodes — fast path: reuse previous RuntimeInstance when content is unchanged.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/runtime/commit.ts` around lines 1788 - 1800, Remove the
temporary debug tracing block in commitNode that inspects globalThis for
__commitDebug and pushes strings into __commitDebugLog; specifically delete the
commitDebug declaration and the if (commitDebug.__commitDebug) { ... } branch
that references __commitDebug, __commitDebugLog, instanceId, vnode, and prev so
the commitNode hot path no longer performs globalThis lookups or log pushes.
packages/core/src/runtime/commit.ts-1334-1386 (1)

1334-1386: ⚠️ Potential issue | 🟡 Minor

In-place RuntimeInstance mutation is intentional but requires full test verification.

The mutation pattern on lines 1337–1338 and 1381–1382 is a deliberate performance optimization to preserve reference identity and prevent cascading parent updates. While RuntimeInstance.vnode can be reassigned (it's not marked readonly), VNode itself is immutable (Readonly<...> structure), so mutations only update the reference, not VNode contents.

Downstream code (layout, renderer, widgetMeta) reads from .vnode properties transiently without caching references, and tests explicitly verify this behavior (see commit.fastReuse.regression.test.ts). This pattern is safe in practice.

Critical requirement: After any changes to this mutation logic, run the full test suite with node scripts/run-tests.mjs before committing. Changes to reconciliation/commit paths are a danger zone—subtle regressions are common and must be caught by comprehensive testing.

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

In `@packages/core/src/runtime/commit.ts` around lines 1334 - 1386, The code
mutates RuntimeInstance.vnode and children references in-place (see
rewriteCommittedVNode, canFastReuseContainerSelf, and assignments to prev.vnode
and prev.children) as a deliberate fast-reuse optimization; keep that behavior
but ensure test coverage and run the full test suite. Preserve the in-place
mutation logic: when fast-reusing, assign prev.vnode =
rewriteCommittedVNode(...) and prev.children = nextChildren and set
prev.selfDirty/prev.dirty appropriately (as done in the fast-reuse and in-place
mutation branches referencing prev, vnode, prevChildren, nextChildren,
committedChildVNodes); do not make vnode or children readonly or replace with
new RuntimeInstance objects, and after any edits to these branches run node
scripts/run-tests.mjs to verify commit/reconciliation/regression tests (e.g.,
commit.fastReuse.regression.test.ts) pass. Ensure commit diagnostic
(__commitDiag) updates remain correct when toggling these paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/regression-dashboard/src/main.ts`:
- Around line 167-176: The current uncaughtException and unhandledRejection
handlers only set process.exitCode and may let the event loop keep the process
alive; change both handlers (the process.on("uncaughtException", ...) and
process.on("unhandledRejection", ...) blocks) to force synchronous/immediate
termination after logging by calling process.exit(1) (or calling process.exit in
a next tick if you must flush logs, e.g., stderrLog(...); setImmediate(() =>
process.exit(1))); apply the same change to the duplicate handler block
referenced at lines 183-188 so the process cannot continue running after these
fatal conditions.

In `@packages/core/src/perf/perf.ts`:
- Around line 232-234: The count method on the Perf tracker allows arbitrary
names which can make this.counters grow unbounded; modify count(name: string,
delta = 1) to guard cardinality by enforcing a maximum distinct key count (e.g.,
MAX_COUNTER_KEYS constant) and a fallback for new keys once the limit is reached
(such as incrementing a special "__other__" bucket or
rejecting/high-frequency-sampled keys), and ensure snapshot() and any consumers
handle the fallback key; update references to this.counters, the count method,
and snapshot to use the capped behavior and add clear tests for hitting the cap.

In `@packages/core/src/renderer/renderToDrawlist/overflowCulling.ts`:
- Around line 48-51: The transparent-wrapper check in
isTransparentOverflowWrapper is too narrow and must include the renderer's
transparent container kinds so subtreeCanOverflowBounds can descend into them;
update isTransparentOverflowWrapper (and any logic in subtreeCanOverflowBounds)
to also treat vnode.kind === "layers" as transparent and treat vnode.kind ===
"layer" as transparent when that layer is in pass-through/transparent mode
(check the layer vnode props that indicate pass-through/transparent mode), so
overflowing descendants inside "layers" and pass-through "layer" are not
short-circuited.

In `@packages/core/src/renderer/shadow.ts`:
- Around line 47-51: DEFAULT_SHADOW.color is currently rgb(0,0,0) which packs to
numeric 0 and gets suppressed by the truthy checks in styleToSgr
(style.fg/style.bg), so change DEFAULT_SHADOW.color to a non-zero dark value
(e.g. rgb(1,1,1)) in the DEFAULT_SHADOW object to ensure the shadow color is
emitted; alternatively, adjust styleToSgr to treat packed zero as a valid color,
but the quick fix is updating DEFAULT_SHADOW.color to rgb(1,1,1).

In `@packages/core/src/theme/heatmapPalettes.ts`:
- Around line 45-47: The first grayscale stop currently uses rgb(0, 0, 0) which
packs to the reserved sentinel value 0 (unset/default); update the start stop in
the grayscale Object.freeze array so it does not use the zero sentinel (e.g.,
replace rgb(0, 0, 0) with a non-zero near-black like rgb(1, 1, 1) or another
minimally different value), leaving the t:0 entry and the rest of the array
unchanged; update any tests/comments referencing explicit black if present.

In `@packages/core/src/theme/interop.ts`:
- Around line 142-147: setColor currently produces numeric Rgb24 values where 0
is a valid sentinel, but consumers may be using truthy checks (e.g., if (base) /
if (primary)), which will drop 0; update all consumers of setColor’s return and
alias wiring to test explicitly for undefined (e.g., value === undefined or
value !== undefined) or use typeof value === 'number' instead of truthiness, and
ensure any aliasing logic that reads from the out map (the Record<string, Rgb24>
named out, keys referenced by key) treats 0 as a valid color rather than falsy
so aliases are not left stale.
- Around line 92-93: isRgb currently returns true for any finite number, letting
non-integer or out-of-range values slip into Theme.colors via flat overrides;
change the type guard isRgb(v: unknown): v is Rgb24 to only accept integers in
the 0–255 range (Number.isInteger(v) && v >= 0 && v <= 255) so only valid 8‑bit
channel values are treated as Rgb24; also ensure downstream
normalization/packing code that assumes packed RGB still runs for values that
fail this guard.

In `@packages/core/src/widgets/heatmap.ts`:
- Around line 23-27: The heatmap may emit the sentinel Rgb24 value 0 (0x000000)
which ink-compat treats as "unset"; update buildScaleTable and any color
interpolation/packing code that produces Rgb24 (e.g., where left/right stops are
chosen and where interpolated rgb values are packed) to guarantee the packed
Rgb24 is never 0: replace any default black stop rgb(0,0,0) with a non-zero
sentinel (or clamp/offset the final packed value to at least 1) and ensure the
packRgb/serialize path post-interpolation enforces value !== 0 so explicit black
is represented without using the reserved zero sentinel.

In `@packages/create-rezi/templates/starship/src/main.ts`:
- Around line 606-621: setupDebugTrace currently awaits
debugController.enable/reset and will throw on failure, which can block startup
since it's called before app.start; modify setupDebugTrace (and the same pattern
at the other occurrence) to guard all async debug calls (debugController.enable
and debugController.reset) with try/catch, log or processLogger.error the
failure including the error object, and ensure the function returns gracefully
(disable/cleanup the debugController or set a flag like debugController =
undefined) rather than rethrowing so app.start is never prevented by debug
instrumentation errors. Also ensure callers (where setupDebugTrace is awaited)
tolerate a non-throwing, failed debug initialization (i.e., proceed even if
debugController is unset).

---

Outside diff comments:
In `@packages/core/src/drawlist/__tests__/builder.cursor.test.ts`:
- Around line 1-10: Update the top JSDoc to match the actual assertion: change
the header note from "Correct v2 header version" to "Correct v1 header version"
(or otherwise reflect V1) so it aligns with the test that checks
ZR_DRAWLIST_VERSION_V1 in builder.cursor.test.ts; locate the JSDoc block at the
top of the file and edit the sentence to reference v1 header version.

In `@packages/core/src/widgets/__tests__/graphicsWidgets.test.ts`:
- Around line 471-483: The test "image invalid source falls back without
DRAW_IMAGE" is constructing a raw VNode literal; replace that literal with the
widget factory call ui.image(...) so the test uses the same invariant as other
tests (see ui.image used on line 461). Update the renderBytes invocation to pass
ui.image({ src: "bad-bytes" as unknown as Uint8Array, width: 20, height: 4, alt:
"Broken image" }) instead of the { kind: "image", props: { ... } } object and
keep the createDrawlistBuilder() argument unchanged.
- Around line 114-147: The test uses the low-level pipeline functions
(renderBytes calling commitVNodeTree, layout, renderToDrawlist and
builder.build) instead of the required testing API; replace the custom
renderBytes path with the testing helper createTestRenderer() so the test
renders via the module entrypoint: locate the renderBytes helper and update
tests to call createTestRenderer() (or refactor renderBytes to wrap
createTestRenderer()) so commit/layout/render are driven by createTestRenderer
rather than directly invoking commitVNodeTree, layout, or renderToDrawlist;
ensure the viewport and terminalProfile parameters are passed through the
createTestRenderer invocation to preserve existing behavior.

In `@packages/ink-compat/src/translation/colorMap.ts`:
- Around line 4-22: Named "black" and other explicit-black parse paths currently
produce rgb(0,0,0) which collides with the unset sentinel; update the color
normalization so any explicit-black result is mapped to near-black (0x010101)
before returning. Concretely: adjust NAMED_COLORS (symbol) or apply a post-parse
normalization in parseColorInner (all return points that return rgb(0,0,0) from
named, hex, rgb-literal branches) to convert {r:0,g:0,b:0} to {r:1,g:1,b:1}, and
do the same in decodeAnsi256Color for the case that returns black (ansi256 0) so
callers like styleToSgr will see a non-falsy color and emit SGR codes.

---

Minor comments:
In `@examples/regression-dashboard/src/screens/overview.ts`:
- Around line 60-61: Replace the use of Date.now() with the renderable timestamp
from state so the UI is deterministic: compute uptimeSec as Math.max(1,
Math.floor((state.nowMs - state.startedAtMs) / 1000)) (using state.nowMs instead
of Date.now()), and then compute updateRate from state.tick / uptimeSec; ensure
you reference the existing symbols uptimeSec, updateRate, state.startedAtMs and
state.nowMs so the component uses the state-provided time source.

In `@packages/core/src/__tests__/drawlistDecode.ts`:
- Around line 263-269: The OP_DEF_STRING decoder branch (and the similar branch
around lines 316-322) currently uses subarray and can silently truncate
malformed payloads; update the decoder to validate payload bounds before
slicing: compute id = u32(bytes, cmd.offset + 8), byteLen = u32(bytes,
cmd.offset + 12) and dataStart = cmd.offset + 16, then check that byteLen is
non-negative and that dataEnd = dataStart + byteLen is <= bytes.length (and >=
dataStart) and that cmd.offset + 16 is within the buffer; if any check fails,
throw a descriptive error (rather than calling subarray), otherwise use
cloneBytes(bytes.subarray(dataStart, dataEnd)) and set the string. Ensure the
same explicit bounds checks are applied to the other DEF payload decoder(s)
referenced.

In `@packages/core/src/app/__tests__/rawRender.test.ts`:
- Around line 37-39: The stub builder's buildInto() always succeeds and never
flips the internal built state, so update the test stub created by
makeStubBuilder() so buildInto() mirrors build()'s state transitions: have
buildInto() check the same built flag and return a failure result when already
built, and set built = true on successful first call (or simply delegate to the
existing build() implementation if present). This ensures RawRenderer
double-build behavior is exercised; locate makeStubBuilder, build(), and
buildInto() in the test file and make buildInto follow build's logic (respecting
the built flag and returning the same ok/error shape).

In `@packages/core/src/drawlist/__tests__/builder.alignment.test.ts`:
- Around line 129-133: The test computes a blobIndex via b.addBlob(...) but then
hardcodes 0 when calling b.drawTextRun(2, 1, 0); update the call to use the
computed blobIndex instead of 0 (i.e., call b.drawTextRun(2, 1, blobIndex)) so
the test uses the actual returned value from addBlob and is not fragile; verify
this change in the test that uses addBlob, blobIndex, drawText, and drawTextRun.

In `@packages/core/src/perf/frameAudit.ts`:
- Around line 95-97: The validation branch that sets cmdStreamValid needs 4-byte
alignment checks: ensure cmdOffset and cmdBytes are non-negative, cmdOffset +
cmdBytes <= bytes.byteLength, and both cmdOffset and cmdBytes are 4-byte aligned
(e.g. cmdOffset % 4 === 0 && cmdBytes % 4 === 0); if any fail return the same
frozen invalid result. Likewise, in the other blocks that validate draw commands
(the checks using off and size around lines handling individual commands), add
alignment checks for off and size (off % 4 === 0 && size % 4 === 0) in addition
to their existing bounds checks and return the frozen invalid result when
misaligned so cmdStreamValid remains consistent with the drawlist format
invariants.

In `@packages/core/src/perf/perf.ts`:
- Around line 247-249: The current dynamic materialization uses a plain object
for counters which can collide with inherited properties; change the local
counters map in the method that reads this.counters to use a null-prototype
object (create with Object.create(null) and cast to Record<string, number> as
needed) instead of {} so keys are safe, then populate it from this.counters and
return as before with Object.freeze(phases) and Object.freeze(counters); update
the declaration of the local counters variable referenced in this function to
use the null-prototype creation.

In `@packages/core/src/renderer/__tests__/renderer.text.test.ts`:
- Around line 165-167: The code reads u32(bytes, off + 8) for OP_FREE_BLOB
without ensuring the command payload is large enough; guard this read by
checking the command size/available bytes before calling u32. Before calling
u32(bytes, off + 8) inside the OP_FREE_BLOB branch, verify that cmd.size (or the
available bytes length from off) is at least 12 (off + 8 + 4) and only then
compute blobId and touch textRunBlobsByIndex; otherwise skip the operation or
treat it as a malformed frame. This uses the existing symbols OP_FREE_BLOB, cmd,
bytes, off, u32, and textRunBlobsByIndex to locate and fix the check.

In `@packages/core/src/runtime/commit.ts`:
- Around line 1788-1800: Remove the temporary debug tracing block in commitNode
that inspects globalThis for __commitDebug and pushes strings into
__commitDebugLog; specifically delete the commitDebug declaration and the if
(commitDebug.__commitDebug) { ... } branch that references __commitDebug,
__commitDebugLog, instanceId, vnode, and prev so the commitNode hot path no
longer performs globalThis lookups or log pushes.
- Around line 1334-1386: The code mutates RuntimeInstance.vnode and children
references in-place (see rewriteCommittedVNode, canFastReuseContainerSelf, and
assignments to prev.vnode and prev.children) as a deliberate fast-reuse
optimization; keep that behavior but ensure test coverage and run the full test
suite. Preserve the in-place mutation logic: when fast-reusing, assign
prev.vnode = rewriteCommittedVNode(...) and prev.children = nextChildren and set
prev.selfDirty/prev.dirty appropriately (as done in the fast-reuse and in-place
mutation branches referencing prev, vnode, prevChildren, nextChildren,
committedChildVNodes); do not make vnode or children readonly or replace with
new RuntimeInstance objects, and after any edits to these branches run node
scripts/run-tests.mjs to verify commit/reconciliation/regression tests (e.g.,
commit.fastReuse.regression.test.ts) pass. Ensure commit diagnostic
(__commitDiag) updates remain correct when toggling these paths.

In `@packages/core/src/widgets/__tests__/basicWidgets.render.test.ts`:
- Around line 70-76: The test currently masks a desync by defaulting to an empty
string when decoded[drawTextIndex] is missing; instead, update the DRAW_TEXT
handling in the test so it fails fast: check that decoded[drawTextIndex] is
defined (use the existing variables decoded and drawTextIndex) and throw a clear
error or assert with context (e.g., include off and drawTextIndex or the
expected header/size) if it's undefined, rather than falling back to "". This
ensures parser/header desyncs are surfaced immediately in basicWidgets.render
tests.

In `@packages/core/src/widgets/__tests__/style.merge-fuzz.test.ts`:
- Around line 25-29: The test removed a value-level assertion for the packed
attrs field after making MutableResolvedTextStyle.attrs optional, which allows
regressions to slip by; restore an independent expected-attrs calculation in the
fuzz test and assert equality against the actual attrs value (not just its
type). Locate the assertions that check resolved text style boolean fields and
the MutableResolvedTextStyle shape (references: MutableResolvedTextStyle and the
test block that computes expected resolved style) and add code to compute
expectedAttrs from the boolean flags the same way production does, then assert
expectedAttrs === actual.attrs (or treat undefined as 0 if the code packs that
way) in addition to the existing boolean and type checks. Ensure this explicit
packed-attrs verification is added in the same test cases that validate boolean
fields so regressions in drawlist/packing are caught.

In `@packages/create-rezi/templates/starship/src/main.ts`:
- Around line 629-649: The polling loop started by debugTraceTimer can overlap
calls to pump because setInterval fires regardless of pump completion; fix by
adding an in-flight guard or switching to a self-scheduling async loop so only
one pump runs at a time: introduce a boolean flag (e.g., debugPumpInFlight) or a
mutex checked/set at the start of pump and cleared on exit (including in
catch/finally), or replace the setInterval with an async function that awaits
pump then uses setTimeout to schedule the next run; ensure you reference and
update the same shared state variables used in pump (debugController,
debugLastRecordId, snapshotDebugRecord) and still clear the timer/stop the loop
when debugging is cancelled.

---

Nitpick comments:
In `@examples/regression-dashboard/src/helpers/formatters.ts`:
- Around line 38-45: The fleetCounts function does three separate array filters
causing redundant scans; refactor fleetCounts to iterate services once (e.g., a
single for loop or reduce) and increment counters for healthy/warning/down based
on service.status, then return Object.freeze({ healthy, warning, down });
reference the fleetCounts function and the Service.status checks to locate where
to replace the three filters with a single-pass accumulation.

In `@examples/regression-dashboard/src/helpers/state.ts`:
- Around line 123-137: createInitialState currently assigns SEED_SERVICES
directly to the services field, risking shared mutable references between state
instances; change createInitialState to initialize services with a deep copy of
SEED_SERVICES (e.g., map over SEED_SERVICES and clone each service object or use
structuredClone) so each DashboardState gets its own independent service
objects; update references in createInitialState, and ensure DashboardState
consumers expect the cloned shape rather than the original shared objects.

In `@examples/regression-dashboard/src/theme.ts`:
- Around line 18-22: THEME_BY_NAME is a constant configuration object that
should be protected from accidental mutation; freeze the registry after creation
by applying Object.freeze to THEME_BY_NAME (and optionally Object.freeze each
value inside THEME_BY_NAME to make the ThemeSpec entries immutable) so consumers
cannot modify the map or its entries; update where THEME_BY_NAME is
defined/initialized to call Object.freeze(THEME_BY_NAME) (and Object.freeze on
each THEME_BY_NAME[key]) to enforce immutability.

In `@packages/core/src/app/createApp.ts`:
- Around line 596-603: The conditional checking backendDrawlistVersion is
redundant because readBackendDrawlistVersionMarker() already throws for non-1
values and returns 1 | null; remove the entire if block that inspects
backendDrawlistVersion and calls invalidProps (the code referencing
backendDrawlistVersion and invalidProps) so only the call to
readBackendDrawlistVersionMarker(backend) remains (or remove the assignment if
unused), leaving no duplicate validation logic.

In `@packages/core/src/app/widgetRenderer.ts`:
- Around line 587-594: The hardcoded debug strings in the needles array used by
the audit helper (the const needles = [...] block) and gated under
FRAME_AUDIT_TREE_ENABLED should be made configurable or explicitly documented:
extract the array into a single exported constant or configuration provider
(e.g., getAuditNeedles or AUDIT_NEEDLES) and load it from a config/env or from a
top-level constant with a descriptive name and JSDoc explaining their purpose,
or add a clear inline comment above FRAME_AUDIT_TREE_ENABLED usage describing
that these values are debug/test-only and how to change them; update references
in widgetRenderer.ts (where needles and FRAME_AUDIT_TREE_ENABLED are used) to
consume the new config/export.
- Around line 761-762: The BFS loop currently uses array.shift() which is O(n)
per pop; change the loop to an index-based queue by introducing a head/index
variable (e.g., let head = 0) and iterate while head < queue.length, reading the
current item via queue[head++] instead of queue.shift(); update any places that
assumed shift's return value and ensure children are still pushed onto queue as
before (or alternatively swap to a dedicated queue class), referencing the
existing queue variable and the while (queue.length > 0) { const current =
queue.shift(); } block in widgetRenderer.ts.
- Around line 2860-2936: Wrap the entire fallback sequence (the block guarded by
doCommit && shapeMismatch !== null that resets this._layoutTreeCache and runs
layout/fallbackLayoutRes/directLayoutRes) with lightweight perf markers via
emitFrameAudit when FRAME_AUDIT_ENABLED is true: emit a "layout.fallback.start"
event immediately before resetting this._layoutTreeCache (include context fields
like path/depth/runtimeKind/layoutKind) and emit a "layout.fallback.end" event
after the final shapeMismatch check (or on each early return) that includes
attemptsPerformed (1/2/3), duration (timestamp difference), and which final
layoutRes used (initial/fallback/direct). Use the existing symbols
shapeMismatch, layout, fallbackLayoutRes, directLayoutRes, emitFrameAudit, and
FRAME_AUDIT_ENABLED to locate and instrument the code so you can monitor
frequency and duration of the fallback paths without changing the core logic.

In `@packages/core/src/drawlist/__tests__/builder_v6_resources.test.ts`:
- Line 1: Rename the test file to remove the "v6" protocol reference (e.g.,
rename builder_v6_resources.test.ts → builder.resources.test.ts) and update any
test runner / import references to this file so the test suite still discovers
it; ensure the top-level test declarations (describe/test) in this file remain
unchanged and that any CI or test patterns that filter by filename are adjusted
to accept the new name.

In `@packages/core/src/drawlist/__tests__/writers.gen.v6.test.ts`:
- Around line 74-88: Add a complementary test where the declared byteLen exceeds
the source payload length to assert that the writer zero-fills the tail: create
a bytes buffer (Uint8Array) initialized with 0xcc, a DataView, a short payload
(e.g., 3 bytes), call writeDefString with a larger declared length (e.g., 9) and
assert the returned end index, the u32 size fields (u32(bytes, 4), u32(bytes,
12)), that the payload bytes are written and that the remaining declared payload
bytes are zero-filled (check subarray region for zeros), and that the byte after
the declared region remains 0xcc; repeat the same complementary “declared length
> source length” test for each corresponding writer variant referenced in this
test file (e.g., other DEF_* writers) to cover the regression.

In `@packages/core/src/drawlist/writers.gen.ts`:
- Around line 260-261: The generated writer should validate the string length in
the generator template (scripts/drawlist-spec.ts) before emitting code that sets
payloadBytes and size; update the template so that instead of directly doing
"const payloadBytes = byteLen >>> 0;" it first checks that byteLen is a finite
number, non-negative, and within the uint32 range (e.g. Number.isFinite(byteLen)
&& byteLen >= 0 && byteLen <= 0xFFFFFFFF) and throw a RangeError for invalid
values, then compute payloadBytes = byteLen >>> 0 and compute size =
align4(DEF_STRING_BASE_SIZE + payloadBytes); ensure the emitted symbols in
writers.gen.ts remain payloadBytes, byteLen, align4, DEF_STRING_BASE_SIZE and
size so callers get a clear, defensive validation at generation time.

In `@packages/core/src/layout/engine/layoutEngine.ts`:
- Around line 59-76: The __layoutProfile object is exported from core runtime
but is temporary and should be confined to perf/test-only surfaces to avoid API
leakage; make __layoutProfile non-exported (or move it into a dedicated dev/perf
module) and provide a dev-only accessor (e.g. getLayoutProfile()) that only
exports or returns the profile when a build/dev flag or NODE_ENV !==
'production' (or an explicit ENABLE_LAYOUT_PROFILE flag) is set, update any
references to use the accessor, and remove the public export so production
builds have no runtime coupling to __layoutProfile.

In `@packages/core/src/perf/perf.ts`:
- Around line 309-312: The perfSnapshot function currently allocates and freezes
new objects on every disabled call; create a module-level frozen singleton
(e.g., const DISABLED_PERF_SNAPSHOT = Object.freeze({ phases: Object.freeze({}),
counters: Object.freeze({}) })) and return that singleton when PERF_ENABLED is
false inside perfSnapshot; update references in perfSnapshot to return
DISABLED_PERF_SNAPSHOT to avoid repeated allocations and preserve immutability.

In `@packages/core/src/pipeline.ts`:
- Around line 34-35: Remove the temporary profiling export from the stable
barrel by deleting the exported symbol __layoutProfile from pipeline.ts and
instead re-export it from an explicitly-internal/unstable entrypoint (e.g., an
internal profiling module); update any internal callers to import
__layoutProfile from that new internal entrypoint (not from pipeline.ts) so the
symbol is not leaked as part of the public API. Ensure the new internal module
clearly names the export as unstable/internal and do not add it back into the
stable barrel export list.

In `@packages/core/src/renderer/__tests__/renderer.partial.test.ts`:
- Around line 64-73: The test's DrawlistBuilder stub methods (setCursor,
hideCursor, setLink, drawCanvas, drawImage) are no-ops so the test can't detect
mismatches; update each stub to record an operation into the builder's recording
structure (same ops array used elsewhere in this test harness) by pushing an
object or tuple that includes the operation name (e.g., "setCursor",
"hideCursor", "setLink", "drawCanvas", "drawImage") and the received arguments
(use ..._args) so the partial-render test can compare emitted ops against the
full plan and surface blind spots in cursor/link/canvas/image emission.

In `@packages/core/src/renderer/__tests__/textStyle.opacity.test.ts`:
- Line 2: The tests currently call the production helper rgbBlend inside
assertions, coupling the oracle to implementation; update the tests in
textStyle.opacity.test.ts to use explicit expected RGB24 values (or a simple
test-local blend helper) instead of rgbBlend when computing expected results for
assertions that reference rgbBlend (also update the occurrences around the other
noted lines); modify the assertions to compare against hard-coded RGB triplets
(or the tiny local function) and remove imports/uses of rgbBlend from the test
so the expected-value logic is independent from the production rgbBlend
implementation.

In `@packages/core/src/renderer/renderToDrawlist/renderTree.ts`:
- Around line 131-135: subtreeCanOverflowBounds is being recomputed many times
during a single damage traversal; add a per-frame memoization layer (e.g., a Map
or WeakMap keyed by the node) and replace direct calls to
subtreeCanOverflowBounds(...) inside renderTree traversal with a cached wrapper
(name it something like subtreeCanOverflowBoundsCached(node)). Initialize/clear
the cache at the start of the frame/damage traversal (where frame state is
prepared) so results are only reused within one traversal, and ensure the
wrapper delegates to the original subtreeCanOverflowBounds when the entry is
missing and stores the result before returning.

In `@packages/core/src/renderer/renderToDrawlist/widgets/renderCanvasWidgets.ts`:
- Line 353: The fallback "?? 0" after hashImageBytes is redundant because
hashImageBytes returns a number; update the imageId assignment in
renderCanvasWidgets.ts (the const imageId line using readNonNegativeInt and
hashImageBytes) to remove the unreachable "?? 0" fallback so the expression
becomes readNonNegativeInt(props.imageId) ?? hashImageBytes(analyzed.bytes).
- Around line 225-227: The helper addBlobAligned now just forwards to
builder.addBlob(bytes) but its name implies alignment semantics that no longer
exist; either rename the function to something explicit like addBlob (or remove
it entirely) and replace all callers to call builder.addBlob(bytes) directly, or
inline the call sites and delete addBlobAligned; update all references to
addBlobAligned (including other occurrences) and adjust imports/exports
accordingly so there are no dangling references to addBlobAligned.

In `@packages/core/src/testing/renderer.ts`:
- Around line 176-188: RecordingDrawlistBuilder currently leaves
cursor/link/canvas/image methods as no-ops so tests don't record those ops;
implement these methods (setCursor, hideCursor, setLink, drawCanvas, drawImage)
to append appropriate op records to the builder's internal operations list using
the same shape used by existing op methods, and update buildInto to serialize
into the provided _dst (or call build and copy the resulting bytes into _dst) so
tests can observe the recorded ops; locate RecordingDrawlistBuilder, its
internal ops array and the build() method to mirror their encoding/recording
pattern for these new methods.

In `@packages/core/src/theme/__tests__/theme.extend.test.ts`:
- Around line 141-145: The test currently asserts
Object.isFrozen(extended.colors.accent.primary) but that is tautological because
packed Rgb24 colors are JS primitives and Object.isFrozen always returns true
for primitives; update the test to remove that primitive-freeze assertion and
instead verify the freeze behavior of the containing object and the color value
itself by asserting Object.isFrozen(extended.colors.accent) is true, asserting
typeof extended.colors.accent.primary === "number" (or comparing to the expected
numeric packed color), and verifying equality to the expected color value so the
test proves both immutability of the container and correctness of the packed
color rather than relying on Object.isFrozen on a primitive.

In `@packages/core/src/theme/resolve.ts`:
- Around line 191-223: The function isValidColorPath recreates the validPaths
Set on every call; extract the array/set into a module-level constant (e.g.,
const VALID_COLOR_PATHS: ReadonlySet<string> or build it from a const array) and
have isValidColorPath(path: string): path is ColorPath simply return
VALID_COLOR_PATHS.has(path); keep the same type (ReadonlySet<string>) and the
same element strings so behavior does not change.

In `@packages/core/src/ui/recipes.ts`:
- Around line 40-42: The adjustBrightness function currently uses 0 as the black
target which collides with the Rgb24 sentinel; change the darkening target from
0 to a near-black floor (e.g., 0x010101 or (1<<16)|(1<<8)|1) so extreme
darkening blends toward an explicit near-black instead of the sentinel; update
the target calculation in adjustBrightness and keep using blendRgb(color,
target, Math.abs(amount)) so other behavior remains unchanged.

In `@packages/core/src/widgets/__tests__/style.merge-fuzz.test.ts`:
- Around line 58-64: Don't call mergeTextStyle from the fuzz helper; instead
construct the fuzz base style locally so the test oracle is independent from the
SUT. Create a plain object by copying the relevant DEFAULT_BASE_STYLE fields
(fg, bg and any explicit keys you need), set fg/bg via randomRgb(rng) and then
assign BOOL_ATTRS entries explicitly based on rng.u32() % 3 (leave
undefined/omit when state === 0, set false/true for 1/2). Do the same change for
the other helper at lines 68-74 so both fuzz bases are built locally and not via
mergeTextStyle.

In `@packages/core/src/widgets/commandPalette.ts`:
- Around line 284-290: Replace the hardcoded PALETTE_COLORS constant with
theme-driven tokens: add command-palette color tokens to your theme interface
(names matching the existing keys: background, border, selectedBg, highlight,
placeholder) and register default values equal to the current rgb(...) values;
then update code that references PALETTE_COLORS to read from the theme (e.g.,
theme.getToken or theme.commandPalette.*) so the palette colors come from the
theme system rather than the PALETTE_COLORS constant (keep the rgb(...) defaults
when registering the tokens).

In `@packages/core/src/widgets/field.ts`:
- Line 11: Replace the hardcoded RGB usage introduced via the rgb import in
packages/core/src/widgets/field.ts with a semantic theme token: find where
rgb(255, 0, 0) (or similar hardcoded rgb(...) calls) is used for the error
foreground in this file (e.g., within Field, FieldLabel, or whatever component
uses the error foreground) and swap it for a theme token accessor like
theme.colors.error or getToken("color.error") (use your app's token API and
prefer "error" or "danger" as the token name); update imports to pull the
theme/token helper instead of rgb and ensure the component consumes the token
value rather than a literal RGB.

In `@packages/core/src/widgets/toast.ts`:
- Around line 173-181: TOAST_COLORS is computed once from defaultTheme.colors so
it won't reflect runtime theme changes; change the static TOAST_COLORS const
into a function (e.g., getToastColors(theme) or toastColorsFor(theme)) that
reads colors from the provided theme (use the same keys:
"widget.toast.info"/info, "widget.toast.success"/success, etc.), and update all
call sites that reference TOAST_COLORS to call this function with the current
theme (similar to logLevelToThemeColor in editors.ts) so toast border colors
update when the active theme changes.

In `@packages/ink-compat/src/__tests__/translation/propsToVNode.test.ts`:
- Around line 304-310: Add a negative assertion to the test to ensure
marker-based flex-basis is not present: in the same block that checks
vnode.props.width/height/flexBasis and the
__inkPercentMinWidth/__inkPercentMinHeight markers, assert that
vnode.props.__inkPercentFlexBasis is undefined (or otherwise not present) so the
test fails if a marker-based flex-basis regresses back in; update the assertion
near the existing checks for vnode.props.flexBasis and
__inkPercentMinWidth/__inkPercentMinHeight.

In `@packages/ink-compat/src/translation/propsToVNode.ts`:
- Around line 495-502: The NATIVE_PERCENT_PROPS Set is currently created inside
the box translation path causing per-node allocations; hoist it to module/file
scope by moving the declaration of NATIVE_PERCENT_PROPS (the Set of
"width","height","flexBasis") out of the function where propsToVNode/box
translation runs and declare it once at top-level so all invocations reuse the
same Set instance.

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: 1

🧹 Nitpick comments (2)
packages/core/src/renderer/renderToDrawlist/overflowCulling.ts (1)

21-39: Consider checking blur and spread for complete overflow detection.

The shadow configuration may include blur and spread properties that can cause overflow even when offsets are zero. Currently only offsetX and offsetY are checked.

If this is an intentional simplification for the culling heuristic (conservative approximation is acceptable), this is fine. Otherwise, you may want to extend the check.

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

In `@packages/core/src/renderer/renderToDrawlist/overflowCulling.ts` around lines
21 - 39, hasBoxShadowOverflow currently only checks offsetX/offsetY via
readShadowOffset; extend it to also consider shadow.blur and shadow.spread since
nonzero blur/spread can create overflow. In the hasBoxShadowOverflow function,
after reading offsetX/offsetY from config, read blur and spread from the same
config using the existing shadow-reading helpers (e.g., readShadowBlur and
readShadowSpread or analogous utilities used elsewhere) with the same default
semantics, then return true if offsetX>0 || offsetY>0 || blur>0 || spread>0;
keep the existing early returns for non-box nodes and non-object shadow configs
and reuse the same config variable names (config, offsetX, offsetY) to locate
the change.
packages/create-rezi/templates/starship/src/main.ts (1)

646-667: Add in-flight guard to prevent overlapping debug queries.

The async pump() function at line 646 is called from setInterval() every 250ms without waiting for completion. If debugController.query() takes longer than the interval, overlapping requests will accumulate, creating unnecessary query/log pressure in debug mode.

Add a simple debugTraceInFlight flag to serialize the pump calls:

let debugTraceTimer: ReturnType<typeof setInterval> | null = null;
+let debugTraceInFlight = false;
...
  const pump = async () => {
-   if (!debugController) return;
+   if (!debugController || debugTraceInFlight) return;
+   debugTraceInFlight = true;
    try {
      const records = await debugController.query({ maxRecords: 256 });
      if (records.length === 0) return;
      for (const record of records) {
        if (record.header.recordId <= debugLastRecordId) continue;
        debugLastRecordId = record.header.recordId;
        snapshotDebugRecord(record);
      }
    } catch (error) {
      debugSnapshot("runtime.debug.query.error", {
        message: error instanceof Error ? error.message : String(error),
        route: currentRouteId(),
      });
+   } finally {
+     debugTraceInFlight = false;
    }
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/main.ts` around lines 646 - 667,
Add a boolean guard (e.g., debugTraceInFlight) around the async pump to prevent
overlapping runs: declare debugTraceInFlight in the same scope as
pump/debugTraceTimer, at the top of pump return immediately if
debugTraceInFlight is true, set debugTraceInFlight = true when starting the work
(before calling debugController.query) and ensure debugTraceInFlight is reset to
false in a finally block so it clears on both success and error; keep existing
logic using debugController.query, debugLastRecordId,
snapshotDebugRecord/debugSnapshot and currentRouteId unchanged.
🤖 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/renderToDrawlist/overflowCulling.ts`:
- Around line 13-19: readShadowOffset currently clamps negative shadow offsets
to 0 causing hasBoxShadowOverflow to miss overflows; update readShadowOffset
(the function named readShadowOffset) to return the absolute magnitude of the
truncated numeric input instead of zero for negative values (i.e., use Math.abs
on the truncated value and then ensure non-negative result or fallback), keeping
the fallback behavior when input is not a finite number so negative offsets like
-5 correctly contribute to overflow detection.

---

Nitpick comments:
In `@packages/core/src/renderer/renderToDrawlist/overflowCulling.ts`:
- Around line 21-39: hasBoxShadowOverflow currently only checks offsetX/offsetY
via readShadowOffset; extend it to also consider shadow.blur and shadow.spread
since nonzero blur/spread can create overflow. In the hasBoxShadowOverflow
function, after reading offsetX/offsetY from config, read blur and spread from
the same config using the existing shadow-reading helpers (e.g., readShadowBlur
and readShadowSpread or analogous utilities used elsewhere) with the same
default semantics, then return true if offsetX>0 || offsetY>0 || blur>0 ||
spread>0; keep the existing early returns for non-box nodes and non-object
shadow configs and reuse the same config variable names (config, offsetX,
offsetY) to locate the change.

In `@packages/create-rezi/templates/starship/src/main.ts`:
- Around line 646-667: Add a boolean guard (e.g., debugTraceInFlight) around the
async pump to prevent overlapping runs: declare debugTraceInFlight in the same
scope as pump/debugTraceTimer, at the top of pump return immediately if
debugTraceInFlight is true, set debugTraceInFlight = true when starting the work
(before calling debugController.query) and ensure debugTraceInFlight is reset to
false in a finally block so it clears on both success and error; keep existing
logic using debugController.query, debugLastRecordId,
snapshotDebugRecord/debugSnapshot and currentRouteId unchanged.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47375c9 and 3155af7.

📒 Files selected for processing (11)
  • examples/regression-dashboard/src/main.ts
  • packages/core/src/renderer/renderToDrawlist/overflowCulling.ts
  • packages/create-rezi/templates/starship/src/main.ts
  • packages/native/vendor/VENDOR_COMMIT.txt
  • packages/native/vendor/zireael/include/zr/zr_drawlist.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_diff.c
  • packages/native/vendor/zireael/src/core/zr_drawlist.c
  • packages/native/vendor/zireael/src/core/zr_engine_present.inc
  • vendor/zireael
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/regression-dashboard/src/main.ts

…ized-renderer

# Conflicts:
#	packages/core/src/__tests__/integration/integration.dashboard.test.ts
#	packages/core/src/__tests__/integration/integration.file-manager.test.ts
#	packages/core/src/__tests__/integration/integration.form-editor.test.ts
#	packages/core/src/__tests__/integration/integration.reflow.test.ts
#	packages/core/src/__tests__/integration/integration.resize.test.ts
#	packages/core/src/__tests__/stress/stress.large-trees.test.ts
#	packages/core/src/abi.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/createApp.ts
#	packages/core/src/app/rawRenderer.ts
#	packages/core/src/app/widgetRenderer.ts
#	packages/core/src/drawlist/__tests__/builder.alignment.test.ts
#	packages/core/src/drawlist/__tests__/builder.golden.test.ts
#	packages/core/src/drawlist/__tests__/builder.graphics.test.ts
#	packages/core/src/drawlist/__tests__/builder.limits.test.ts
#	packages/core/src/drawlist/__tests__/builder.reset.test.ts
#	packages/core/src/drawlist/__tests__/builder.round-trip.test.ts
#	packages/core/src/drawlist/__tests__/builder.string-cache.test.ts
#	packages/core/src/drawlist/__tests__/builder.string-intern.test.ts
#	packages/core/src/drawlist/__tests__/builder.style-encoding.test.ts
#	packages/core/src/drawlist/__tests__/builder.text-run.test.ts
#	packages/core/src/drawlist/__tests__/builder.validate-caps.test.ts
#	packages/core/src/drawlist/__tests__/builder_style_attrs.test.ts
#	packages/core/src/drawlist/__tests__/builder_v6_resources.test.ts
#	packages/core/src/drawlist/builder.ts
#	packages/core/src/drawlist/builderBase.ts
#	packages/core/src/drawlist/index.ts
#	packages/core/src/drawlist/types.ts
#	packages/core/src/index.ts
#	packages/core/src/renderer/__tests__/focusIndicators.test.ts
#	packages/core/src/renderer/__tests__/overlay.edge.test.ts
#	packages/core/src/renderer/__tests__/persistentBlobKeys.test.ts
#	packages/core/src/renderer/__tests__/recipeRendering.test-utils.ts
#	packages/core/src/renderer/__tests__/render.golden.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.scrollbar.test.ts
#	packages/core/src/renderer/__tests__/renderer.text.test.ts
#	packages/core/src/renderer/renderToDrawlist/renderTree.ts
#	packages/core/src/renderer/renderToDrawlist/widgets/renderCanvasWidgets.ts
#	packages/core/src/testing/renderer.ts
#	packages/core/src/theme/__tests__/theme.extend.test.ts
#	packages/core/src/theme/__tests__/theme.switch.test.ts
#	packages/core/src/ui/__tests__/themed.test.ts
#	packages/core/src/widgets/__tests__/basicWidgets.render.test.ts
#	packages/core/src/widgets/__tests__/graphics.golden.test.ts
#	packages/core/src/widgets/__tests__/graphicsWidgets.test.ts
#	packages/core/src/widgets/__tests__/renderer.regressions.test.ts
#	packages/core/src/widgets/__tests__/style.utils.test.ts
#	packages/core/src/widgets/__tests__/styleUtils.test.ts
#	packages/core/src/widgets/__tests__/widgetRenderSmoke.test.ts
#	packages/ink-compat/src/runtime/createInkRenderer.ts
#	packages/native/vendor/VENDOR_COMMIT.txt
#	packages/native/vendor/zireael/include/zr/zr_drawlist.h
#	packages/native/vendor/zireael/src/core/zr_drawlist.c
#	packages/native/vendor/zireael/src/core/zr_engine.c
#	packages/native/vendor/zireael/src/core/zr_engine_present.inc
#	packages/node/src/backend/nodeBackend.ts
#	packages/testkit/fixtures/zrdl-v1/golden/draw_text_interned.bin
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/drawlist/builderBase.ts (1)

405-427: ⚠️ Potential issue | 🟠 Major

Synchronize protocol specification with builder interface refactoring.

The appendDrawTextCommand signature was refactored to remove the byteOff parameter, but the drawlist protocol specification and generated writers code were not updated to match. Currently:

  • scripts/drawlist-spec.ts still defines DRAW_TEXT with the byteOff field at offset 20
  • writers.gen.ts still includes byteOff in the generated signature
  • builder.ts works around this by hardcoding byteOff to 0 when calling writeDrawText
  • docs/protocol/zrdl.md still documents the old signature: DRAW_TEXT (stringId, byteOff, byteLen)

To complete the refactoring:

  1. Update scripts/drawlist-spec.ts to remove the byteOff field from DRAW_TEXT
  2. Regenerate with npm run codegen and validate with npm run codegen:check
  3. Update docs/protocol/zrdl.md to reflect the new signature
  4. Update docs/protocol/versioning.md if this is a breaking change
  5. Update byte-level tests in packages/core/src/drawlist/__tests__/writers.gen.test.ts

This is a binary protocol change requiring full synchronization per project guidelines.

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

In `@packages/core/src/drawlist/builderBase.ts` around lines 405 - 427, The
protocol and generated writers were not updated after refactoring
appendDrawTextCommand to remove byteOff: update the DRAW_TEXT entry in
scripts/drawlist-spec.ts to remove the byteOff field (so offsets no longer
reserve offset 20), run npm run codegen and npm run codegen:check to regenerate
writers (writers.gen.ts) so writeDrawText signatures no longer include byteOff,
remove the temporary hardcoded 0 in builder.ts where writeDrawText is called,
then update docs/protocol/zrdl.md (and docs/protocol/versioning.md if this is a
breaking change) to reflect the new DRAW_TEXT signature and finally adjust
byte-level tests in packages/core/src/drawlist/__tests__/writers.gen.test.ts to
match the new binary layout.
🧹 Nitpick comments (3)
packages/core/src/drawlist/__tests__/writers.gen.test.ts (1)

530-541: Consider adding explicit tests for new def block sizes and writers.

The new writeDefString and writeDefBlob functions are only tested implicitly via the round-trip integration test. For consistency with other writers and better fault isolation:

  1. Add size constant tests for DEF_STRING_BASE_SIZE and DEF_BLOB_BASE_SIZE in this section
  2. Consider adding byte identity tests for the def writers (though their variable-length nature may require a different test pattern)
📝 Suggested additions to the size constants section
 describe("writers.gen - size constants", () => {
   test("CLEAR_SIZE", () => assert.equal(CLEAR_SIZE, 8));
   test("FILL_RECT_SIZE", () => assert.equal(FILL_RECT_SIZE, 52));
   test("DRAW_TEXT_SIZE", () => assert.equal(DRAW_TEXT_SIZE, 60));
   test("PUSH_CLIP_SIZE", () => assert.equal(PUSH_CLIP_SIZE, 24));
   test("BLIT_RECT_SIZE", () => assert.equal(BLIT_RECT_SIZE, 32));
   test("POP_CLIP_SIZE", () => assert.equal(POP_CLIP_SIZE, 8));
   test("DRAW_TEXT_RUN_SIZE", () => assert.equal(DRAW_TEXT_RUN_SIZE, 24));
   test("SET_CURSOR_SIZE", () => assert.equal(SET_CURSOR_SIZE, 20));
   test("DRAW_CANVAS_SIZE", () => assert.equal(DRAW_CANVAS_SIZE, 32));
   test("DRAW_IMAGE_SIZE", () => assert.equal(DRAW_IMAGE_SIZE, 40));
+  test("DEF_STRING_BASE_SIZE", () => assert.equal(DEF_STRING_BASE_SIZE, 16));
+  test("DEF_BLOB_BASE_SIZE", () => assert.equal(DEF_BLOB_BASE_SIZE, 16));
 });

Based on learnings: "When changing drawlist command layout/opcodes/field offsets: update byte-level tests in packages/core/src/drawlist/__tests__/writers.gen.test.ts"

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

In `@packages/core/src/drawlist/__tests__/writers.gen.test.ts` around lines 530 -
541, Add explicit tests for the new def block sizes and writers: in the existing
"writers.gen - size constants" test group add assertions that
DEF_STRING_BASE_SIZE and DEF_BLOB_BASE_SIZE equal their expected base values
(mirroring the pattern used for CLEAR_SIZE, DRAW_TEXT_SIZE, etc.), and add
focused byte-identity tests for writeDefString and writeDefBlob that write a
short known string/blob and assert the produced buffer length and header bytes
match the expected base size + payload length (or compare the header slice to an
expected header); reference the writeDefString, writeDefBlob,
DEF_STRING_BASE_SIZE, and DEF_BLOB_BASE_SIZE symbols when locating code to
update.
packages/core/src/drawlist/builderBase.ts (1)

690-741: Error messages in internString have misleading context when called from addTextRunBlob.

The internString method is called from both drawText (line 205) and addTextRunBlob (line 320), but error messages (lines 695, 703, 724) are hardcoded with "drawText:" prefix. When string interning fails during addTextRunBlob, the error will misleadingly reference "drawText".

Consider using a generic prefix like "internString:" or passing the caller context as a parameter.

♻️ Proposed fix
- protected internString(text: string): number | null {
+ protected internString(text: string, callerHint = "internString"): number | null {
    const existing = this.stringIndexByValue.get(text);
    if (existing !== undefined) return existing;

    if (!this.encoder) {
-     this.fail("ZRDL_INTERNAL", "drawText: TextEncoder is not available");
+     this.fail("ZRDL_INTERNAL", `${callerHint}: TextEncoder is not available`);
      return null;
    }

    const nextIndex = this.stringSpanOffs.length;
    if (nextIndex + 1 > this.maxStrings) {
      this.fail(
        "ZRDL_TOO_LARGE",
-       `drawText: maxStrings exceeded (count=${nextIndex + 1}, max=${this.maxStrings})`,
+       `${callerHint}: maxStrings exceeded (count=${nextIndex + 1}, max=${this.maxStrings})`,
      );
      return null;
    }
    // ... similar change for maxStringBytes error at line 724
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/drawlist/builderBase.ts` around lines 690 - 741, The error
strings inside internString currently hardcode the "drawText:" prefix which
misattributes failures when internString is invoked from other callers like
addTextRunBlob; update internString to use a neutral prefix (e.g.,
"internString:") or accept an optional callerContext parameter and include that
in the process of failing so errors reflect the true caller (update all
fail(...) calls in internString and adjust callers such as drawText and
addTextRunBlob to pass context if you choose the parameter approach). Ensure you
update references to internString, drawText, and addTextRunBlob so tests/logs
show the new message format consistently.
packages/core/src/renderer/__tests__/renderer.damage.test.ts (1)

67-74: Minor inconsistency with other test file's blitRect stub.

This file uses explicit parameters for blitRect, while renderer.partial.test.ts uses ...args: Parameters<DrawlistBuilder["blitRect"]>. Both work correctly, but using the Parameters<> approach would be more maintainable if the signature changes.

♻️ Optional: Use Parameters<> for consistency
-  blitRect(
-    _srcX: number,
-    _srcY: number,
-    _w: number,
-    _h: number,
-    _dstX: number,
-    _dstY: number,
-  ): void {}
+  blitRect(..._args: Parameters<DrawlistBuilder["blitRect"]>): void {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/renderer/__tests__/renderer.damage.test.ts` around lines 67
- 74, Replace the explicit parameter list in the blitRect stub with a variadic
typed parameter using Parameters to match other tests: change the signature
currently named blitRect(_srcX: number, _srcY: number, _w: number, _h: number,
_dstX: number, _dstY: number) to use ...args:
Parameters<DrawlistBuilder["blitRect"]> so the test stays consistent and will
automatically track future signature changes of DrawlistBuilder.blitRect.
🤖 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/drawlist/builder.ts`:
- Around line 286-292: getTextPerfCounters currently returns a hardcoded
textEncoderCalls: 0 which is misleading; update the implementation and API so
the metric is accurate: either add a textEncoderCalls counter property to the
builder class and increment it inside the text encoding paths (where strings are
encoded/added — look for methods performing text encoding or writing into string
arena) and return that counter in getTextPerfCounters alongside
this.stringBytesLen and this.textPerfSegments, or remove textEncoderCalls from
the DrawlistTextPerfCounters interface and from getTextPerfCounters so the
contract matches implemented metrics; adjust any callers/tests expecting the
field accordingly.
- Around line 315-322: Update the protocol docs and consumer code to reflect
that link references are stored 1-based: document in docs/protocol/zrdl.md (and
update docs/protocol/versioning.md if compatibility is impacted) that the style
fields linkUriRef and linkIdRef are 1-based indices with 0 as the sentinel for
"no active link"; reference the builder behavior where internString(...) result
is stored as (idx + 1) >>> 0 in activeLinkUriRef and activeLinkIdRef to make the
scheme explicit. Also update the renderer (Zireael) to read these style fields
as 1-based IDs (subtract 1 before indexing into interned arrays) and add a
compatibility note in versioning.md if this change affects older renderers.
Ensure examples in zrdl.md show both the sentinel 0 and a sample 1-based value.

---

Outside diff comments:
In `@packages/core/src/drawlist/builderBase.ts`:
- Around line 405-427: The protocol and generated writers were not updated after
refactoring appendDrawTextCommand to remove byteOff: update the DRAW_TEXT entry
in scripts/drawlist-spec.ts to remove the byteOff field (so offsets no longer
reserve offset 20), run npm run codegen and npm run codegen:check to regenerate
writers (writers.gen.ts) so writeDrawText signatures no longer include byteOff,
remove the temporary hardcoded 0 in builder.ts where writeDrawText is called,
then update docs/protocol/zrdl.md (and docs/protocol/versioning.md if this is a
breaking change) to reflect the new DRAW_TEXT signature and finally adjust
byte-level tests in packages/core/src/drawlist/__tests__/writers.gen.test.ts to
match the new binary layout.

---

Nitpick comments:
In `@packages/core/src/drawlist/__tests__/writers.gen.test.ts`:
- Around line 530-541: Add explicit tests for the new def block sizes and
writers: in the existing "writers.gen - size constants" test group add
assertions that DEF_STRING_BASE_SIZE and DEF_BLOB_BASE_SIZE equal their expected
base values (mirroring the pattern used for CLEAR_SIZE, DRAW_TEXT_SIZE, etc.),
and add focused byte-identity tests for writeDefString and writeDefBlob that
write a short known string/blob and assert the produced buffer length and header
bytes match the expected base size + payload length (or compare the header slice
to an expected header); reference the writeDefString, writeDefBlob,
DEF_STRING_BASE_SIZE, and DEF_BLOB_BASE_SIZE symbols when locating code to
update.

In `@packages/core/src/drawlist/builderBase.ts`:
- Around line 690-741: The error strings inside internString currently hardcode
the "drawText:" prefix which misattributes failures when internString is invoked
from other callers like addTextRunBlob; update internString to use a neutral
prefix (e.g., "internString:") or accept an optional callerContext parameter and
include that in the process of failing so errors reflect the true caller (update
all fail(...) calls in internString and adjust callers such as drawText and
addTextRunBlob to pass context if you choose the parameter approach). Ensure you
update references to internString, drawText, and addTextRunBlob so tests/logs
show the new message format consistently.

In `@packages/core/src/renderer/__tests__/renderer.damage.test.ts`:
- Around line 67-74: Replace the explicit parameter list in the blitRect stub
with a variadic typed parameter using Parameters to match other tests: change
the signature currently named blitRect(_srcX: number, _srcY: number, _w: number,
_h: number, _dstX: number, _dstY: number) to use ...args:
Parameters<DrawlistBuilder["blitRect"]> so the test stays consistent and will
automatically track future signature changes of DrawlistBuilder.blitRect.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3155af7 and 5882c57.

📒 Files selected for processing (12)
  • packages/core/src/drawlist/__tests__/builder.text-arena.equivalence.test.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/drawlist/builderBase.ts
  • packages/core/src/drawlist/writers.gen.ts
  • packages/core/src/renderer/__tests__/persistentBlobKeys.test.ts
  • packages/core/src/renderer/__tests__/renderer.damage.test.ts
  • packages/core/src/renderer/__tests__/renderer.partial.test.ts
  • packages/core/src/renderer/renderToDrawlist/overflowCulling.ts
  • packages/core/src/renderer/renderToDrawlist/widgets/containers.ts
  • packages/core/src/runtime/commit.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/core/src/drawlist/tests/writers.gen.v6.test.ts
  • packages/core/src/drawlist/writers.gen.ts
  • packages/core/src/renderer/renderToDrawlist/overflowCulling.ts
  • packages/core/src/renderer/tests/persistentBlobKeys.test.ts

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.

🧹 Nitpick comments (1)
packages/ink-compat/src/runtime/createInkRenderer.ts (1)

458-476: Dead code: countSelfDirty is defined but never used.

The comment indicates this is a temporary debug helper. Since the investigation appears complete, consider removing it to avoid leaving dead code in production.

🧹 Proposed cleanup
-// ---------------------------------------------------------------------------
-// Debug helpers (temporary — remove after investigation)
-// ---------------------------------------------------------------------------
-
-function countSelfDirty(root: RuntimeInstance | null): number {
-  if (!root) return 0;
-  let count = 0;
-  const stack: RuntimeInstance[] = [root];
-  while (stack.length > 0) {
-    const node = stack.pop();
-    if (!node) continue;
-    if (node.selfDirty) count++;
-    for (let i = node.children.length - 1; i >= 0; i--) {
-      const child = node.children[i];
-      if (child) stack.push(child);
-    }
-  }
-  return count;
-}
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ink-compat/src/runtime/createInkRenderer.ts` around lines 458 - 476,
Remove the unused debug helper function countSelfDirty from createInkRenderer
(the function signature and its internal traversal logic referencing
RuntimeInstance and properties like selfDirty and children); delete the entire
function block to eliminate dead code and any related temporary comment header
("Debug helpers (temporary — remove after investigation)"), ensuring no other
code references countSelfDirty remain (if they do, either remove those calls or
replace them with appropriate production logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ink-compat/src/runtime/createInkRenderer.ts`:
- Around line 458-476: Remove the unused debug helper function countSelfDirty
from createInkRenderer (the function signature and its internal traversal logic
referencing RuntimeInstance and properties like selfDirty and children); delete
the entire function block to eliminate dead code and any related temporary
comment header ("Debug helpers (temporary — remove after investigation)"),
ensuring no other code references countSelfDirty remain (if they do, either
remove those calls or replace them with appropriate production logic).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5882c57 and 4cbcb8b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • docs/protocol/versioning.md
  • docs/protocol/zrdl.md
  • packages/core/src/drawlist/__tests__/builder.text-arena.equivalence.test.ts
  • packages/core/src/drawlist/builder.ts
  • packages/core/src/drawlist/builderBase.ts
  • packages/ink-compat/src/runtime/createInkRenderer.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/protocol/zrdl.md

@RtlZeroMemory RtlZeroMemory merged commit 115410b into main Feb 27, 2026
6 of 7 checks passed
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