Repository navigation
t27c: host fn declarations, so gen-js/gen-ts can emit a browser host (Canvas2D, rAF, matchMedia) with no hand-written JS #10295
Description
A .t27 module cannot call the browser, so no widget host can be written in t27 today.
The particle_field widget (specs/t27f/particle_field.t27, merged in #10290) has to become a live
page on t27.ai. Under the only-t27 rule the host has to be .t27 compiled by t27c gen-js or
gen-ts. Two constructs are missing:
- Host imports (this issue). The language has no declaration for a function the host
environment provides. Todayexternexists only as a reserved word of the target languages.
A canvas host needs about a dozen calls:- Canvas2D:
beginPath,arc,moveTo,lineTo,fill,stroke,fillStyleas an hsl
string,globalAlpha,shadowBlur,globalCompositeOperation,createLinearGradient requestAnimationFramematchMedia("(prefers-reduced-motion: reduce)")- setting a DOM text node, for the aria-live mirror
- key events
No.t27construct can name or call any of these.
- Canvas2D:
- Function bodies with floats. gen-js and gen-ts emit no fn bodies today ("this backend
lowers declarations, not bodies"). The pure-body lowering in gen-ts: lower the bodies of pure functions #7872 refuses floats, and every
coordinate in particle_field is f64. That part is tracked there and is a dependency here, not
the scope of this issue.
Today every one of trinity's 139 widgets carries a hand-written tool.js. Repeated on
2026-10-18, the owner's rule forbids adding another, even a 40-line loader. So until this gap is
closed, particle_field has no page. Related: the T27F Live design,
docs/nona-02-organism/t27f/genui-research.md section 2.2, asks for exactly this: a host-interface
spec that lists the imports, plus a t27c backend that emits the shim. Self-host epic #5980.
Proposed shape, for discussion. A host block in a .t27 spec declares typed signatures with
no body, for example host fn canvas_arc(x: f64, y: f64, r: f64) -> void. gen-js lowers each
signature to a call on an object passed in at instantiation. The binding table, which maps each
host fn to ctx.arc and so on, is itself .t27 data. The backend emits it, so no glue is
written by hand. gen-zig lowers the same declarations to wasm imports, so the Zig-to-wasm path
used by 999 scripts/t27-logic.mjs gets them too.
Boundary
- bootstrap/src/compiler.rs
- bootstrap/src/codegen_js.rs
- bootstrap/src/codegen_ts.rs
- specs/t27f/host_canvas.t27
User Scenarios
- Given a
.t27spec that declares host fns for Canvas2D, requestAnimationFrame and matchMedia, whent27c gen-jscompiles it, then the output is an ES module that calls those browser APIs, with no hand-written file beside it. - Given a spec that calls an undeclared host fn, when t27c checks it, then compilation fails with an error naming the fn.
Requirements
- FR-001: The language MUST have a declaration for a host-provided function with typed parameters and result and no body.
- FR-002:
t27c gen-jsandt27c gen-tsMUST lower calls to declared host fns into calls on the host object, emitting the binding table from the spec. - FR-003:
t27c gen-zigMUST lower the same declarations to wasm imports. - FR-004: A call to an undeclared host fn MUST be a compile error, never a silent drop.
- FR-005: specs/t27f/host_canvas.t27 MUST declare the Canvas2D, requestAnimationFrame, matchMedia, key-event and DOM-text subset listed above, with tests.
Success Criteria
t27c gen-js specs/t27f/host_canvas.t27exits 0 and writeshost_canvas.js.- 0 hand-written JS files in the particle_field widget directory once a host is generated.
cargo test -p t27cexits 0 with at least 3 new tests: a lowered host call, an undeclared host fn refused, and a wasm import emitted.
Activity