# Tu — Agent Skill > **Audience**: LLM agents (Claude, GPT, etc.) that need to write or reason about Tu source code. This page is written for ingestion by automated tools — copy-paste it into a system prompt, save it as `.claude/skills/tu/SKILL.md`, or `fetch` it programmatically. A plain-text version is mirrored at [`/llms.txt`](./llms.txt). ## Identity (the one-liner you need to keep in mind) Tu is a **reactive UI language** that compiles to JS/TS. It is **JS-superset-with-types-via-TS** in spirit — most JS expression-level constructs work, types come from TypeScript (Volar pattern), reactivity comes from TC39 Signals. The grammar **converges JS, never collides with active TC39 proposals**. The compiler maps Tu source to TS shadow files; tsserver does the type checking. The runtime is a tiny Signal + DOM glue layer. There is no virtual machine and no custom runtime — it's vanilla JS at runtime, with `Signal.State` / `Signal.Computed` cells as the only library-level primitive. Mental model order when reading Tu: 1. **Top-level `let`** — module-private binding. Auto-binds to a `Signal.State` cell unless the value is a `() => …` lambda (then it's a plain const) or `computed(...)` (then `Signal.Computed`). 2. **Lambdas are components** — capitalized lambda → component callable as `Foo()` or `Foo() { children }`. Lowercase → HTML tag-call (in markup position) or plain function (in expression position). 3. **`{ … }` after a callee = children block** — the trailing-closure DSL. Each child is whitespace-separated; no `;` or `,` between children. 4. **Markup, props, and style live in one syntax**, top-to-bottom. ## File anatomy ```tu // 1. Imports. Sources end in .tu (cross-Tu) or are bare (npm packages). import { Fragment } from "@tu-lang/runtime" import { Card } from "./Card.tu" // 2. Type aliases (TS-style; raw RHS preserved to TS shadow). type Point = { x: number; y: number } // 3. Module-private cell (top-level `let` → Signal.State). let count = 0 // 4. Public cell (export → consumers can import the cell). export let origin: Point = { x: 0, y: 0 } // 5. Computed cell (re-derives when its read-cells mutate). export let doubled = computed(count * 2) // 6. Component (capitalized lambda; not wrapped in a Signal cell). export let App = (children: Child[]) => .panel() { h1 { "count = " count " (doubled = " doubled ")" } button(onClick: () => count = count + 1) { "+1" } children style { .panel { font-family: system-ui, sans-serif; padding: 1rem; } .panel > h1 { color: #312e81; } } } ``` ## Bindings ### `let X = value` Module-private. - **Value is a primitive / object literal / array literal / `[…]` / call result** → `let X = …` compiles to `const X = new Signal.State(…)`. - **Value is `(args) => body`** → plain `const X = (args) => body`. Not wrapped. - **Value is `computed(expr)`** → `const X = new Signal.Computed(() => expr)`. ```tu let count = 0 // Signal.State let make = (n) => n * 2 // plain function, no cell let doubled = computed(count * 2) // Signal.Computed ``` ### `export let X = value` Public — appears in the module's named exports. Same wrapping rules as bare `let`. ### Annotated bindings ```tu let count: number = 0 // Signal.State let names: string[] = [] // Signal.State let snap: Point = { x: 0, y: 0 } // Signal.State let cell: Signal.State = … // user opts out of double-wrap; the // compiler honors a pre-wrapped Signal.* type ``` The annotation is a raw source slice (depth-tracked across `()`, `{}`, `[]`, `<…>`). The TS-mode emit threads it through verbatim; JS-mode strips it. ### Local `let` (inside a block) ```tu let App = () => { let greeting = "Hello, " + name + "!" p { greeting } } ``` A local `let` is a **plain const**, not a Signal cell. It exists for closures, derived values, and small locals. Block bodies with one or more `let`s compile to an IIFE. ## Type aliases ```tu type Point = { x: number; y: number } type RGB = readonly [number, number, number] export type AppProps = { children: Child[] } ``` `type` is a contextual keyword (only triggers when followed by `Ident =` at statement boundary). The RHS is captured verbatim and emitted into the TS shadow. JS mode erases the entire alias. ## Values ### Literals ```tu "a string" // StringLit (escapes: \n \t \r \" \\) 42 // NumberLit (integers only at lexer level; decimals work via JS) [1, 2, 3] // ArrayLit [] // empty ArrayLit (Signal.State auto-widened) { x: 1, y: 2 } // ObjectLit { "data-id": 7 } // ObjectLit with string key {} // EMPTY OBJECT — not an empty block ``` `{` is disambiguated against the block form by lookahead: `{ }`, `{ Ident :`, or `{ String :` triggers an ObjectLit. Anything else (`{ x }`, `{ let y = 1; y }`, `{ tag(...) }`) stays a Block. **Not yet supported** (don't emit these — see the [Deferred backlog](./DEFERRED)): - Object shorthand: `{ x }` — write `{ x: x }` - Computed keys: `{ [k]: v }` - Spread: `{ ...rest }`, `[...arr]` ### Identifiers + member access ```tu count // bare ident — reads the binding origin.x // member access (postfix) make(n).field // member access on a call result nested.outer.inner // chained ``` Member access **only** works on value-yielding expressions — `Ident`, plain `CallExpr` (no children block), existing `MemberExpr`, `ObjectLit`, `ArrayLit`. It does **not** work after a `TagCall` / `IfExpr` / `Block` / lambda body. This rule prevents `div { x }\n.body() { y }` from re-parsing as `(div{x}).body(){y}`. ### Lambdas ```tu (x) => x + 1 (x: number) => x + 1 (name: string, age: number) => p { name } () => p { "hi" } (x: number): string => "ok" // return-type annotation (): Map => empty // generics + nested types OK ``` Param types and return types are raw slices — preserved in TS mode, erased in JS mode. The body is any expression (including a Block, IfExpr, ForExpr, TagCall, ObjectLit, …). ### Calls ```tu foo(arg, another) // CallExpr — positional args make({ x: 1 }) // arg can be any expression ``` Identifiers followed by `(` and positional args (no `Ident:` immediately inside) parse as call expressions. The result is whatever the function returns. ### Blocks ```tu { someStmt anotherStmt finalExpr // value of the block } ``` Each item is parsed as an expression (or a `LocalLet`). The last non-LocalLet expression is the block's value. Multi-statement blocks compile to an IIFE; single-statement blocks compile to `(stmt)`. **Note: `{}` is an empty object literal, not an empty block** — write `{ undefined }` if you want a block that evaluates to `undefined`. ## Markup (tag-calls) Trailing-closure DSL. **Capitalization is the discriminator** (mirrors React/JSX): - **Lowercase identifier** → `h("tag", props, children)` — an HTML element. - **Uppercase identifier** → `Callee(args, [children])` — a real component function call. ### Bare tag with children ```tu div { "Hello" } → h("div", {}, ["Hello"]) h1 { "title" p { "body" } } → h("h1", {}, ["title", h("p", {}, ["body"])]) ``` Children are **whitespace-separated**, NOT comma-separated. Newlines / spaces between them are insignificant. ### Tag with named props ```tu div(class: "card", id: "main") { … } button(onClick: () => count = count + 1) { "+1" } input(type: "text", value: name) ``` Props are `name: value` pairs separated by `,`. Values can be any expression — strings, idents (cell reads inject `.get()`), lambdas, ObjectLits, ClassRefs, etc. ### Component invocation ```tu Card("title") → Card("title") Card("title") { p { "body" } } → Card("title", [h("p", {}, ["body"])]) Card { p { "no args, just kids" } } → Card([h("p", {}, ["no args, just kids"])]) ``` Components are real functions. tsserver sees them as such — hover, goto-definition, and rename all work cross-`.tu`. The trailing children block becomes the **last positional argument**, conventionally typed as `(children: Child[])`. ### Fragment (multi-root return) ```tu import { Fragment } from "@tu-lang/runtime" let App = () => Fragment { header { … } main { … } footer { … } } ``` `Fragment` is a built-in helper that takes the children array and returns it as-is, letting a component return multiple sibling vnodes without an enclosing wrapper. ### Pug-style class shorthand ```tu .card // ClassRef (used as a value, e.g. class: .card) .card.elevated // multi-class binding .card() { "x" } → div(class: "card …") { "x" } .card.elevated() { "x" } → div(class: "card elevated …") { "x" } .card(tag: "section") { "x" } // override default tag with a string literal ``` Pug-shorthand desugars to a `div` (or the `tag:` override) with the listed classes injected. An explicit `class:` prop in shorthand-position is a parse error — the shorthand already binds class. ### Children types A child can be: TagCall, CallExpr, BinaryExpr, StringLit, NumberLit, Ident, IfExpr, ForExpr, StyleBlock, ClassRef, ArrayLit, ObjectLit, MemberExpr. A child **cannot** be: Lambda, Block, AssignExpr (these throw at parse time). ## Control flow ### `if` / `else` ```tu if (count > 0) { p { "positive: " count } } else if (count == 0) { p { "zero" } } else { p { "negative" } } ``` The condition is parenthesized; both branches are blocks. Else-if chains are supported as nested IfExpr. `if` is an expression — its value is the chosen branch's block-value. ### `for` ```tu for item in items { li { item } } ``` Compiles roughly to `Array.from(items, (item) => …)`. The iterable's tail `{ … }` is the loop body, **not** a tag-call on the iterable (the parser suppresses brace-block parsing inside the iter expression for exactly this reason). ## Reactivity - Top-level `let X = …` (non-lambda, non-`computed(…)`) → `Signal.State`. Reads of `X` inside any expression context emit as `X.get()`. Assignments `X = expr` desugar to `X.set(expr)`. - `let X = computed(expr)` → `Signal.Computed` whose body re-runs whenever any cell read inside `expr` mutates. - **Local `let`** (inside a block) is a plain const; reads/writes pass through unchanged. - **Lambda params** are plain idents (no `.get()` injection). - `mount(thunk, container)` re-runs `thunk` whenever any cell it reads mutates. - `computed(...)` cells lazily re-evaluate on read after invalidation. The `.get()` injection rule: a bare ident emits as `name.get()` if and only if `name` resolves to a top-level state or computed cell **and** is not shadowed by a local `let`, lambda param, or `for` binder. ## Style block ```tu let Card = (title: string) => .card() { h1(class: .card__title) { title } p { "body" } style { .card { padding: 1rem; border-radius: 8px; } .card__title { font-size: 1.25rem; } :global(.legacy) { color: gray; } // unscoped escape hatch } } ``` - `style { … }` is a special form (no parens). The body is raw CSS, preserved verbatim in the StyleBlock AST and emitted as a `