Skip to content
weftGitHub

Tooling

AGENTS.md

<!-- Generated by weft agents-md from weft@0.1.0. Do not edit: change weft.config.ts and regenerate. -->

This is a weft app. It is not React: components run once, state is a box, and React hooks don't exist. Run weft check --json after every change; each diagnostic gives code, location, why and fix.

Start with .weft/map.md (weft map): what this app has and what to call.

Rules

tsx
import { derived } from '@weft-js/core'export function Cart({ items, taxRate }: { items: { id: string; name: string; price: number }[]; taxRate: number }) {  const subtotal = derived(() => items.reduce((s, i) => s + i.price, 0))  const total = derived(() => subtotal.value * (1 + taxRate))  return (    <section>      <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>      <p>{total.value.toFixed(2)}</p>    </section>  )}
  1. State is a box. Read and write .value. Everywhere, JSX included (W015).
  2. Anything computed from other values is derived. Not in a watch or the component body (W001).
  3. JSX is always live. Every {expr} and attribute re-evaluates when what it reads changes.
  4. Props are always current. Destructure them (one level, W006). Read them in derived, watch, JSX or handlers. const x = props.y is a live read-only alias. let x = props.y and props.y = write locally; only bind:y={box} writes out.
  5. The component body runs once. No early returns on state (W040), no let counters (W041). Branch in JSX.
  6. Pass a box only to give write access (value={coupon}, child prop typed State<string | null>), or so list rows can read one entry: cart.get(p.id) re-runs one row, cart[p.id] or cart.value[p.id] re-runs them all (W017), in the list and in the row component alike.
  7. Lists and conditionals the way you know them: .map with key (W010), &&, ternaries. Keep list items stable: replace only the item that changed (p.id === id ? { ...p, price } : p), never ({ ...p }) every item (W014).
  8. Copy a prop into state with state.linked(() => prop), not state(prop) (W005). untrack(() => …) is a snapshot that never updates: not for anything you render (W018).
  9. Side effects go in watch(() => source, value => cleanup?); DOM work in onMount. flush() applies pending DOM updates now.
  10. Async data is resource(signal => fetch(…)) (.value .pending .error .refresh()), never derived(async …) (W012). Loading and errors: <Boundary pending={…} error={(e, retry) => …}>. More: weft docs async.

Leave props untyped: weft docs props.

API in weft@0.1.0

  • @weft-js/core: Boundary, batch, context, defineState, derived, flush, onCleanup, onMount, resource, state, untrack, watch, state.linked
  • @weft-js/core/dom: render, hydrate.
  • @weft-js/core/ssr: renderToString, renderToStringAsync. Never import the rest.
  • @weft-js/core/browser: online, visible, lifecycle, media, on, interval. Create them in setup (W051); server-rendered media needs ssr (W054). More: weft docs browser; hover and press as state: weft docs ui-state.
  • @weft-js/core/surface: canvas and WebGL, never raw <canvas>: weft docs surface.
  • @weft-js/core/test: assertNoLeaks, clock, flush, memoryStorage, net, page, render, scope, screen, tabs, touches, user, and expect(fn).toTouchAtMost(n, { runs }). Query by role/label/text; user actions and clock.advance end in flush(), so never sleep or waitFor.

Diagnostics

weft docs <code> explains one. Also flagged: raw addEventListener, setInterval or sockets with no cleanup (W050), and a watch that folds a stream (W052) or copies a live value into state (W056).

Test checks (weft check on test files): T001–T009, T013. No assertion, tautologies, sleeps, container/selectors/test ids, mocking weft, snapshots, conditional expect, .only/.skip. To allow one: // weft-allow T00x: <reason>.

Before a change is done, see it work (weft browser snapshot|act|assert, or __weft__.agent in your own browser) and run weft test --gate: new tests must fail on the old code and kill a mutant (T010, T011, T020). weft build ends by auditing the pages it built at a phone's and a desktop's width (L-codes: names, contrast, console, the head, sideways scroll, layout shift) and fails on an error; weft audit --url <deploy> checks a live site. More: weft docs verify.

Full stack

@weft-js/start app: file routes in src/routes (_layout.tsx, [id].tsx, load, meta), server functions in src/server/** or *.server.ts. Read weft docs start first. Import your own code as #/server/todos (src/server/todos); generated files are weft:slot/<id> and weft:tokens/<name>.

  • Reads: query({ tags: ['todos'], auth: 'user', run: ({ ctx }) => ctx.db.select().from(todos) }). In a component todos().value ?? []; in load and handlers await todos.fetch().
  • Writes: mutation({ input, auth: 'user', invalidates: ['todos'], optimistic, run }).
  • Database: Drizzle; await every statement. Schema change: weft db generate (Q015). Inputs: schemas.todos.insert.pick({ title: true }).
  • Auth: ctx.user is the user or null; auth: 'user' refuses anyone else (A002). A guard is redirect('/login') in a layout's load. run must still check who owns a row.
  • Never copy query results into state, import db from a component (Q012), fetch /_weft/*, or poll (todos.live(); rooms: channel).
  • Every page sets meta as you build it, from data and params: weft docs meta.

Styling

Run weft style find <what it is> first, then reuse a group or add an axis value; never class lists. New: const card = group('card', { base: { pad: 'group' }, axes: { status: { late: { tone: 'critical' } } } }) from @weft-js/core/style, <li {...card({ status })}>. Tokens only. D001–D007, D011, D020–D025, D030–D041, W060: weft docs style, weft docs design; weft fix applies the safe fixes.

Commands

CommandDoesSide effects
weft check --jsonCompile every component in check mode and report W- and P-diagnostics with fixesnone
weft docs <topic>Print a docs topic or a diagnostic code (weft docs W014)none
weft mapWrite .weft/map.md: a matrix of what to call in this appfs:.weft/map.*
weft addInstall a plugin and wire it into the config (--dry-run shows the changes)fs:weft.config.*, fs:package.json, network:install
weft devDev servernetwork:listen
weft buildProduction build and contractfs:.output/**
weft dbgenerate: new migrationsfs:drizzle/, fs:migrations/, fs:prisma/**
weft testThe test gate on the diff (--gate)fs:.weft/gate
weft fixApply the safe autofixes from weft checkfs:scope.write
weft browserDrive the running app in any open browser by role/label/text (weft browser help)network:dev-server, fs:.weft/browser
weft runRun a project task by nametask
weft run contentGenerate docs, search indexes, llms.txt and numbers from the repofs

weft tasks --json lists the rest.

Plugins

Fonts (@weft-js/text)

  • Declare fonts once in text({ fonts }) in weft.config.ts: { body: { family, src, fallback } }. Never hand-write @font-face (X020).
  • The build subsets each face by unicode-range, adds a metric-matched "<Family> Fallback" face and preloads body and display. @weft-js/core/vite loads them: no import. weft docs fonts.
  • A build trims faces on its own: custom axes the source never varies are pinned, and uncommon latin characters load on demand. An axis set at runtime needs declaring in axes; trim: false turns it all off.
  • text.measure is lazy: it costs nothing until something measures, a canvas s.text(), measure(), lines() or fit() from @weft-js/core/text, or a warp list given text (measure: 'off' removes it).
  • Font stacks name the fallback second: font-family: "Inter", "Inter Fallback", sans-serif. font-display is swap or optional, never block (X021).

warp (UI kit)

Import components from @weft-js/warp; each brings its own CSS (the spec's, split per component). Layout: Stack Row Grid Split Center Cover Frame Spacer Inset (gap: hair tight related group section page; as: the element). Text: Heading (level, role), Text (role, tone, subtle). No raw values (D001–D007). Intent props only: tone (neutral accent positive caution critical info), emphasis (solid soft outline ghost frame link), size (sm md lg). No style props: the look comes from the dials, data-expression="quiet|balanced|vivid", data-theme, data-density, data-motion, data-flair on any element (scopes nest; setDial() from @weft-js/warp-core writes them). Fields take label (required), hint, error, native value/onInput; variant="classic" puts the label above. Modal/Drawer take open as a box. App-wide look: warp({ seed: { accent: '#0a7d4f' }, dials, radius, fonts, tokens }); weft check fails with D010 if any pair can't pass.

Icons

Import icons from @weft-js/warp-icons: CheckIcon is hollow, the default, CheckIconSolid the other form (<Icon name="check" solid />); <Icons.Check /> and warp's <Icon name="check" /> build to the same bytes. Tiers are a prop, tier, or the page's data-expression. Decorative by default; label gives one a name. An unknown literal name is I001 at the build and in weft check. A name only known at runtime is legal and loads the pack lazily (I002 reports it), or single files with icons({ source }) and weft icons publish <dir>. trash, bell, check, refresh and copy animate with action. weft docs icons has the rest.

More

weft docs <topic> or weft mcp: rules, props, lists, async, testing, browser, verify, start, style, surface, config, diagnostics, commands.