Skip to content

Commit b515df0

Browse files
committed
chore(version): v0.5.327 — close #191 wire CameraView through codegen
Wires the CameraView widget through every codegen path (LLVM, JS, WASM) so user code calling CameraView() / cameraStart / cameraStop / cameraFreeze / cameraUnfreeze / cameraSampleColor / cameraSetOnTap resolves on every target. Pre-fix the runtime FFI was implemented in perry-ui-ios (AVCaptureSession) and perry-ui-android (Camera2) but no codegen had a dispatch row; calls fell through the receiver-less early-out and silently returned 0.0. Adds: - 7 rows to PERRY_UI_TABLE in crates/perry-codegen/src/lower_call.rs - 7 arms each in JS + WASM emit map_ui_method - 7 browser stubs in wasm_runtime.js + dispatch table registration - macOS / GTK4 / Windows no-op stubs (tvOS / visionOS / watchOS already had them from prior audit work) - TS declarations under a new "Camera" section in types/perry/ui/index.d.ts - docs/examples/ui/camera/snippets.ts (compile-only with `run: false`) - docs/src/ui/camera.md drops the not-yet-wired callout, replaces it with the platform-support matrix, converts all 10 ,no-test fences to {{#include}} extracts Verified: cargo build clean, ./perry compile --no-link emits all 7 camera FFI calls as undefined references in the .o, full link produces a 0.9 MB macOS executable that exits 0 in PERRY_UI_TEST_MODE=1, ./target/release/doc-tests --filter camera passes 1/1 (1080 ms), full sweep is 79/81 same as v0.5.325 baseline. Version slot bumped above origin's v0.5.326 (slider FFI calling-convention fix, parallel-track on origin) per the merge precedent for collisions.
1 parent fe3d705 commit b515df0

13 files changed

Lines changed: 356 additions & 86 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
88

99
Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation.
1010

11-
**Current Version:** 0.5.326
11+
**Current Version:** 0.5.327
1212

1313
## TypeScript Parity Status
1414

@@ -149,6 +149,7 @@ First-resolved directory cached in `compile_package_dirs`; subsequent imports re
149149

150150
Keep entries to 1-2 lines max. Full details in CHANGELOG.md.
151151

152+
- **v0.5.327** — Closes #191: wires the `CameraView` widget through every codegen path (LLVM, JS, WASM) so user code calling `CameraView()` / `cameraStart` / `cameraStop` / `cameraFreeze` / `cameraUnfreeze` / `cameraSampleColor` / `cameraSetOnTap` resolves on every target instead of failing with "unknown function" at compile time. Pre-fix the runtime FFI was implemented in `crates/perry-ui-ios/src/lib.rs` (AVCaptureSession) and `crates/perry-ui-android/src/lib.rs` (Camera2) but no codegen had a dispatch row; calls reached the receiver-less early-out and silently returned 0.0. Five-part landing: (1) **LLVM codegen** — 7 new rows in `PERRY_UI_TABLE` (`crates/perry-codegen/src/lower_call.rs`): `CameraView` → `perry_ui_camera_create` (no args, returns Widget), `cameraStart`/`cameraStop`/`cameraFreeze`/`cameraUnfreeze` → matching `perry_ui_camera_*` setters (Widget arg, Void return), `cameraSampleColor` → `perry_ui_camera_sample_color` (F64×2 args, F64 return — packed RGB or -1), `cameraSetOnTap` → `perry_ui_camera_set_on_tap` (Widget + Closure). (2) **JS codegen** (`crates/perry-codegen-js/src/emit.rs`) + **WASM codegen** (`crates/perry-codegen-wasm/src/emit.rs::map_ui_method`) — 7 new arms in each routing the TS names to the canonical `perry_ui_camera_*` symbols. (3) **WASM runtime stubs** (`crates/perry-codegen-wasm/src/wasm_runtime.js`) — `perry_ui_camera_create` allocates a placeholder `<div>` so layout slots reserve the same space they would for a real preview, `perry_ui_camera_sample_color` returns -1 (matching the documented "no frame available" sentinel), the rest are no-ops. All 7 registered in `__perryUiDispatch`. (4) **Desktop no-op stubs** added to `perry-ui-macos/src/lib.rs`, `perry-ui-gtk4/src/lib.rs`, `perry-ui-windows/src/lib.rs` — `perry_ui_camera_create` returns 0, `perry_ui_camera_sample_color` returns -1.0, the rest are no-ops. tvOS / visionOS / watchOS already had stubs from earlier audit work. (5) **TS surface** — `types/perry/ui/index.d.ts` adds the 7 declarations under a new "Camera (issue #191)" section with platform-support documentation; `docs/examples/ui/camera/snippets.ts` (compile-only with `run: false` banner since the camera needs hardware permissions) pins every name down so a future rename / drop trips a link error in CI; `docs/src/ui/camera.md` drops the "Status: not yet wired into any codegen path" callout, replaces it with the platform-support matrix (iOS+Android real, others no-op stubs), and converts all 10 `,no-test` fences to `{{#include}}` extracts using anchor markers. Verified end-to-end: `cargo build --release -p perry-runtime -p perry-stdlib -p perry-ui-macos -p perry` clean; `./perry compile --no-link` on the example produces an .o with all 7 `_perry_ui_camera_*` symbols as undefined references (codegen wired); full link produces a 0.9 MB macOS executable that exits 0 in `PERRY_UI_TEST_MODE=1`; `./target/release/doc-tests --filter camera` passes (1/1, 1080 ms compile-only); full doc-tests sweep `./target/release/doc-tests --skip-xcompile` is 79/81 — same baseline as v0.5.325 (the lone fail remains the pre-existing `ui/gallery.ts` retina screenshot drift, the lone skip remains `platforms/wasm_snippets.ts` which opts out of the macOS host run phase). Real-camera wiring on macOS / Linux / Windows / Web (AVFoundation, GStreamer/V4L2, Media Foundation, `getUserMedia`) tracked as separate follow-ups — the codegen surface is now stable and contributors can light up each backend incrementally without touching the dispatcher. Version slot: bumped above origin's v0.5.326 (slider FFI calling-convention fix, parallel-track on origin) per the merge precedent for collisions.
152153
- **v0.5.326** — Fix latent calling-convention mismatch on `perry_ui_slider_create` across all 7 backends. Surfaced as `GLib-GObject-CRITICAL: value "nan" of type 'gdouble' is invalid or out of range for property 'value'` on Linux GTK4 when running `docs/examples/ui/styling/visual_test.ts` (section 10). Root cause: codegen at `crates/perry-codegen/src/lower_call.rs:5094` declares Slider with 3 args (`UiArgKind::F64, F64, Closure`) per the TS surface `Slider(min, max, onChange)` in `types/perry/ui/index.d.ts:224`, but every native backend's FFI shim declared 4 (`min, max, initial, on_change`) and called `widgets::slider::create(min, max, initial, on_change)`. Verified by `PERRY_NO_CACHE=1 PERRY_SAVE_LL=...` dump: emitted IR is `declare i64 @perry_ui_slider_create(double, double, double)` + `call i64 @perry_ui_slider_create(double 0.0, double 100.0, double %r480_callback)`. The 4th `initial` parameter at the FFI boundary therefore reads whatever the SysV ABI's xmm3 holds at the call site (uninitialized → frequently NaN). On macOS / iOS / Android / Windows that NaN flowed into `slider.setDoubleValue(NaN)` → silently clamped to min by AppKit/UIKit/Material/Win32 — no symptom. On GTK4 it flowed into `Adjustment::new(value=NaN, lower=0, upper=100, ...)` → GTK validated the range and emitted the loud warning. Fix: 5 native FFI shims (`crates/perry-ui-{gtk4,macos,ios,android,windows}/src/lib.rs`) drop the `initial` parameter and pass `min` to the internal `widgets::slider::create(min, max, min, on_change)`. Internal `widgets/slider.rs::create` keeps its 4-arg form for any future caller that wants explicit-initial semantics. Web (`crates/perry-codegen-js/src/web_runtime.js:275`) and wasm (`crates/perry-codegen-wasm/src/wasm_runtime.js:2579`) `perry_ui_slider_create` JS functions also drop their `initial` param — pre-fix Web set `el.value = undefined` (browser silently coerced to empty string then probably to min), wasm set `el.value = initial || 0` (worked coincidentally for `[0..N]` ranges, broke silently for `[10..100]`). Default-to-`min` matches the most common pattern (slider starts at the low end of its range) and the cross-backend behavior the existing native impls were already implicitly providing. Verified end-to-end on the visual_test repro: pre-fix `PERRY_UI_TEST_MODE=1 ./visual_test` printed the GLib-GObject-CRITICAL once before exit-0; post-fix the binary exits silent. Full Linux ui/ doc-tests: 44/45 pass / 0 fail / 1 skip (the lone skip is the pre-existing `image_symbol.ts` Linux banner exclusion). No regressions on the 12 other styling examples. Cross-backend FFI signatures now agree with codegen — closes the latent UB across all 7 platforms in one change. Version slot: bumped above origin's v0.5.325 (#219 doc-tests banner fix, parallel-track on origin) per the merge precedent for collisions.
153154
- **v0.5.325** — Closes #219: `docs/examples/platforms/wasm_snippets.ts` was failing every host doc-tests gate (macOS, GTK4, Windows) because its `// platforms: macos, linux, windows` banner enabled the host run phase, but the file's `declare function bloom_init_window/bloom_draw_rect` FFIs only resolve as WASM imports under `--target wasm` / `--target web` — on a native compile the host linker can't find them and bombs with `undefined reference to bloom_init_window`. Two-part fix: (1) `crates/perry-doc-tests/src/main.rs::read_banner` now tracks a `platforms_seen` flag — an explicit empty `// platforms:` directive (no values) suppresses the default-fill of `[macos, linux, windows]`, letting an example opt out of the host run phase entirely. Pre-fix the parser couldn't distinguish "directive absent" from "directive present but empty". (2) `docs/examples/platforms/wasm_snippets.ts` banner switched from `// platforms: macos, linux, windows` to `// platforms:` + `// targets: wasm, web` — the cross-compile phase still drives both targets (catching API drift in `declare function`, `fetch()`'s options shape, and `parallelMap`), but the host phase skips. Verified end-to-end: `./target/release/doc-tests --filter wasm_snippets --verbose` now reports `SKIP platform 'macos' not listed in banner` + `XCOMPILE_PASS target='wasm'` (504 ms) + `XCOMPILE_PASS target='web'` (101 ms); `strings target/perry-doc-tests/platforms_wasm_snippets__wasm.wasm` confirms `bloom_init_window` and `bloom_draw_rect` are present as WASM imports (per the issue's spot-check criterion). Full sweep `./target/release/doc-tests --skip-xcompile` now 78/80 pass with the lone fail being the pre-existing `ui/gallery.ts` retina screenshot baseline mismatch — unchanged from prior baselines, no regression from the parser change.
154155
- **v0.5.324** — Issue #185 follow-up: `box-shadow` on macOS (sections 4 of `docs/examples/ui/styling/visual_test.ts`) was invisible despite the FFI args being correct in the IR dump. Root cause: `crates/perry-ui-macos/src/widgets/mod.rs::set_shadow` set `shadowColor` / `shadowOpacity` / `shadowRadius` / `shadowOffset` on the view's CALayer but never set `setMasksToBounds: false` — and CALayer shadows are clipped by `masksToBounds`. The iOS / tvOS / visionOS mirror impls all set this; the macOS one was missing it. One-line fix: `let _: () = objc2::msg_send![layer, setMasksToBounds: false];` after the four shadow setters. Also adds (1) `docs/examples/ui/styling/visual_test.ts` — single-window comprehensive 13-section visual styling test exercising every styling prop family (colors hex/named/object/alpha/runtime, borders, padding, **shadow** (the case that surfaced the bug), gradient, opacity, typography, buttons, inputs, controls, image symbols, hidden/opacity-0 states, runtime-resolved colors via Phase C step 7); (2) `docs/examples/ui/styling/visual_test.spec.md` — LLM-debuggable per-cell expected-values manifest with 13 numbered sections matching the .ts and a "Visible signature" string per cell so a screenshot can be compared cell-by-cell against the spec; (3) `scripts/run_visual_test_check.sh` — drift check that asserts the .ts's `labeled("N. ...", ...)` calls and the spec's `### N.` headers stay 1:1 in lockstep, fails CI when either file adds/removes a row without updating the other; (4) wired into `.github/workflows/test.yml` after the existing UI styling matrix step. Verified end-to-end via 3 rounds of `shadow_repro.ts` (high-contrast yellow shadow against gray cells) — round 1 was inconclusive low-contrast, round 2 confirmed shadows missing, round 3 (post-fix) confirmed shadows render correctly. Cross-platform compile sanity check: web/wasm + iOS-simulator both produce valid binaries from the same `visual_test.ts` source; tvOS/watchOS sim builds need nightly Rust + `-Zbuild-std` (tier-3 targets), deferred. iOS simulator launch crash observed by user — investigation deferred so Windows/Linux validation can run; the macOS fix here is independently shippable and only touches the macOS UI crate.

Cargo.lock

Lines changed: 27 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ opt-level = "s" # Optimize for size in stdlib
109109
opt-level = 3
110110

111111
[workspace.package]
112-
version = "0.5.326"
112+
version = "0.5.327"
113113
edition = "2021"
114114
license = "MIT"
115115
repository = "https://github.com/PerryTS/perry"

crates/perry-codegen-js/src/emit.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3062,6 +3062,17 @@ impl JsEmitter {
30623062
"ProgressView" | "progressview_create" => "perry_ui_progressview_create",
30633063
"Image" | "image_create" => "perry_ui_image_create",
30643064
"Picker" | "picker_create" => "perry_ui_picker_create",
3065+
// Camera (issue #191) — browser stubs in wasm_runtime.js return
3066+
// 0 / -1 since there's no DOM equivalent for live capture; the
3067+
// dispatch entries here exist so the JS-target compile resolves
3068+
// the names rather than emitting `perry_ui_unknown`.
3069+
"CameraView" | "camera_create" => "perry_ui_camera_create",
3070+
"cameraStart" => "perry_ui_camera_start",
3071+
"cameraStop" => "perry_ui_camera_stop",
3072+
"cameraFreeze" => "perry_ui_camera_freeze",
3073+
"cameraUnfreeze" => "perry_ui_camera_unfreeze",
3074+
"cameraSampleColor" => "perry_ui_camera_sample_color",
3075+
"cameraSetOnTap" => "perry_ui_camera_set_on_tap",
30653076
"Form" | "form_create" => "perry_ui_form_create",
30663077
"Section" | "section_create" => "perry_ui_section_create",
30673078
"NavigationStack" | "navigationstack_create" => "perry_ui_navigationstack_create",

crates/perry-codegen-wasm/src/emit.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,17 @@ fn map_ui_method(method: &str, class_name: Option<&str>) -> &'static str {
439439
"pickerAddItem" => "perry_ui_picker_add_item",
440440
"pickerSetSelected" => "perry_ui_picker_set_selected",
441441
"pickerGetSelected" => "perry_ui_picker_get_selected",
442+
// Camera (issue #191) — Web has no live-camera FFI yet, so the
443+
// wasm_runtime.js stubs return 0 / -1. The dispatch entries here
444+
// exist so user code calling `CameraView()` from a browser build
445+
// resolves rather than throwing "perry_ui_unknown".
446+
"CameraView" | "camera_create" => "perry_ui_camera_create",
447+
"cameraStart" => "perry_ui_camera_start",
448+
"cameraStop" => "perry_ui_camera_stop",
449+
"cameraFreeze" => "perry_ui_camera_freeze",
450+
"cameraUnfreeze" => "perry_ui_camera_unfreeze",
451+
"cameraSampleColor" => "perry_ui_camera_sample_color",
452+
"cameraSetOnTap" => "perry_ui_camera_set_on_tap",
442453
// Image
443454
"imageSetSize" => "perry_ui_image_set_size",
444455
"imageSetTint" => "perry_ui_image_set_tint",

crates/perry-codegen-wasm/src/wasm_runtime.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3185,6 +3185,34 @@ function perry_system_notification_send(title, body) {
31853185
function perry_ui_frame_split_create() { return perry_ui_hstack_create(0); }
31863186
function perry_ui_frame_split_add_child(splitH, childH) { perry_ui_widget_add_child(splitH, childH); }
31873187

3188+
// ---------- Camera (issue #191) ----------
3189+
// Browser stubs. The Web target has no `getUserMedia`-backed live preview
3190+
// integrated yet, so these return sentinel values matching the documented
3191+
// contract: `cameraSampleColor` returns `-1` when no frame is available, the
3192+
// other setters are no-ops. User code calling `CameraView()` from a browser
3193+
// build resolves the symbol cleanly; on iOS/Android the same source compiles
3194+
// to the real AVCaptureSession / Camera2 backend.
3195+
function perry_ui_camera_create() {
3196+
// Render a placeholder div so layout slots reserve space the same way
3197+
// they would for a real preview. No camera permission is requested.
3198+
const el = document.createElement("div");
3199+
el.style.background = "#000";
3200+
el.style.color = "#888";
3201+
el.style.display = "flex";
3202+
el.style.alignItems = "center";
3203+
el.style.justifyContent = "center";
3204+
el.style.minWidth = "200px";
3205+
el.style.minHeight = "200px";
3206+
el.textContent = "[camera preview not supported on web]";
3207+
return uiAlloc(el);
3208+
}
3209+
function perry_ui_camera_start(_h) {}
3210+
function perry_ui_camera_stop(_h) {}
3211+
function perry_ui_camera_freeze(_h) {}
3212+
function perry_ui_camera_unfreeze(_h) {}
3213+
function perry_ui_camera_sample_color(_x, _y) { return -1; }
3214+
function perry_ui_camera_set_on_tap(_h, _cb) {}
3215+
31883216
// ---------- UI Dispatch table (maps bridge function names to implementations) ----------
31893217
const __perryUiDispatch = {
31903218
// Widget creation
@@ -3273,6 +3301,10 @@ const __perryUiDispatch = {
32733301
perry_system_keychain_delete, perry_system_notification_send,
32743302
// Frame split
32753303
perry_ui_frame_split_create, perry_ui_frame_split_add_child,
3304+
// Camera (issue #191) — browser stubs
3305+
perry_ui_camera_create, perry_ui_camera_start, perry_ui_camera_stop,
3306+
perry_ui_camera_freeze, perry_ui_camera_unfreeze,
3307+
perry_ui_camera_sample_color, perry_ui_camera_set_on_tap,
32763308
};
32773309

32783310
// Also expose as __perryUi for JS async function context

0 commit comments

Comments
 (0)