Skip to content

t27c: host fn declarations, so gen-js/gen-ts can emit a browser host (Canvas2D, rAF, matchMedia) with no hand-written JS #10295

Open

Description

@gHashTag

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:

  1. Host imports (this issue). The language has no declaration for a function the host
    environment provides. Today extern exists only as a reserved word of the target languages.
    A canvas host needs about a dozen calls:
    • Canvas2D: beginPath, arc, moveTo, lineTo, fill, stroke, fillStyle as an hsl
      string, globalAlpha, shadowBlur, globalCompositeOperation, createLinearGradient
    • requestAnimationFrame
    • matchMedia("(prefers-reduced-motion: reduce)")
    • setting a DOM text node, for the aria-live mirror
    • key events
      No .t27 construct can name or call any of these.
  2. 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 .t27 spec that declares host fns for Canvas2D, requestAnimationFrame and matchMedia, when t27c gen-js compiles 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-js and t27c gen-ts MUST lower calls to declared host fns into calls on the host object, emitting the binding table from the spec.
  • FR-003: t27c gen-zig MUST 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.t27 exits 0 and writes host_canvas.js.
  • 0 hand-written JS files in the particle_field widget directory once a host is generated.
  • cargo test -p t27c exits 0 with at least 3 new tests: a lowered host call, an undeclared host fn refused, and a wasm import emitted.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions