feat(native): mer native — WebView shell + window.mer.invoke bridge (P0–P3) - #100
feat(native): mer native — WebView shell + window.mer.invoke bridge (P0–P3)#100pranavp311 wants to merge 86 commits into
Conversation
🤝 Review + live-test handoff (for the next agent · cc @pranavp311)Picked this up, reviewed it, and tested ✅ Verified working
🔧 Issues to fix before merge
📝 Nits (non-blocking)
ℹ️ Not a code issueCouldn't grab a screenshot of the native window from this environment — macOS screen-capture needs the host app's Accessibility/Screen-Recording grants, unrelated to the PR. Rendering is 1:1 regardless (WKWebView = Safari's WebKit; served SSR matches source byte-for-byte). For sanity I also stood up a real Next.js 15 app rendering the same UI: merjs native 5.8 MB / ~55 ms cold start / zero runtime vs Next.js ~322 MB on disk (node_modules + .next) / 773 ms Handoff state: the 3 fixes are all one-liners; nothing pushed, all testing was in a throwaway worktree. Next agent: knock out 1–3, re-run |
|
Raised owner review/merge tracker: #101. Latest PR branch push includes the blocker fixes from review:
Local verification passed:
@justrach please review and merge PR #100 into release/v0.2.53 if the changes look good. |
|
Follow-up pushed for the non-blocking owner review nits: 55eb3bd. Addressed:
Verification passed locally:
The only local unstaged file left is the pre-existing codedb.snapshot change, not included in the PR commits. |
|
Updated this PR with the native-shell side of the Cmd+W/window close fix in 23cca54 (pushed to feat/mer-native):
Validated in the MerJS repo:
The Codegraff GUI-side usage/manifest changes are committed separately to the Codegraff PR (#84). |
Issue/PR trajectory: - Read the mer native epic and PR justrach#100 state against the local feat/mer-native branch. The feature is now macOS-first P0-P3 plus follow-up built-ins: manifest-driven WKWebView shell, ServerReady loopback binding, window.mer.invoke, clipboard/dialog/open/window commands, packaging, and docs. - Reconciled owner/reviewer concerns with the local implementation instead of expanding scope: keep Linux/Windows/CEF/static mer://app/signing/custom registries deferred; keep PR justrach#100 focused on a safe macOS system-WebView release. Security trajectory: - Verified the shell already prepends the exact runtime loopback origin after port=0 binds, so portless manifest origins must not wildcard all 127.0.0.1 ports. Kept structured scheme/host/port matching and documented that runtime origin injection is what makes ephemeral binding work. - Hardened the macOS WKScriptMessage path before bridge.dispatch: measure the full NSString UTF-8 byte length, reject oversized direct posts before C-string truncation, and reject embedded-NUL payloads that would otherwise let UTF8String/std.mem.span parse only a trusted prefix. Docs trajectory: - Updated docs/native.md to include the implemented window.close command and to describe the current frame-origin/global-origin policy accurately. - Updated plans/mer-native.md so the plan matches the shipped PR surface: built-in registry plus top-level permissions/global origins now; bridge.commands per-command allowlists and app-level custom registries later; real built-ins instead of old dialog/clipboard stubs. Validation trajectory: - zig build test - zig build cli - zig build native-build -Doptimize=ReleaseSmall - zig build package -Doptimize=ReleaseSmall - git diff --check
064c8cb to
eed2c28
Compare
Issue/PR trajectory: - Read the mer native epic and PR justrach#100 state against the local feat/mer-native branch. The feature is now macOS-first P0-P3 plus follow-up built-ins: manifest-driven WKWebView shell, ServerReady loopback binding, window.mer.invoke, clipboard/dialog/open/window commands, packaging, and docs. - Reconciled owner/reviewer concerns with the local implementation instead of expanding scope: keep Linux/Windows/CEF/static mer://app/signing/custom registries deferred; keep PR justrach#100 focused on a safe macOS system-WebView release. Security trajectory: - Verified the shell already prepends the exact runtime loopback origin after port=0 binds, so portless manifest origins must not wildcard all 127.0.0.1 ports. Kept structured scheme/host/port matching and documented that runtime origin injection is what makes ephemeral binding work. - Hardened the macOS WKScriptMessage path before bridge.dispatch: measure the full NSString UTF-8 byte length, reject oversized direct posts before C-string truncation, and reject embedded-NUL payloads that would otherwise let UTF8String/std.mem.span parse only a trusted prefix. Docs trajectory: - Updated docs/native.md to include the implemented window.close command and to describe the current frame-origin/global-origin policy accurately. - Updated plans/mer-native.md so the plan matches the shipped PR surface: built-in registry plus top-level permissions/global origins now; bridge.commands per-command allowlists and app-level custom registries later; real built-ins instead of old dialog/clipboard stubs. Validation trajectory: - zig build test - zig build cli - zig build native-build -Doptimize=ReleaseSmall - zig build package -Doptimize=ReleaseSmall - git diff --check
|
@pranavp311 this native work is awesome - we built and launched Before we treat this as production-ready / merge-ready, can you add or split out a follow-up plan for the remaining native hardening pieces? Requested additions:
I looked through the current implementation with codedb/codedb-pro and here are the concrete places that seem like the right integration points: Packaging / signing / notarizationCurrent macOS packaging lives in
Suggested direction:
Auto-updaterThere is currently no updater layer. Good places to hook it:
Possible manifest shape: .update = .{
.provider = "github-releases",
.feed_url = "https://...",
.public_key = "...",
}Would be good to support signed update manifests/artifacts rather than downloading arbitrary binaries. Linux + Windows supportRight now if (builtin.os.tag != .macos) return error.UnsupportedPlatform;and dispatches only to: @import("macos.zig").openWindow(...)Current backend files:
Suggested additions:
Hardened permission modelCurrent bridge checks are a good start:
Recommended hardening:
Plugin systemCurrent bridge registry is static in pub const registry = [_]Command{ ... };That works for built-ins, but a mature plugin system probably needs:
Production security auditWould love a written checklist/doc before production claims. Areas to audit:
Also small PR cleanup before merge:
Again, the core feature works locally and feels excellent - this is mostly the production-readiness checklist for making |
|
@pranavp311 I opened a tracking issue with the production-readiness checklist and concrete acceptance tests here: #103 |
|
Thanks for the native work here — I pulled the branch and did a local verification pass. A few follow-ups before this is ready:
During local testing I hit |
|
One more verification request for the Linux side: Please use Suggested evidence to include: # from a clean container / clean checkout
zig version
zig build test
zig build cli
zig build worker
zig build wasm
zig build prodIf native macOS packaging is intentionally macOS-only, that’s fine — the goal here is to show the non-native framework/CLI paths still work cleanly on Linux after the native additions, and to have the container log in the PR for review/release confidence. |
|
Follow-up pushed in What changed:
Validation on macOS / Zig 0.16.0: zig version
# 0.16.0
zig build test
zig build cli
zig build worker
zig build wasm
zig build prod
zig build native-build -Doptimize=ReleaseSmall
zig build package -Doptimize=ReleaseSmall
git diff --checkAll passed locally. Also verified the production gate still fails closed on the demo manifest, as expected, with missing signing/notary/update metadata: zig build native-prod-check
# exits 1 with missing .macos.signing_identity, .macos.notarization_profile,
# .update.provider, .update.feed_url, .update.public_keyI also had an isolated reviewer agent inspect the uncommitted diff before commit; it found no production-readiness blockers. Remaining external evidence: I do not have |
|
Added a Linux/Windows native-platform planning + safe Phase 1 prep commit: Scope deliberately stays conservative: it does not claim Linux/Windows runtime support yet. It prepares the shared code so those backends can be added without weakening the macOS bridge/security model. What changed:
Validation passed locally: zig build test
zig build cli
zig build worker
zig build wasm
zig build prod
zig build native-build -Doptimize=ReleaseSmall
zig build package -Doptimize=ReleaseSmall
zig test src/native/platform_commands.zig -target x86_64-linux --test-no-exec
zig test src/native/platform_commands.zig -target x86_64-windows --test-no-exec
zig test src/native/bridge.zig -target x86_64-linux --test-no-exec -lc
zig test src/native/bridge.zig -target x86_64-windows --test-no-exec
git diff --checkI also had a thorough reviewer agent inspect this platform-prep diff. Initial blockers were found around Windows bridge compile assumptions and host-vs-target framework linking; both were fixed and the final review found no blockers. |
|
Clean Linux evidence using Host/tooling: container --version
# container CLI version 1.0.0 (build: release, commit: ee848e3)
container system status
# status: runningContainer command used from macOS host: container run --rm -i --memory 8G \
--mount type=bind,source=/tmp/container-mount,target=/mnt/host,readonly \
docker.io/library/debian:bookworm bash -s <<'SH'
set -euxo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends ca-certificates git xz-utils build-essential libc-dev
mkdir -p /opt/zig
tar -xJf /mnt/host/zig-aarch64-linux-0.16.0.tar.xz -C /opt/zig --strip-components=1
export PATH=/opt/zig:$PATH
mkdir -p /work
cd /work
git clone --depth 1 --branch feat/mer-native https://github.com/pranavp311/merjs.git merjs
cd merjs
git rev-parse --short HEAD
zig version
zig build test
zig build cli
zig build worker
zig build wasm
zig build prod
SHRelevant output: While getting this running, the first clean Linux pass exposed a Zig 0.16 |
|
Latest native hardening update pushed in What changed in this follow-up:
Important scope note: this still does not claim runtime auto-update download/install or Ed25519 cryptographic verification. This PR adds the fail-closed structural contract and docs for future updater runtime work. Final validation after push on macOS / Zig 0.16.0: zig test src/native/update.zig ✅
zig test src/native/bridge.zig -lc -framework AppKit -framework Foundation ✅
zig build test ✅
zig build cli ✅
zig build worker ✅
zig build wasm ✅
zig build prod ✅
git diff --check ✅
git status --short ✅ cleanReview/audit summary:
|
Issue/PR trajectory: - Read the mer native epic and PR justrach#100 state against the local feat/mer-native branch. The feature is now macOS-first P0-P3 plus follow-up built-ins: manifest-driven WKWebView shell, ServerReady loopback binding, window.mer.invoke, clipboard/dialog/open/window commands, packaging, and docs. - Reconciled owner/reviewer concerns with the local implementation instead of expanding scope: keep Linux/Windows/CEF/static mer://app/signing/custom registries deferred; keep PR justrach#100 focused on a safe macOS system-WebView release. Security trajectory: - Verified the shell already prepends the exact runtime loopback origin after port=0 binds, so portless manifest origins must not wildcard all 127.0.0.1 ports. Kept structured scheme/host/port matching and documented that runtime origin injection is what makes ephemeral binding work. - Hardened the macOS WKScriptMessage path before bridge.dispatch: measure the full NSString UTF-8 byte length, reject oversized direct posts before C-string truncation, and reject embedded-NUL payloads that would otherwise let UTF8String/std.mem.span parse only a trusted prefix. Docs trajectory: - Updated docs/native.md to include the implemented window.close command and to describe the current frame-origin/global-origin policy accurately. - Updated plans/mer-native.md so the plan matches the shipped PR surface: built-in registry plus top-level permissions/global origins now; bridge.commands per-command allowlists and app-level custom registries later; real built-ins instead of old dialog/clipboard stubs. Validation trajectory: - zig build test - zig build cli - zig build native-build -Doptimize=ReleaseSmall - zig build package -Doptimize=ReleaseSmall - git diff --check
f11afdf to
1f52db5
Compare
|
Rebase / updater / audit follow-up is pushed and PR #100 is now retargeted to Latest head: What changed:
Validation on macOS / Zig 0.16.0 after rebase to zig test src/native/update.zig ✅
zig build test ✅
zig build cli ✅
zig build worker ✅
zig build wasm ✅
zig build prod ✅
zig build native-build -Doptimize=ReleaseSmall ✅
zig build package -Doptimize=ReleaseSmall ✅
git diff --check ✅
git status --short ✅ cleanPR state after retarget: |
|
Added and pushed a zero-trust-oriented native bridge hardening pass in
Independent reviewer subagents audited the token/origin model; final focused review found no high/medium defects. Validation on macOS / Zig 0.16.0: zig test src/native/bridge.zig
# 29 passed; 1 skipped; 0 failed
zig build test
zig build cli
zig build worker
zig build wasm
zig build prod
zig build native-build -Doptimize=ReleaseSmall
zig build package -Doptimize=ReleaseSmall
git diff --checkAll passed locally. I restored regenerated tracked worker Positioning: mer native is now more zero-trust-oriented, fail-closed, and least-agency by default for bridge dispatch; it is not claiming fully mature/certified zero trust yet. |
|
Addressed the latest PR #100 readiness feedback and pushed Plan executed for all reported items (including nice-to-fix):
Reviews:
Validation on macOS / Zig 0.16.0: zig test src/native/bridge.zig
zig test src/native/manifest.zig
zig test src/native/update.zig
zig build test
zig build cli
zig build worker
zig build wasm
zig build prod
zig build native-build -Doptimize=ReleaseSmall
zig build package -Doptimize=ReleaseSmall
zig test src/native/platform_commands.zig -target x86_64-linux --test-no-exec
zig test src/native/platform_commands.zig -target x86_64-windows --test-no-exec
zig test src/native/bridge.zig -target x86_64-linux --test-no-exec -lc
zig test src/native/bridge.zig -target x86_64-windows --test-no-exec
git diff --checkAll passed locally. Confirmed Additional expected checks: zig build package-sign -Doptimize=ReleaseSmall
# fails with only missing signing identity, not the full production gate
zig build native-prod-check
# intentionally fails for the demo/dev manifest because real signing/notary/update trust-root metadata is not configuredPR readiness review:
|
|
@codegraff-bot review? |
|
On it — I picked up:
Running on your Codegraff account — I'll post my review here when it's done. |
Keep the complete interactive utility demonstration in one page-sized exception because splitting markup sections would not yield independently useful behavior. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Add semantic page structure separately from its generated design stylesheet so content and presentation can be reviewed independently. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Add the cohesive generated dashboard stylesheet as a CSS-file exception; arbitrary rule splitting would make visual review harder, not easier. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Add the versioned release route and semantic markup before its isolated presentation layer. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Keep the versioned generated stylesheet intact as a CSS-file exception so cascade and responsive behavior remain reviewable together. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Separate budget policy and host-fetch bridging from the main Worker so their validation and resource bounds are focused. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Apply bounded request, AI, and streaming behavior in the deployment entry point as a single-file exception because they share cancellation state. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Commit the required npm lockfileVersion 3 separately from source to pin Wrangler 4.118.0 and transitive deployment tools; regenerate it with npm install in examples/site/worker/worker. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Wire Worker package metadata and Wrangler configuration only after runtime and bridge modules are complete. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Remove checked-in build outputs from site and Worker examples so clean builds prove every required artifact dependency. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Update component helpers, routes, and build wiring together because the showcase is a compact consumer-level compatibility fixture. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Update the Vercel adapter, configuration, documentation, and matching runtime artifact together because deployment tests consume the bundled WASM contract. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Harden the primary and package-facing shell installers together so platform selection, download, and extraction follow one release contract. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Add hermetic installer and release-version checks separately from implementation so failure cases remain explicit review evidence. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Update the documentation and Cloudflare-hosted installer after the canonical installer contract is fixed, keeping each distribution path consistent. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Cover clean deployment assembly and generated snapshot behavior together because both validate the packaged Worker artifact graph. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Exercise bridge cancellation, bounds, and response semantics in a focused test module independent of deployment assembly. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Remove superseded browser fixtures now that deployment and bridge tests cover the maintained runtime paths. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Explain utility syntax, Tailwind mappings, and Next.js migration together so users can evaluate the complete CSS compatibility surface. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Document the native application model and manifest workflow as one review-sized user guide. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Keep platform support and zero-trust guidance together because capability differences determine the exposed security boundary. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Explain signing, notarization, updates, and durable anti-replay state independently from feature planning so release owners have an actionable runbook. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Retain the native design plan separately from user documentation because it records scope and trade-offs rather than operational instructions. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Refresh public primitives, architecture, README, and security policy after all runtime surfaces are finalized. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
Update Linux, macOS, npm, PyPI, beta, and release workflows together because they enforce one cross-platform publication contract and remain within the review-size guideline. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
4d2a6e8 to
ce5c38b
Compare
History reconstruction completeI rewrote What changed
Size profile
Why this structureThe approximate 400-line target is applied as a reviewability guideline, not by creating syntactically invalid partial Zig files. Cohesive state machines/protocol modules remain atomic where arbitrary hunk splitting would make commits non-buildable or misleading. Verification
GitHub still shows no hosted checks or approving review on the rewritten head, so workflow approval/execution and maintainer review remain merge gates. |
|
Superseded by #107, a clean restart from release/v0.2.53. The replacement keeps the first macOS runtime slice to 8 files / 499 changed lines, with every commit below 400 lines. This PR remains available as design and follow-up reference; its pre-rewrite tree is also preserved on backup/feat-mer-native-before-history-rewrite-4d2a6e8. |
Closes #101
Summary
Implements
mer nativefor macOS: a Zig native shell hosting the system WebView (WKWebView) over the merjs loopback server, with a hardenedwindow.mer.invoke()JS↔Zig bridge and manifest-driven.apppackaging. No Electron, no bundled Chromium, no Node.This follows the zero-native model. merjs already owns the server half (
src/server.zig, routing, SSR, watcher, static assets); the native layer adds the WebView window, bridge, production gates, and packaging hooks.Developer experience
mer nativereuses the existing codegen/server/watcher pipeline and attaches a WebView window once the server reports its bound port. UI edits hot-reload inside the native window via the existing/_mer/eventsSSE channel.What shipped
Native shell + CLI
New
src/native/module, re-exported asmer.native:shell.zig— loopback server onport=0,ServerReadyhandshake, watcher/static-dir options, platformopenWindow.macos.zig— WKWebView + NSWindow via extern ObjC primitives, no@cImport.manifest.zig— comptime parse/defaulting ofmer.app.zon.bridge.zig— hardenedwindow.mer.invokedispatch.platform_commands.zig/macos_commands.zig— platform command facade and macOS command implementations.update.zig— structural update feed/config validation helpers.main.zig— native binary entry with--dev/--no-dev.CLI/build additions:
mer native,mer native build,mer package,mer add native,mer native doctor.zig build native,native-build,package,package-sign,package-notarize,native-prod-check,native-prod-release.mer.app.zon+examples/starter/templates.Hardened bridge/security model
Implemented bridge protections:
127.0.0.1recommended;localhostintentionally rejected).security.bridge.allowed_commands.security.bridge.command_origins.open.externalscheme allowlist.open.pathfails closed unless explicitsecurity.open.path_rootsare configured.Built-in commands:
mer.ping,mer.echoclipboard.read,clipboard.writedialog.openFile,dialog.pickDirectory,dialog.openDirectoryopen.external,open.pathwindow.setTitle,window.closeStatic custom command registry
Adds a safe static extension point without dynamic plugin loading:
Shell.RunOpts.commandsbridge.Ctx.extra_commandsbridge.dispatchWithRegistry(...)Custom commands fail closed unless they:
allowed_commands;Invalid custom registries resolve the caller with
InvalidRegistryand invoke no built-in or custom handlers.Packaging, signing, notarization, production gate
Packaging hardening:
.appbundle /Info.plist.Production/release hooks:
package-signruns hardened-runtimecodesign.package-notarizesubmits vianotarytooland staples.native-prod-checkfails closed unless manifest production hardening is present:Update feed/config validation
Adds structural validation for future updater inputs:
github-releases,custom-http;ed25519:-tagged public key/signature fields;(os, arch)platform entries;N[.N[.N]]versions;min_supported_version <= versionrollback-window validation;This PR implements Ed25519 verification for signed update metadata plus artifact byte verification (size/SHA-256). It still does not perform automatic install/self-replacement; platform-specific installer and durable rollback storage remain deferred.
Server/runtime fixes requested during review
std.Io.Evented/io_uring stdlib issues.Linux/Windows platform planning
docs/native-platforms.mdwith staged Linux WebKitGTK and Windows WebView2 plan.Verification
Final local validation on macOS / Zig 0.16.0 after latest push (
1f52db5):Additional macOS validation from earlier commits:
Clean Linux evidence using Apple
containeron Debian bookworm / Zig 0.16.0 passed on branch commit673d02fbefore the latest native-only hardening commit:Review / audit notes
Codebase-aware reviewer agents reviewed:
A strict independent internal security-agent audit reviewed the updater/custom registry/security-hardening changes. Findings were fixed, including:
open.pathfail-closed defaults;Final strict security re-audit found no remaining critical/high/medium blockers in the audited areas.
Current PR base / retarget note
This PR has been transplanted with
git rebase --onto upstream/main origin/feat/mercss-responsiveand retargeted tomain. Range-diff review found the native series preserved, with follow-up fixes for main-only worker/WASM build compatibility.Deferred / explicitly not claimed
mer://appremains separate.Notable files