Skip to content

Surface widget failures where developers look - #43

Merged
SunkenInTime merged 5 commits into
masterfrom
agent/error-propagation-seams
Jul 30, 2026
Merged

Surface widget failures where developers look#43
SunkenInTime merged 5 commits into
masterfrom
agent/error-propagation-seams

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Jul 29, 2026

Copy link
Copy Markdown
Owner

What changed

This is the repository-wide error-propagation pass for every place a widget failure can be born, swallowed, or presented.

  • Make SDK renders transactional: failed generations abort, keep the last committed tree, log message + stack, and display a deterministic error surface.
  • Guard effects, cleanup, intervals, canvas frames/resizes, events, storage, and provider callbacks with the same named boundary.
  • Track unhandled QuickJS promise rejections and reject hot-swap candidates whose first render fails.
  • Name runtime budgets with their limit and authored ask, including max_nodes=128, asked for 129.
  • Add weaver check validation for retained-tree budgets, decoded-RGBA image size, and canvas clipping/opacity ancestry.
  • Render explanatory image and native-projection placeholders instead of black or uninitialized surfaces.
  • Keep the dev stream persistently honest about stale bundles and widgets that never present a frame.
  • Reconcile registry/status divergence, stale status publication, renderer sidecars, geometry corruption, widget-log failure, and shared-renderer launch/exit failures.
  • Replace, narrow, log, or document silent catches in the affected paths.

Why

The highest-impact reproduction exceeded the retained node budget, passed weaver check, produced no useful log, committed a half-built tree, and displayed uninitialized GPU memory. The bridge already generated many useful errors, but asynchronous SDK scheduling, generic process exits, stale status surfaces, and log-only fallbacks discarded their meaning before it reached the developer.

The governing rule is: a failure must surface where the developer is looking—weaver check, the dev stream, the per-widget log, or the widget itself—and it must name the cause or budget.

Developer impact

The canonical over-budget widget now:

  1. fails weaver check with the budget, limit, ask, and headroom;
  2. if forced through, logs node capacity exhausted: max_nodes=128, asked for 129 with a stack;
  3. displays an error surface without committing or presenting the partial generation.

Hot-swap failures leave the old bundle running. Dead platform callbacks close their windows instead of retaining stale surface contents.

Native dependency

Depends on SunkenInTime/native#17 for the three-platform callback-name and stale-window fix. The submodule pointer is pinned to that PR's commit.

Validation

  • npm test — 76/76 passed
  • npm run typecheck — passed
  • zig build test in runtime — passed
  • zig build test in host — passed
  • Nine portable example surfaces checked and bundled
  • git diff --check — clean in both repositories

The standalone native test suite reported 1,821 passed / 17 skipped. Its overall build remains environment-limited on this Windows checkout by missing WebView2 loader DLL fixtures and an unavailable runtime-core connection check.

Summary by CodeRabbit

  • New Features
    • Enhanced weaver dev with stale rebuild tracking, periodic reminders, recovery (“caught up”) messaging, recursive widget-directory watching, and “presentation health” frame checks.
    • Expanded weaver check with broader widget-budget limits, improved canvas ancestor validation (clip vs opacity), and stricter local image asset/budget validation with clearer naming.
  • Bug Fixes
    • Improved host/runtime resilience and status publication noise reduction, plus more consistent renderer/projection/image failure diagnostics.
  • Tests
    • Added/extended CLI budget/canvas/image-budget cases and reconciler boundary/error-reporting coverage.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f2dffe85-4b39-4197-a8d4-137a0d173160

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4c3bf and e3a8246.

📒 Files selected for processing (2)
  • AGENTS.md
  • CLAUDE.md

📝 Walkthrough

Walkthrough

The change expands CLI validation and diagnostics, adds transactional rendering and structured runtime failure handling, improves host and reload resilience, and adds tests for budgets, canvas constraints, callback failures, image handling, and recovery behavior.

Changes

CLI validation and development diagnostics

