Skip to content

Cut macOS widgets over to the shared renderer - #47

Merged
SunkenInTime merged 5 commits into
masterfrom
agent/macos-shared-renderer-cutover
Jul 31, 2026
Merged

Cut macOS widgets over to the shared renderer#47
SunkenInTime merged 5 commits into
masterfrom
agent/macos-shared-renderer-cutover

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Jul 31, 2026

Copy link
Copy Markdown
Owner

The weaver half of slice 4 (native half: budget tripwire in SunkenInTime/native#24, at 5/5). This makes the shared renderer the macOS default for weaverd-owned widgets — the cutover the whole plan (docs/macos-memory-handoff.md on feat/macos-memory-work) builds to.

What changes

  • weaver-widget --render-host <bootstrap-name>: the process becomes the shared renderer — the only Metal-owning process on the desktop — and never loads a widget bundle.
  • weaverd owns the host lifecycle: spawned at startup, supervised like a widget (crash → respawn after a 1 s backoff). Widgets never notice beyond one retained-frame beat: they keep their last frame on glass and reconnect on their existing 1/5/30 s retry cadence (proven live below).
  • Cutover env: weaverd-spawned widgets get NATIVE_SDK_GPU_SHARED_RENDERER=1 + the host's bootstrap name. The automation seam keeps the legacy in-process renderer (its correctness verdicts sample the widget's own drawable) — converting automation is future work, deliberately not smuggled in here.
  • Submodule pinned to weaver-main b72da2d2 (shared-renderer slices 1–3).

Acceptance receipts (Mac15,6, macOS 26.5.2, isolated-HOME weaverd, 8 registered clock widgets)

Gate Result
Per-widget footprint 32.2–35.8 MB each (in-process baseline: 125.3 MB)
Graphics arena on widget ledgers zero owned-unmapped-graphics regions in all 8 (baseline: 85 MB / 33 regions each)
31-minute steady state every widget flat within ±0.3 MB; host non-increasing (168.5 → 158.9 MB, −165 KB/min)
Host crash drill kill -9 on the host → weaverd log names the restart, respawn in 1 s → widgets reconnected and resumed live rendering (window-capture diff shows the clock ticking)
Env injection verified via ps -E: both variables present on weaverd-spawned widgets
Suites runtime test-platform-services and host zig build test pass

Full evidence trail (Phase 1 spike gates, per-slice receipts, drift CSVs) lives in docs/macos-memory-handoff.md on feat/macos-memory-work and .zig-cache/macos-memory/ receipts on this machine.

Follow-ups (named, not smuggled)

  • Automation-seam conversion to the shared renderer.
  • Bump the submodule pin to include native#24 (frame-budget tripwire in the host) once it merges.
  • Windows already works this way; weaver dev / standalone launches keep in-process rendering (no weaverd, no host) by design.

Summary by CodeRabbit

  • New Features

    • Added shared rendering on macOS for improved stability.
    • Rendering automatically restarts after crashes or launch failures.
    • Added configurable render-host naming through the environment.
    • Added support for launching the application in render-host mode.
  • Bug Fixes

    • Rendering now stops cleanly during application shutdown.
    • Improved cleanup and handling of render-process state.
    • Updated native runtime components and release validation references.

Dara Adedeji added 2 commits July 31, 2026 00:03
The macOS cutover from the shared-renderer plan
(docs/macos-memory-handoff.md on feat/macos-memory-work): weaverd owns
one render host — the only Metal-owning process — and every
weaverd-spawned widget renders through it device-lessly.

- weaver-widget gains --render-host <bootstrap-name>: the process
  becomes the shared renderer (native_sdk_appkit_render_host_run) and
  never loads a widget bundle.
