Skip to content

perry/tui: JSX intrinsic rewriter for built-in <Box>/<Text> #689

@proggeramlug

Description

@proggeramlug

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions