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
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> )}- State is a box. Read and write
.value. Everywhere, JSX included (W015). - Anything computed from other values is
derived. Not in awatchor the component body (W001). - JSX is always live. Every
{expr}and attribute re-evaluates when what it reads changes. - Props are always current. Destructure them (one level, W006). Read them in
derived,watch, JSX or handlers.const x = props.yis a live read-only alias.let x = props.yandprops.y =write locally; onlybind:y={box}writes out. - The component body runs once. No early returns on state (W040), no
letcounters (W041). Branch in JSX. - Pass a box only to give write access (
value={coupon}, child prop typedState<string | null>), or so list rows can read one entry:cart.get(p.id)re-runs one row,cart[p.id]orcart.value[p.id]re-runs them all (W017), in the list and in the row component alike. - Lists and conditionals the way you know them:
.mapwithkey(W010),&&, ternaries. Keep list items stable: replace only the item that changed (p.id === id ? { ...p, price } : p), never({ ...p })every item (W014). - Copy a prop into state with
state.linked(() => prop), notstate(prop)(W005).untrack(() => …)is a snapshot that never updates: not for anything you render (W018). - Side effects go in
watch(() => source, value => cleanup?); DOM work inonMount.flush()applies pending DOM updates now. - Async data is
resource(signal => fetch(…))(.value .pending .error .refresh()), neverderived(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-renderedmedianeedsssr(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, andexpect(fn).toTouchAtMost(n, { runs }). Query by role/label/text;useractions andclock.advanceend inflush(), so never sleep orwaitFor.
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 componenttodos().value ?? []; inloadand handlersawait todos.fetch(). - Writes:
mutation({ input, auth: 'user', invalidates: ['todos'], optimistic, run }). - Database: Drizzle;
awaitevery statement. Schema change:weft db generate(Q015). Inputs:schemas.todos.insert.pick({ title: true }). - Auth:
ctx.useris the user ornull;auth: 'user'refuses anyone else (A002). A guard isredirect('/login')in a layout'sload.runmust still check who owns a row. - Never copy query results into
state, importdbfrom a component (Q012),fetch/_weft/*, or poll (todos.live(); rooms:channel). - Every page sets
metaas you build it, fromdataandparams: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
| Command | Does | Side effects |
|---|---|---|
weft check --json | Compile every component in check mode and report W- and P-diagnostics with fixes | none |
weft docs <topic> | Print a docs topic or a diagnostic code (weft docs W014) | none |
weft map | Write .weft/map.md: a matrix of what to call in this app | fs:.weft/map.* |
weft add | Install a plugin and wire it into the config (--dry-run shows the changes) | fs:weft.config.*, fs:package.json, network:install |
weft dev | Dev server | network:listen |
weft build | Production build and contract | fs:.output/** |
weft db | generate: new migrations | fs:drizzle/, fs:migrations/, fs:prisma/** |
weft test | The test gate on the diff (--gate) | fs:.weft/gate |
weft fix | Apply the safe autofixes from weft check | fs:scope.write |
weft browser | Drive the running app in any open browser by role/label/text (weft browser help) | network:dev-server, fs:.weft/browser |
weft run | Run a project task by name | task |
weft run content | Generate docs, search indexes, llms.txt and numbers from the repo | fs |
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 preloadsbodyanddisplay.@weft-js/core/viteloads 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: falseturns it all off. text.measureis lazy: it costs nothing until something measures, a canvass.text(),measure(),lines()orfit()from @weft-js/core/text, or a warp list giventext(measure: 'off'removes it).- Font stacks name the fallback second:
font-family: "Inter", "Inter Fallback", sans-serif.font-displayisswaporoptional, neverblock(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.