Skip to content

Commit e329a64

Browse files
committed
fix: perry init type stubs + UI docs examples (v0.5.109) (#103)
State is now generic (State<T = number>) so State<string[]>([]) / State("") type-check. Added ForEach to the stub; stateBindTextfield takes State<string>. Rewrote the UI docs examples (first-app, state, widgets, dialogs) to use the real runtime signatures — the old TextField(state, placeholder) form segfaulted because UiArgKind::Str routes the first arg through get_raw_string_ptr.
1 parent cab6743 commit e329a64

8 files changed

Lines changed: 119 additions & 97 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.108
11+
**Current Version:** 0.5.109
1212

1313
## TypeScript Parity Status
1414

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

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

153+
- **v0.5.109** — Fix `perry init` TypeScript type stubs and the UI docs that exercised them (closes #103). `types/perry/ui/index.d.ts`: `State` is now generic (`State<T = number>`) so `State<string[]>([])` and `State("")` type-check instead of erroring on the old number-only signature; added `ForEach(count: State<number>, render)` export (was used in the todo-app docs example but missing from the stub); `stateBindTextfield` now takes `State<string>`. Rewrote the docs examples in `docs/src/getting-started/first-app.md` (counter + todo), `docs/src/ui/state.md` (two-way binding section, onChange snippet, complete example), `docs/src/ui/widgets.md` (TextField/SecureField/Toggle/Slider/Picker/Form), and `docs/src/ui/dialogs.md` to use the actual runtime signatures — `TextField(placeholder, onChange)`, `Slider(min, max, onChange)`, `Picker(onChange)` + `pickerAddItem`, etc. — instead of the fictional `TextField(state, placeholder)` / `count.onChange(...)` forms. The old forms silently segfaulted at launch because `UiArgKind::Str` at `lower_call.rs:2557` routes the first arg through `get_raw_string_ptr`, and a State handle (i64) reinterpreted as a NaN-boxed string derefs garbage. No runtime/codegen change — the stubs are embedded via `include_str!` at compile time and ship to users via `perry init` / `perry types`. Verified: 5 new example binaries (counter, todo, controls, form, onchange) all `tsc --noEmit` clean AND compile + launch without crashing.
153154
- **v0.5.108** — Honor `PERRY_RUNTIME_DIR` / `PERRY_LIB_DIR` env vars in `find_library` so out-of-tree installs (perry binary in `/usr/local/bin`, source tree elsewhere) can point at an explicit lib dir. The "Could not find libperry_runtime.a" error now lists every candidate path it searched and names the env var as a fix. Closes #101.
154155
- **v0.5.107** — First end-to-end release with npm distribution live. `@perryts/perry` + seven per-platform optional-dep packages (`@perryts/perry-{darwin-arm64,darwin-x64,linux-x64,linux-arm64,linux-x64-musl,linux-arm64-musl,win32-x64}`) publish via OIDC Trusted Publisher from `release-packages.yml` on each GitHub Release. `npx @perryts/perry compile file.ts` works on all seven platforms. No runtime/codegen change.
155156
- **v0.5.106** — Swap `lettre`'s `tokio1-native-tls` feature for `tokio1-rustls-tls` in `crates/perry-stdlib/Cargo.toml`. Eliminates `openssl-sys` / `native-tls` from the transitive dep tree (they were the only holdouts; the policy comment at Cargo.toml:35 already states "rustls only to avoid OpenSSL"). Unblocks the musl CI build — `openssl-sys` was failing with "Could not find openssl via pkg-config: cross-compilation unsupported" on `x86_64-unknown-linux-musl`. No functional change for SMTP clients; rustls provides the same TLS surface.

Cargo.lock

Lines changed: 24 additions & 24 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
@@ -102,7 +102,7 @@ opt-level = "s" # Optimize for size in stdlib
102102
opt-level = 3
103103

104104
[workspace.package]
105-
version = "0.5.108"
105+
version = "0.5.109"
106106
edition = "2021"
107107
license = "MIT"
108108
repository = "https://github.com/PerryTS/perry"

docs/src/getting-started/first-app.md

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@ import { App, Text, Button, VStack, State } from "perry/ui";
1111

1212
const count = State(0);
1313

14-
App("My Counter", () =>
15-
VStack([
16-
Text(`Count: ${count.get()}`),
17-
Button("Increment", () => {
18-
count.set(count.get() + 1);
19-
}),
20-
Button("Reset", () => {
21-
count.set(0);
22-
}),
23-
])
24-
);
14+
App({
15+
title: "My Counter",
16+
width: 400,
17+
height: 300,
18+
body: VStack(16, [
19+
Text(`Count: ${count.value}`),
20+
Button("Increment", () => count.set(count.value + 1)),
21+
Button("Reset", () => count.set(0)),
22+
]),
23+
});
2524
```
2625

2726
Compile and run:
@@ -35,43 +34,51 @@ A native window opens with a label and two buttons. Clicking "Increment" updates
3534

3635
## How It Works
3736

38-
- **`App(title, renderFn)`** — Creates a native application window. The render function defines the UI.
39-
- **`State(initialValue)`** — Creates reactive state. When you call `.set()`, the UI re-renders.
40-
- **`VStack([...])`** — Vertical stack layout (like SwiftUI's VStack or CSS flexbox column).
41-
- **`Text(string)`** — A text label. Template literals with `${state.get()}` update reactively.
37+
- **`App({ title, width, height, body })`** — Creates a native application window. `body` is the root widget.
38+
- **`State(initialValue)`** — Creates reactive state. `.value` reads, `.set(v)` writes and triggers UI updates.
39+
- **`VStack(spacing, [...])`** — Vertical stack layout (like SwiftUI's VStack or CSS flexbox column). Spacing arg is optional.
40+
- **`Text(string)`** — A text label. Template literals referencing `${state.value}` bind reactively.
4241
- **`Button(label, onClick)`** — A native button with a click handler.
4342

4443
## A Todo App
4544

4645
```typescript
47-
import { App, Text, Button, TextField, VStack, HStack, State, ForEach } from "perry/ui";
46+
import {
47+
App, Text, Button, TextField, VStack, HStack, State, ForEach, Spacer,
48+
} from "perry/ui";
4849

4950
const todos = State<string[]>([]);
51+
const count = State(0); // ForEach iterates by index, so we keep a count in sync
5052
const input = State("");
5153

52-
App("Todo App", () =>
53-
VStack([
54-
HStack([
55-
TextField(input, "Add a todo..."),
54+
App({
55+
title: "Todo App",
56+
width: 480,
57+
height: 600,
58+
body: VStack(16, [
59+
HStack(8, [
60+
TextField("Add a todo...", (value: string) => input.set(value)),
5661
Button("Add", () => {
57-
const text = input.get();
62+
const text = input.value;
5863
if (text.length > 0) {
59-
todos.set([...todos.get(), text]);
64+
todos.set([...todos.value, text]);
65+
count.set(count.value + 1);
6066
input.set("");
6167
}
6268
}),
6369
]),
64-
ForEach(todos, (todo, index) =>
65-
HStack([
66-
Text(todo),
70+
ForEach(count, (i: number) =>
71+
HStack(8, [
72+
Text(todos.value[i]),
73+
Spacer(),
6774
Button("Remove", () => {
68-
const items = todos.get();
69-
todos.set(items.filter((_, i) => i !== index));
75+
todos.set(todos.value.filter((_, idx) => idx !== i));
76+
count.set(count.value - 1);
7077
}),
7178
])
7279
),
73-
])
74-
);
80+
]),
81+
});
7582
```
7683

7784
## Cross-Platform

docs/src/ui/dialogs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ App({
111111
}),
112112
]),
113113
Text(`File: ${filePath.value || "No file open"}`),
114-
TextField(content, "Start typing..."),
114+
TextField("Start typing...", (value: string) => content.set(value)),
115115
]),
116116
});
117117
```

0 commit comments

Comments
 (0)