- weaverd spawns the host at startup and supervises it like a widget:
  crash -> respawn after a 1 s backoff; widgets keep their retained
  frames and reconnect on their own (their existing 1/5/30 s retry
  cadence). Widget environments get NATIVE_SDK_GPU_SHARED_RENDERER=1
  and the host's bootstrap name. The automation seam keeps the legacy
  in-process renderer (its verdicts sample the widget's own drawable).
- native-sdk pinned to weaver-main b72da2d2 (shared-renderer slices
  1-3: IOSurface presenter, render host + mach protocol, device-less
  client).

Acceptance (Mac15,6, isolated-HOME weaverd, 8 registered clock
widgets): every widget 32.2-35.8 MB phys_footprint with ZERO
owned-unmapped-graphics regions (in-process baseline: 125.3 MB with
the 85 MB arena each); 31-minute hold flat within ±0.3 MB per widget,
host non-increasing (168.5 -> 158.9 MB, slope -165 KB/min); host
killed -> weaverd restarted it in 1 s and the widgets resumed live
rendering, verified by window-capture diff. Runtime
test-platform-services and host test suites pass.
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

This PR moves weaverd-managed macOS widgets to a supervised shared renderer. It adds render-host mode to weaver-widget, starts and monitors the renderer from weaverd, injects shared-renderer configuration into managed widgets, and updates the native SDK release-audit expectation.

No product defect was established from the available execution results.

T-Rex validation blocked

Missing tool: Zig — the required native lifecycle checks could not run because this Linux worker does not have zig, macOS SDK/Xcode tooling (xcrun), or CMake. Both host test-build attempts failed with zig: not found; the provided macOS smoke suites skipped on Linux, so they did not exercise daemon startup, renderer restart, shutdown, or recovery-marker removal. Configure VMs

Confidence Score: 5/5

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex attempted narrow host test builds on both revisions using Zig-based commands and saw exit 127 because Zig is not installed.
  • macOS host smoke suites were run but skipped since the run was outside macOS, so no daemon or shared render-host lifecycle was exercised.
  • The Linux worker lacks Zig, macOS SDK/Xcode (xcrun), and CMake, blocking a macOS native build and the required live shutdown/recovery check.
  • The Node smoke command exited 0 only because both macOS-only suites explicitly skipped, so this is not product-path validation.
  • Artifacts containing the test outputs and skip conditions were created and uploaded for review.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (2): Last reviewed commit: "Reap the render host before dropping its..." | Re-trigger Greptile

Comment thread host/src/macos_host.zig
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@SunkenInTime, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b9b0147b-19cd-4211-b391-4d204e66d1d7

📥 Commits

Reviewing files that changed from the base of the PR and between bd83ac7 and 28846c3.

📒 Files selected for processing (2)
  • host/src/macos_host.zig
  • runtime/src/main.zig
📝 Walkthrough

Walkthrough

The runtime adds a macOS render-host command. The macOS host supervises one shared render process, restarts it after failure, passes its configuration to non-automation widgets, and stops it during shutdown. The Native SDK audit reference is updated.

Changes

Shared render-host integration

Layer / File(s) Summary
Render-host entry point
runtime/src/main.zig, runtime/native-sdk, scripts/release-audit.mjs
The runtime accepts --render-host <bootstrap-name> and invokes native_sdk_appkit_render_host_run. The Native SDK submodule and release-audit commit reference are updated.
Host supervision and widget wiring
host/src/macos_host.zig
The host configures the render-host name, starts and monitors one shared render process, retries failures after a one-second delay, records child markers, passes render-host variables to non-automation widgets, and terminates the process during shutdown.

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

Sequence Diagram(s)

sequenceDiagram
  participant HostMainLoop
  participant macos_host.zig
  participant RenderHostProcess
  participant NativeSDKAppKit
  HostMainLoop->>macos_host.zig: supervise shared render host
  macos_host.zig->>RenderHostProcess: spawn with bootstrap name
  RenderHostProcess->>NativeSDKAppKit: start AppKit render host
  macos_host.zig->>RenderHostProcess: send SIGTERM during shutdown
Loading

Possibly related PRs

  • SunkenInTime/weaver#43: Both changes modify host/src/macos_host.zig and address shared render-process lifecycle supervision.
🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: moving macOS widgets to the shared renderer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/macos-shared-renderer-cutover

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

@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: 2

🤖 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 `@host/src/macos_host.zig`:
- Around line 613-618: Update stopRenderHost to preserve the recovery marker
until the render host has exited: send the bounded TERM signal, wait with the
same TERM/KILL timeout and waitpid sequence used by stopSlot, then remove the
child marker and clear render_host_process only after reaping succeeds. Reuse
stopSlot’s existing lifecycle behavior rather than introducing a separate
shutdown strategy.

In `@runtime/src/main.zig`:
- Around line 1204-1206: Update the usage output in the argument-validation
block around the dev/render-host parsing so each accepted command form is
printed separately, with <widget-directory> attached only to the applicable
form. Emit the --render-host usage form only on macOS builds, while preserving
the existing dev and regular widget-directory forms.
🪄 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: a13c3e73-7254-4960-a6c9-3185df81e9a7

📥 Commits

Reviewing files that changed from the base of the PR and between 476917a and 7ae4dee.

📒 Files selected for processing (4)
  • host/src/macos_host.zig
  • runtime/native-sdk
  • runtime/src/main.zig
  • scripts/release-audit.mjs

Comment thread host/src/macos_host.zig
Comment thread runtime/src/main.zig Outdated
Comment on lines +1204 to +1206
const dev = args.len == 3 and std.mem.eql(u8, args[1], "--dev");
if ((!dev and args.len != 2) or (dev and args.len != 3)) {
std.debug.print("usage: weaver-widget [--dev] <widget-directory>\n", .{});
std.debug.print("usage: weaver-widget [--dev | --render-host <bootstrap-name>] <widget-directory>\n", .{});

@coderabbitai coderabbitai Bot Jul 31, 2026

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 | 🟡 Minor | ⚡ Quick win

Fix the render-host usage grammar.

Because the render-host branch returns before widget-directory parsing, it accepts only --render-host <bootstrap-name>. Line 1206 places <widget-directory> outside the alternative and advertises an invalid command. The message also advertises this macOS-only option on non-macOS builds.

Print separate usage forms and include the render-host form only on macOS.

Proposed fix
-        std.debug.print("usage: weaver-widget [--dev | --render-host <bootstrap-name>] <widget-directory>\n", .{});
+        const usage = if (builtin.os.tag == .macos)
+            "usage: weaver-widget <widget-directory> | weaver-widget --dev <widget-directory> | weaver-widget --render-host <bootstrap-name>\n"
+        else
+            "usage: weaver-widget <widget-directory> | weaver-widget --dev <widget-directory>\n";
+        std.debug.print(usage, .{});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const dev = args.len == 3 and std.mem.eql(u8, args[1], "--dev");
if ((!dev and args.len != 2) or (dev and args.len != 3)) {
std.debug.print("usage: weaver-widget [--dev] <widget-directory>\n", .{});
std.debug.print("usage: weaver-widget [--dev | --render-host <bootstrap-name>] <widget-directory>\n", .{});
const dev = args.len == 3 and std.mem.eql(u8, args[1], "--dev");
if ((!dev and args.len != 2) or (dev and args.len != 3)) {
const usage = if (builtin.os.tag == .macos)
"usage: weaver-widget <widget-directory> | weaver-widget --dev <widget-directory> | weaver-widget --render-host <bootstrap-name>\n"
else
"usage: weaver-widget <widget-directory> | weaver-widget --dev <widget-directory>\n";
std.debug.print(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/main.zig` around lines 1204 - 1206, Update the usage output in
the argument-validation block around the dev/render-host parsing so each
accepted command form is printed separately, with <widget-directory> attached
only to the applicable form. Emit the --render-host usage form only on macOS
builds, while preserving the existing dev and regular widget-directory forms.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed: teardown now escalates exactly like widget teardown (TERM, 1.5 s reap window, KILL, blocking reap) and removes the recovery marker only after the process is dead and reaped; the usage string now shows the two argument shapes honestly.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline runtime/src/main.zig --items all --type function --match main
sed -n '1190,1225p' runtime/src/main.zig

Length of output: 2377


@SunkenInTime, the argument shapes are now correct.

One part of the finding remains. Non-macOS builds still print weaver-widget --render-host <bootstrap-name>, but the parser accepts that option only when builtin.os.tag == .macos.

Please conditionally include the render-host usage form on macOS. I will mark the comment addressed after that change.

You are interacting with an AI system.

Dara Adedeji added 3 commits July 31, 2026 00:36
Review is right that teardown removed the marker, sent a best-effort
TERM, and forgot the pid — a TERM-ignoring host would outlive its
marker and the next weaverd could not find the orphan. Teardown now
escalates exactly like widget teardown (TERM, 1.5 s reap window, KILL,
blocking reap) and removes the marker only after the process is dead.
Also fixes the usage string to show the two argument shapes honestly.
@SunkenInTime

Copy link
Copy Markdown
Owner Author

@coderabbitai The usage string is now comptime-conditional: non-macOS builds print only the widget-directory form, matching what the parser accepts.

@SunkenInTime
SunkenInTime merged commit a59b0c6 into master Jul 31, 2026
4 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