Layer / File(s) Summary
Development, status, and source validation
cli/src/index.ts, cli/src/host-tools.ts, cli/src/origin.ts
Development health reporting, status divergence diagnostics, lowered-tree budgets, canvas ancestry checks, source-file attribution, and local image validation are expanded.
CLI validation tests
cli/test/*, test/all.test.mjs
Tests cover canvas ancestry, component boundaries, image budgets, and authored budget diagnostics.

Runtime rendering and callback failures

Layer / File(s) Summary
Tree transactions and native bridge errors
runtime/src/tree.zig, runtime/src/bridge.zig, sdk/src/native.d.ts
Batches can be aborted and restored; bridge failures include contextual limits, causes, node identifiers, and canvas ancestry details.
SDK and runtime failure boundaries
sdk/src/reconciler.ts, runtime/src/js_engine.zig, runtime/src/main.zig, runtime/src/widget_log.zig
Rendering, effects, timers, providers, storage, events, promises, images, projections, and log writes use explicit failure state, reporting, and recovery paths.
Runtime support and tests
runtime/src/geometry.zig, runtime/src/dev_reload.zig, runtime/native-sdk, sdk/test/*, runtime/src/*
Geometry and reload errors are surfaced with warnings, the native SDK revision is updated, and runtime failure paths receive test coverage.

Host and reload resilience

Layer / File(s) Summary
Listener retry handling
host/src/macos_host.zig, runtime/src/dev_reload.zig
Accept loops retry transient failures with suppressed repeated logs and recovery messages.
Supervision and status publication
host/src/supervisor.zig, host/src/macos_host.zig, host/src/windows_host.zig
Launch, crash, renderer, and status-write failures preserve causes and track degraded or recovered state.
Backend sidecar cleanup
host/src/macos_host.zig
Startup cleanup removes orphan renderer status sidecars and tolerates already-missing files.

Project guidance

Layer / File(s) Summary
Repository guidance and terminology
AGENTS.md, CLAUDE.md
Project guidance now defines the platform, glossary, measurement practices, error-reporting expectations, and development principles.

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

Sequence Diagram(s)

sequenceDiagram
  participant Widget as Widget callback
  participant Reconciler
  participant NativeBridge
  participant Tree
  Widget->>Reconciler: throw callback error
  Reconciler->>NativeBridge: reportError(scope, details)
  Reconciler->>NativeBridge: abortBatch()
  NativeBridge->>Tree: restore transaction snapshot
  Tree-->>NativeBridge: restored committed tree
  NativeBridge-->>Reconciler: failure surface state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and relevant to the PR’s main theme of surfacing widget failures to developers.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/error-propagation-seams

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

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes widget failures transactional and surfaces actionable diagnostics across development, validation, runtime, and host boundaries.

  • Adds transactional SDK rendering, named callback boundaries, deterministic error surfaces, and failed hot-swap rejection.
  • Extends weaver check with retained-tree, image, and canvas-ancestry validation.
  • Adds stale-build and presentation-health diagnostics to the development stream.
  • Improves runtime, host, status, renderer, geometry, image, projection, and logging failure reporting.
  • Updates the native SDK pin and expands CLI, SDK, and runtime coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
sdk/src/reconciler.ts Makes render generations transactional and applies named failure boundaries to effects, cleanup, events, timers, canvas work, storage, and provider callbacks.
runtime/src/tree.zig Adds batch rollback, explicit budget diagnostics, canvas ancestry enforcement, and deterministic runtime error surfaces.
runtime/src/js_engine.zig Tracks unhandled QuickJS promise rejections and propagates render failure state through runtime execution.
runtime/src/main.zig Expands runtime error propagation, image validation, renderer lifecycle handling, and developer-facing diagnostics.
cli/src/index.ts Adds recursive development watching, stale and presentation-health reporting, status reconciliation warnings, and broader static widget validation.
host/src/supervisor.zig Improves shared-renderer launch, exit, and status-publication failure handling.
host/src/macos_host.zig Hardens macOS callback and renderer failure handling.
host/src/windows_host.zig Hardens Windows callback and renderer failure handling.
runtime/src/widget_log.zig Improves widget-log durability, truncation safety, and fallback diagnostics.
sdk/test/reconciler.test.mjs Extends coverage for transactional aborts, callback scopes, error reporting, and hot-swap state transfer.

Sequence Diagram

sequenceDiagram
    participant Widget
    participant SDK
    participant Runtime
    participant Host
    participant Developer
    Widget->>SDK: Render generation
    SDK->>Runtime: beginBatch
    SDK->>Runtime: Apply tree operations
    alt Generation succeeds
        SDK->>Runtime: endBatch
        Runtime->>Host: Present committed frame
    else Generation or callback fails
        SDK->>Runtime: abortBatch and reportError
        Runtime->>Runtime: Preserve last committed tree
        Runtime->>Host: Display deterministic error surface
        Runtime-->>Developer: Emit named message and stack
    end
    Developer->>Runtime: Submit hot-swap candidate
    alt First render succeeds
        Runtime->>Host: Activate candidate
    else First render fails
        Runtime-->>Developer: Report failed hot swap
        Runtime->>Host: Keep previous bundle running
    end
Loading

Reviews (5): Last reviewed commit: "Document Weaver's performance and design..." | Re-trigger Greptile

@SunkenInTime

Copy link
Copy Markdown
Owner Author

Addressed the CI release-audit ratchet in 395a262; the native dependency SHA is now both pinned and audited.\n\n@greptileai

@SunkenInTime

Copy link
Copy Markdown
Owner Author

@greptileai

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (3)
host/src/windows_host.zig (1)

767-783: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Expose renderer failure even when a GPU sidecar already exists.

A widget that previously wrote gpu retains that sidecar after the shared renderer exits, so this condition leaves its status reason empty despite renderer_failure_reason being active. Apply the shared failure to running GPU widgets with no widget-specific reason regardless of backend.

Proposed fix
-            else if (slot.wants_gpu and backend.len == 1 and backend[0] == '-' and self.renderer_failure_reason_len > 0)
+            else if (slot.platform.process != null and slot.wants_gpu and self.renderer_failure_reason_len > 0)
                 self.renderer_failure_reason[0..self.renderer_failure_reason_len]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@host/src/windows_host.zig` around lines 767 - 783, Update the reason
selection in the entries construction to use renderer_failure_reason for running
GPU widgets without a widget-specific slot reason, regardless of the backend
value. Remove the backend.len/backend[0] == '-' restriction from the fallback
while preserving slot.reason() precedence and the existing empty-string
fallback.
runtime/src/widget_log.zig (2)

66-91: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

write_failed never clears, so a transient log write failure permanently replaces the widget UI.

A momentarily full disk or a locked file latches write_failed for the process lifetime, and view() shows the WidgetLogUnavailable panel forever even after writes resume. Every other degraded path added in this PR latches and recovers (dispatchProviderFrames and noteProjectionFailure in runtime/src/main.zig). Clear the flag on a successful write for consistency.

🔧 Proposed fix
 fn writeLine(line: []const u8) void {
-    writeLineFallible(line) catch |err| noteFailure(err);
+    writeLineFallible(line) catch |err| return noteFailure(err);
+    if (write_failed.swap(false, .acq_rel)) {
+        fallback_reported.store(false, .release);
+        std.debug.print("weaver widget log recovered; path={s}\n", .{path_buffer[0..path_len]});
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/widget_log.zig` around lines 66 - 91, Update the successful-write
path in writeLine or writeLineFallible to clear the write_failed flag after
writeLineFallible completes without error. Preserve noteFailure(err) for failed
writes, and ensure subsequent successful writes allow view() to stop showing
WidgetLogUnavailable.

14-26: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Eager createFile failure bypasses the new WidgetLogUnavailable surface.

init now hard-fails on createFile, and main() calls it with try (Line 1198 of runtime/src/main.zig), so the exact conditions this PR added failed() for — unwritable log directory, no disk space — now kill the process before the window exists, instead of rendering the WidgetLogUnavailable panel added in view(). Route the eager-create failure through noteFailure and let startup continue.

🔧 Proposed fix
-    var file = try std.Io.Dir.cwd().createFile(runtime_io, path, .{ .read = true, .truncate = false });
-    file.close(runtime_io);
     write_failed.store(false, .release);
     fallback_reported.store(false, .release);
+    if (std.Io.Dir.cwd().createFile(runtime_io, path, .{ .read = true, .truncate = false })) |file| {
+        var opened = file;
+        opened.close(runtime_io);
+    } else |err| {
+        noteFailure(err);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/widget_log.zig` around lines 14 - 26, Update init to handle
createFile failures through noteFailure instead of propagating the error,
allowing startup to continue and failed() to expose WidgetLogUnavailable in
view(). Preserve successful file creation and cleanup behavior, and keep
initialization of the path, I/O, and failure state intact.
🧹 Nitpick comments (6)
runtime/src/geometry.zig (1)

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

Good error-propagation change; consider covering it with a Store.load test.

Distinguishing FileNotFound (→ null) from parse failures (→ error.InvalidGeometryRecord) while propagating other I/O errors is a sound improvement, and matches how main.zig already logs and degrades gracefully on any load error. However, the existing tests only exercise parse/format directly (lines 87-92); there's no test hitting Store.load itself with a malformed on-disk record to confirm it now surfaces InvalidGeometryRecord rather than silently returning null.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/geometry.zig` around lines 37 - 44, Add a test for Store.load
that writes a malformed geometry record to a temporary path, invokes load, and
asserts it returns error.InvalidGeometryRecord rather than null. Keep the
existing parse and format tests unchanged, and use the established Store
construction and test temporary-resource cleanup patterns.
runtime/src/tree.zig (1)

277-304: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Iterate nodes by pointer to avoid copying every Node.

for (self.nodes) copies each Node (inline text/source/font buffers) per iteration. canvasAncestorViolation runs on every endBatch, so this is measurable for free savings.

♻️ Suggested change
-        for (self.nodes) |node_value| if (node_value.alive) {
+        for (&self.nodes) |*node_value| if (node_value.alive) {
             count += 1;
         };
@@
-        for (self.nodes, 0..) |node_value, index| {
+        for (&self.nodes, 0..) |*node_value, index| {

As per coding guidelines, "Optimize for very low memory and CPU usage."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/tree.zig` around lines 277 - 304, Update nodeCount and
canvasAncestorViolation to iterate self.nodes by pointer (using mutable or const
pointers as appropriate) instead of copying each Node value, while preserving
the existing alive, kind, and ancestor checks and returned results.

Source: Coding guidelines

cli/src/index.ts (1)

483-499: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Stop the presentation watcher once it has reported.

After presentationFailureReported is set, the interval keeps waking every second for the rest of the dev session only to return at Line 484. Clearing it on the reporting paths keeps dev idle cost at zero.

♻️ Clear the interval after the terminal report
       presentationFailureReported = true;
       const state = status ? `${status.state}${status.reason ? `: ${status.reason}` : ""}` : "absent from host status";
       process.stderr.write(`weaver dev ERROR: "${project.config.name}" has not presented a frame after 10 seconds (${state}); check weaver logs ${JSON.stringify(project.config.name)}\n`);
+      clearInterval(presentationWatch);
     } catch (error) {
       presentationFailureReported = true;
       process.stderr.write(`weaver dev ERROR: presentation health could not be read after 10 seconds: ${errorMessage(error)}\n`);
+      clearInterval(presentationWatch);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/index.ts` around lines 483 - 499, Update the presentationWatch
callback so it calls clearInterval(presentationWatch) immediately after setting
presentationFailureReported and emitting each terminal failure report, covering
both the missing/non-presented status path and the readStatus catch path.
Preserve the existing early return for healthy presentation and ensure the
watcher stops after reporting.
runtime/src/main.zig (2)

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

Hand-rolled PNG/GIF/BMP/JPEG header parsing just to build an error label.

The offsets and bounds here check out, but this is ~50 lines of bespoke format sniffing that duplicates knowledge the decoder already had when it returned ImageTooLarge. Every added format or malformed-header edge case becomes Weaver's problem for a cosmetic string. Prefer surfacing the dimensions from the decoder/SDK error path (or the manifest/weaver check validation, which per the PR already validates decoded RGBA sizes) and drop this parser.

As per coding guidelines: "Prefer obvious, straightforward solutions over clever implementations; actively suggest simpler or more obvious alternatives when appropriate."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/main.zig` around lines 1455 - 1501, Remove the bespoke
encoded-image header parser, including encodedImageDimensions and its
format-specific logic. Instead, obtain the actual image dimensions from the
decoder/SDK error path or the existing manifest/weaver check validation when
constructing the ImageTooLarge error label, while preserving the current
validation behavior and avoiding duplicate format parsing.

Source: Coding guidelines


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

Derive the image-budget strings from named constants. max_image_rgba_bytes=262144 is duplicated, and asked for 17 should come from max_images + 1 instead of hardcoded values so the failure text stays aligned with the runtime limits.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/main.zig` around lines 1418 - 1449, Update the ImageTooLarge and
ImageRegistryFull handling around the failure-label construction to derive all
limit text from the existing named runtime constants. Replace duplicated 262144
values with the image RGBA-byte limit constant, and calculate the
ImageRegistryFull requested count as max_images + 1 instead of hardcoding 17;
use these derived values consistently in labels and log messages.
runtime/src/js_engine.zig (1)

315-325: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the unnecessary JS_DupValue pair JS_ToCStringLen2 only reads the value, so rendered can be passed directly and the extra refcount churn dropped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/js_engine.zig` around lines 315 - 325, Update valueDetails to
pass value directly to JS_ToCStringLen2, removing the rendered duplicate and its
corresponding JS_FreeValue cleanup while preserving the existing formatting,
copying, and error-handling behavior.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/src/index.ts`:
- Around line 1631-1637: Update the unknown-bundled-font diagnostic in the
class-attribute handling branch to pass the current visited sourceFile to
locationMessage instead of project.sourceFile, while preserving the existing
classAttribute location and message behavior.

In `@runtime/src/bridge.zig`:
- Around line 268-288: Update endBatch so canvasAncestorViolation() is evaluated
only on the outermost batch commit, after the batch depth is decremented by the
appropriate endBatch flow. Preserve the existing violation-specific fail
messages, and avoid rejecting transient nested-batch state before the batch
fully closes.

In `@runtime/src/js_engine.zig`:
- Around line 288-300: Update the truncation logic in the unhandled rejection
display around valueDetails and tree.showError so byte limits are rounded down
to a valid UTF-8 codepoint boundary before slicing or copying. Ensure both the
details truncation and first_line extraction never pass partial UTF-8 sequences
to the renderer, while preserving the existing 150-byte limit and fallback
behavior.

In `@runtime/src/main.zig`:
- Around line 1384-1396: Update buildNodeForTest to accept a caller-owned Model
pointer instead of constructing a local Model from retained_tree and fonts, then
pass that model to buildNode. Update every caller to construct one persistent
Model before invoking buildNodeForTest, using the existing retained tree and
fonts storage, so the returned WidgetUi.Node borrows from storage that remains
alive through finalize and avoids copying the large Tree onto the stack.
- Around line 123-137: Move startup image registration out of initEffects and
invoke it after loadLocalImages and seedImageStates complete, so image states
exist before registerImageBytes and failure handling run. Preserve the existing
registration, recordImageFailure, registered, and clearImageFailure behavior
while changing only the initialization order.

In `@runtime/src/tree.zig`:
- Around line 230-238: Update Tree batch snapshot handling in beginBatch and its
corresponding endBatch cleanup to reuse a parent-owned snapshot allocated during
initialization, copying the current tree into that existing storage for each
outermost batch instead of calling allocator.create/destroy per render. Preserve
the existing batch_depth and rollback behavior while eliminating per-batch
allocation and deallocation churn.

In `@runtime/src/widget_log.zig`:
- Around line 34-43: Update the log-writing flow around writer.print(format,
args) so fixed-buffer exhaustion truncates the current message and still writes
the buffered content, rather than calling noteFailure. Preserve noteFailure for
genuine write failures from operations such as writeTimestamp, prefix writes,
and the final output write, and ensure oversized lines do not latch write_failed
or replace the widget.

---

Outside diff comments:
In `@host/src/windows_host.zig`:
- Around line 767-783: Update the reason selection in the entries construction
to use renderer_failure_reason for running GPU widgets without a widget-specific
slot reason, regardless of the backend value. Remove the backend.len/backend[0]
== '-' restriction from the fallback while preserving slot.reason() precedence
and the existing empty-string fallback.

In `@runtime/src/widget_log.zig`:
- Around line 66-91: Update the successful-write path in writeLine or
writeLineFallible to clear the write_failed flag after writeLineFallible
completes without error. Preserve noteFailure(err) for failed writes, and ensure
subsequent successful writes allow view() to stop showing WidgetLogUnavailable.
- Around line 14-26: Update init to handle createFile failures through
noteFailure instead of propagating the error, allowing startup to continue and
failed() to expose WidgetLogUnavailable in view(). Preserve successful file
creation and cleanup behavior, and keep initialization of the path, I/O, and
failure state intact.

---

Nitpick comments:
In `@cli/src/index.ts`:
- Around line 483-499: Update the presentationWatch callback so it calls
clearInterval(presentationWatch) immediately after setting
presentationFailureReported and emitting each terminal failure report, covering
both the missing/non-presented status path and the readStatus catch path.
Preserve the existing early return for healthy presentation and ensure the
watcher stops after reporting.

In `@runtime/src/geometry.zig`:
- Around line 37-44: Add a test for Store.load that writes a malformed geometry
record to a temporary path, invokes load, and asserts it returns
error.InvalidGeometryRecord rather than null. Keep the existing parse and format
tests unchanged, and use the established Store construction and test
temporary-resource cleanup patterns.

In `@runtime/src/js_engine.zig`:
- Around line 315-325: Update valueDetails to pass value directly to
JS_ToCStringLen2, removing the rendered duplicate and its corresponding
JS_FreeValue cleanup while preserving the existing formatting, copying, and
error-handling behavior.

In `@runtime/src/main.zig`:
- Around line 1455-1501: Remove the bespoke encoded-image header parser,
including encodedImageDimensions and its format-specific logic. Instead, obtain
the actual image dimensions from the decoder/SDK error path or the existing
manifest/weaver check validation when constructing the ImageTooLarge error
label, while preserving the current validation behavior and avoiding duplicate
format parsing.
- Around line 1418-1449: Update the ImageTooLarge and ImageRegistryFull handling
around the failure-label construction to derive all limit text from the existing
named runtime constants. Replace duplicated 262144 values with the image
RGBA-byte limit constant, and calculate the ImageRegistryFull requested count as
max_images + 1 instead of hardcoding 17; use these derived values consistently
in labels and log messages.

In `@runtime/src/tree.zig`:
- Around line 277-304: Update nodeCount and canvasAncestorViolation to iterate
self.nodes by pointer (using mutable or const pointers as appropriate) instead
of copying each Node value, while preserving the existing alive, kind, and
ancestor checks and returned results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ce22106d-56fc-4635-89bb-ad17c346588a

📥 Commits

Reviewing files that changed from the base of the PR and between 0cda7e5 and ba58956.

📒 Files selected for processing (25)
  • cli/src/host-tools.ts
  • cli/src/index.ts
  • cli/src/origin.ts
  • cli/test/canvas-size.test.mjs
  • cli/test/image-budget.test.mjs
  • cli/test/lowered-budget.test.mjs
  • host/src/macos_host.zig
  • host/src/supervisor.zig
  • host/src/windows_host.zig
  • runtime/native-sdk
  • runtime/src/bridge.zig
  • runtime/src/dev_reload.zig
  • runtime/src/geometry.zig
  • runtime/src/image_paths.zig
  • runtime/src/js_engine.zig
  • runtime/src/main.zig
  • runtime/src/network.zig
  • runtime/src/provider_macos.zig
  • runtime/src/tree.zig
  • runtime/src/widget_log.zig
  • sdk/src/class-compiler.ts
  • sdk/src/native.d.ts
  • sdk/src/reconciler.ts
  • sdk/test/reconciler.test.mjs
  • test/all.test.mjs

Comment thread cli/src/index.ts
Comment thread runtime/src/bridge.zig
Comment thread runtime/src/js_engine.zig
Comment thread runtime/src/main.zig
Comment on lines 123 to 137
fn initEffects(model: *Model, effects: *Effects) void {
for (model.images[0..model.image_count]) |image| {
_ = effects.registerImageBytes(image.id, image.bytes) catch |err| {
std.log.err("widget image {d} failed to decode/register: {s}", .{ image.id, @errorName(err) });
if (findImageState(model, @intCast(image.id))) |state| {
recordImageFailure(state, err, image.bytes, "initial image decode/register");
} else {
std.log.err("initial image decode/register failed: image={d}, cause={s}", .{ image.id, @errorName(err) });
}
continue;
};
if (findImageState(model, @intCast(image.id))) |state| state.registered = true;
if (findImageState(model, @intCast(image.id))) |state| {
state.registered = true;
clearImageFailure(state);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find where init_fx is invoked in the Native SDK app wrapper.
rg -nP --type=zig -C4 '\binit_fx\b'

Repository: SunkenInTime/weaver

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== main.zig references ==\n'
rg -n --hidden --glob 'runtime/src/main.zig' 'initEffects|init_fx|seedImageStates|loadLocalImages|synchronizeImages|findImageState|registered|recordImageFailure|clearImageFailure' runtime/src/main.zig

printf '\n== main.zig around relevant lines ==\n'
sed -n '100,170p' runtime/src/main.zig

printf '\n== create / startup flow ==\n'
sed -n '1260,1365p' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 10612


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== locate WidgetApp.create and init_fx wiring ==\n'
rg -n --hidden --glob 'runtime/src/main.zig' 'WidgetApp\.create|init_fx\s*=|init_fx\b' runtime/src/main.zig

printf '\n== locate synchronizeImages and image seeding ==\n'
rg -n --hidden --glob 'runtime/src/main.zig' 'synchronizeImages|seedImageStates|loadLocalImages' runtime/src/main.zig

printf '\n== locate initEffects call sites ==\n'
rg -n --hidden --glob 'runtime/src/main.zig' '\binitEffects\s*\(' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 900


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== runtime/src/main.zig outline ==\n'
ast-grep outline runtime/src/main.zig --view expanded | sed -n '1,220p'

printf '\n== exact references ==\n'
rg -n --hidden 'initEffects|WidgetApp\.create|loadLocalImages|seedImageStates|synchronizeImages|findImageState|initFx|init_fx' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 1311


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search entire repo for init_fx / initFx ==\n'
rg -n --hidden '\binit[_A]?fx\b|\binitFx\b' .

printf '\n== search entire repo for WidgetApp.create wiring ==\n'
rg -n --hidden 'WidgetApp\.create|create\(' runtime src . | sed -n '1,200p'

Repository: SunkenInTime/weaver

Length of output: 10022


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1140,1188p' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 2355


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1068,1125p' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 2626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== synchronizeImages ==\n'
sed -n '1149,1198p' runtime/src/main.zig

printf '\n== image state mutation helpers ==\n'
sed -n '1068,1125p' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 5066


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Model definition and defaults ==\n'
sed -n '1,120p' runtime/src/main.zig

printf '\n== loadLocalImages and seedImageStates ==\n'
sed -n '977,1038p' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 8019


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== image_tree_generation and image_epoch writes ==\n'
rg -n --hidden 'image_tree_generation|image_epoch|registered = true|clearImageFailure|recordImageFailure|seedImageStates|synchronizeImages|synchronizeImageNode|removeImageState|addImageState' runtime/src/main.zig

printf '\n== around any image generation writes ==\n'
sed -n '1038,1168p' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 8146


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '360,405p' runtime/src/main.zig

Repository: SunkenInTime/weaver

Length of output: 2321


Move startup image registration after seeding init_fx runs before loadLocalImages/seedImageStates, and seedImageStates marks the tree as current, so the initial registerImageBytes/failure-recording branch in initEffects never handles startup images.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/main.zig` around lines 123 - 137, Move startup image registration
out of initEffects and invoke it after loadLocalImages and seedImageStates
complete, so image states exist before registerImageBytes and failure handling
run. Preserve the existing registration, recordImageFailure, registered, and
clearImageFailure behavior while changing only the initialization order.

Comment thread runtime/src/main.zig
Comment thread runtime/src/tree.zig
Comment thread runtime/src/widget_log.zig
@SunkenInTime

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@SunkenInTime

Copy link
Copy Markdown
Owner Author

@greptileai

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
cli/src/index.ts (2)

454-463: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Update activeManifest only after reload succeeds.

activeManifest is assigned before signalHost/signalDevReload completes. If signaling fails, the next rebuild can treat the manifest as unchanged and take the in-place reload path, leaving host configuration stale.

Proposed fix
 if (configChanged) {
-  activeManifest = next.manifest;
   signalHost("--signal-reload");
+  activeManifest = next.manifest;
 } else {
   await signalDevReload(directory);
+  activeManifest = next.manifest;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/index.ts` around lines 454 - 463, Move the activeManifest assignment
in the rebuild flow to occur only after both signalHost and signalDevReload
complete successfully. Preserve the existing failure handling so a signaling
error leaves activeManifest pointing to the last successfully applied manifest,
forcing the next rebuild to use the correct reload path.

1659-1671: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Enforce the image-size limit before reading the file.

readFileSync(canonical) loads the entire asset before checking the 1 MiB limit, allowing an arbitrarily large local file to consume excessive memory during weaver check. Check statSync(canonical).size first or read only MAX_IMAGE_STREAM_BYTES + 1 bytes.

As per coding guidelines, optimize for very low memory and CPU usage.

Proposed fix
+  const encodedSize = statSync(canonical).size;
+  if (encodedSize > 1024 * 1024) {
+    return `ImageStreamTooLarge: ${JSON.stringify(source)} is ${encodedSize} encoded bytes; max_image_stream_bytes=1048576`;
+  }
   bytes = readFileSync(canonical);
-  if (bytes.length > 1024 * 1024) {
-    return `ImageStreamTooLarge: ...`;
-  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/index.ts` around lines 1659 - 1671, The asset validation flow should
enforce the 1 MiB limit before loading file contents. In the block containing
canonical path resolution and readFileSync, inspect statSync(canonical).size
first and return the existing ImageStreamTooLarge error when it exceeds
MAX_IMAGE_STREAM_BYTES; otherwise read the file, preserving the current
unreadable and root-boundary handling.

Source: Coding guidelines

runtime/src/main.zig (1)

1076-1120: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Use const for state here state is only used to mutate its pointee, so this binding should be const; var triggers Zig’s “local variable is never mutated” error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtime/src/main.zig` around lines 1076 - 1120, Change the `state` binding in
`synchronizeImageNode` from `var` to `const`, preserving all mutations to the
pointed-to image state through the existing pointer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cli/src/index.ts`:
- Around line 454-463: Move the activeManifest assignment in the rebuild flow to
occur only after both signalHost and signalDevReload complete successfully.
Preserve the existing failure handling so a signaling error leaves
activeManifest pointing to the last successfully applied manifest, forcing the
next rebuild to use the correct reload path.
- Around line 1659-1671: The asset validation flow should enforce the 1 MiB
limit before loading file contents. In the block containing canonical path
resolution and readFileSync, inspect statSync(canonical).size first and return
the existing ImageStreamTooLarge error when it exceeds MAX_IMAGE_STREAM_BYTES;
otherwise read the file, preserving the current unreadable and root-boundary
handling.

In `@runtime/src/main.zig`:
- Around line 1076-1120: Change the `state` binding in `synchronizeImageNode`
from `var` to `const`, preserving all mutations to the pointed-to image state
through the existing pointer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 946feed5-828a-4af5-8bc2-cbcdbbebf195

📥 Commits

Reviewing files that changed from the base of the PR and between 395a262 and c80657d.

📒 Files selected for processing (8)
  • cli/src/index.ts
  • host/src/windows_host.zig
  • runtime/src/bridge.zig
  • runtime/src/geometry.zig
  • runtime/src/js_engine.zig
  • runtime/src/main.zig
  • runtime/src/tree.zig
  • runtime/src/widget_log.zig
🚧 Files skipped from review as they are similar to previous changes (1)
  • runtime/src/js_engine.zig

@SunkenInTime

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@SunkenInTime

Copy link
Copy Markdown
Owner Author

@greptileai

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- Expand agent guidance with project context and shared terminology
- Clarify measurement-backed limits, visible failures, and obvious solutions
@SunkenInTime
SunkenInTime marked this pull request as ready for review July 30, 2026 05:38
@SunkenInTime
SunkenInTime merged commit 82f07f3 into master Jul 30, 2026
3 of 4 checks passed
@SunkenInTime
SunkenInTime deleted the agent/error-propagation-seams branch July 30, 2026 05:38
SunkenInTime pushed a commit that referenced this pull request Jul 30, 2026
Native SDK branch macos-memory-shared-renderer-prep (600d6cf6) carries
the autorelease-pool, analytic-rounded-clip, and Metal tiled-image
memory work; the tiled-image change still needs live verification.
The briefs scope the two follow-up passes: the error-propagation seams
(partially landed via #43) and the receipt sweep over every numeric
limit. The shared-renderer experiment plan lives in the handoff doc
accompanying this work.
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