Summary
The single deferred follow-up from #679 (closed). User-component JSX (<App />) works as of v0.5.810 — js_jsx recognises closure pointers and invokes them. But built-in widget intrinsics in JSX form fall through:
```tsx
import { Box, Text, render } from "perry/tui";
// Works today (function-call form):
render(Box({ flexDirection: "row" }, [Text("hello")]));
// Doesn't work (JSX intrinsic form):
render(hello);
// → js_jsx(Box, props) → TAG_UNDEFINED
```
The JSX form lowers to jsx(Box, props) / jsxs(Box, props), but js_jsx (in crates/perry-runtime/src/jsx.rs) doesn't recognise the Box / Text function-pointer constants as widget builders — only closure pointers (which is how user components dispatch). Result: TAG_UNDEFINED.
This is a blocker for true ink source-compat — every real ink program uses JSX intrinsic syntax, and AI tools generating perry/tui code will default to it.
Repro
`test-files/test_issue_679_perry_tui_jsx_audit.tsx` already documents the limitation:
```tsx
// 3. Built-in widget intrinsics: <Box flexDirection="row">...</Box> and
// <Text color="red">...</Text> lower to jsx(Box, props) and today
// fall through to TAG_UNDEFINED. The function-call form
// Box({ flexDirection: "row" }, [Text("…", { fg: "red" })]) still works.
```
Fix shape (from #679's closing comment)
Compile-time recogniser in crates/perry-codegen/src/lower_call/native.rs that pattern-matches:
```
Call { callee: ExternFuncRef("jsx" | "jsxs"), args: [ExternFuncRef("Box" | "Text" | "Spacer" | ...), props, ...children] }
```
and rewrites in-place to a direct Box(opts, children) / Text(content, opts) call. This sidesteps the runtime entirely — by the time js_jsx would run, codegen has already lowered the call to the same shape the function-call form produces.
Open question: should the rewriter cover all perry/tui intrinsics (Box, Text, Spacer, Input, TextArea, List, Select, Spinner, ProgressBar, Table, Tabs) or just Box/Text in v1? Argument for all: consistency, no surprise "this one works in JSX, that one doesn't." Argument for Box/Text only: covers 90%+ of real ink code; the rest can be added incrementally.
Acceptance
- The JSX form in the summary above compiles and renders the same as the function-call form.
- Add JSX variants of the existing
test_perry_tui_inkcompat_*.ts programs (or convert them outright) and prove they still pass.
- Update
test_issue_679_perry_tui_jsx_audit.tsx to assert success on the previously-deferred cases.
Related
Summary
The single deferred follow-up from #679 (closed). User-component JSX (
<App />) works as of v0.5.810 —js_jsxrecognises closure pointers and invokes them. But built-in widget intrinsics in JSX form fall through:```tsx
import { Box, Text, render } from "perry/tui";
// Works today (function-call form):
render(Box({ flexDirection: "row" }, [Text("hello")]));
// Doesn't work (JSX intrinsic form):
render(hello);
// → js_jsx(Box, props) → TAG_UNDEFINED
```
The JSX form lowers to
jsx(Box, props)/jsxs(Box, props), butjs_jsx(incrates/perry-runtime/src/jsx.rs) doesn't recognise theBox/Textfunction-pointer constants as widget builders — only closure pointers (which is how user components dispatch). Result: TAG_UNDEFINED.This is a blocker for true ink source-compat — every real ink program uses JSX intrinsic syntax, and AI tools generating perry/tui code will default to it.
Repro
`test-files/test_issue_679_perry_tui_jsx_audit.tsx` already documents the limitation:
```tsx
// 3. Built-in widget intrinsics:
<Box flexDirection="row">...</Box>and//
<Text color="red">...</Text>lower tojsx(Box, props)and today// fall through to TAG_UNDEFINED. The function-call form
//
Box({ flexDirection: "row" }, [Text("…", { fg: "red" })])still works.```
Fix shape (from #679's closing comment)
Compile-time recogniser in
crates/perry-codegen/src/lower_call/native.rsthat pattern-matches:```
Call { callee: ExternFuncRef("jsx" | "jsxs"), args: [ExternFuncRef("Box" | "Text" | "Spacer" | ...), props, ...children] }
```
and rewrites in-place to a direct
Box(opts, children)/Text(content, opts)call. This sidesteps the runtime entirely — by the timejs_jsxwould run, codegen has already lowered the call to the same shape the function-call form produces.Open question: should the rewriter cover all perry/tui intrinsics (Box, Text, Spacer, Input, TextArea, List, Select, Spinner, ProgressBar, Table, Tabs) or just Box/Text in v1? Argument for all: consistency, no surprise "this one works in JSX, that one doesn't." Argument for Box/Text only: covers 90%+ of real ink code; the rest can be added incrementally.
Acceptance
test_perry_tui_inkcompat_*.tsprograms (or convert them outright) and prove they still pass.test_issue_679_perry_tui_jsx_audit.tsxto assert success on the previously-deferred cases.Related
ink(React-based TUI framework) end-to-end viaperry.compilePackages#348 (deprioritized) — ink-via-compilePackages. Not a blocker; perry/tui is the recommended path regardless.