Skip to content

Latest commit

 

History

History
2754 lines (2234 loc) · 84.8 KB

40-diagnostics.md

File metadata and controls

2754 lines (2234 loc) · 84.8 KB

40. Diagnostics

wf build, wf check, wf serve and your editor report the same findings, from one pipeline. Each starts with its code and what is wrong, then the place in the file:line:col form editors and CI read, the line of source with the part that is wrong underlined, what to do about it, and a link to its entry here:

error[T05]: `User` has no field `nmae`
 --> src/pages/Profile.wf:3:18
  |
3 |     Heading(user.nmae).h1
  |                  ^^^^
  = help: Its fields are `id`, `name`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t05-a-field-or-method-that-does-not-exist
  • Errors stop the build: the program cannot mean what it says.
  • Warnings print and the build goes on: it means something, probably not what you wanted. Nothing is a warning that the compiler could have decided for itself.

Every stage runs whatever the one before it found, and every file is read even when another does not parse, so one build shows every mistake once — then one line sums them up: error: the build stopped: 2 errors, 5 warnings. A build that stops on its findings exits with 1; one that could not run at all — a file it could not read, a config it could not load — with 2.

wf check --format json, sarif or github writes the same findings for a tool (Command line), and wf explain T05 prints a code's entry below in the terminal.

Fixes. Where the compiler knows the fix, it says so as edits, which your editor offers as a quick fix, --format json carries, and wf fix applies. A fix is offered only where it is certain; where it is not, the entry below says what to do instead. Each family has its decided fix:

  • The nearest name, wherever the code names a set: a field, member, method, variable, component, case or flag (T02, T05, T06, T13, T16, E101, E103), a prop (C02, C04), a route (R01, which also writes a relative link from the root), a store (X05), a locale (I04), a file of public/ (D01), a CSS property or token (V05, V06), an icon (V08) and an event (V09).
  • What is missing, written in: ?. for a read that may be null (T04), (await …) for a promise read as its value (T17), the missing props (C01), the missing arms of a match (T15) and an error arm (T21), a Router in an app with none (R03), bind: for a box that shows a state and never changes it (F04), px for a bare number where CSS wants a length (V07), controls: true (A09), and the migrate step a version is missing (P01) — with the version raised in the same change when a persisted shape changed (D04).
  • A name, drafted from the code: alt: "" for an image (A01); a label from what a control binds or an icon is (A02–A04), a link's from where it leads (A06), a dialog's title from what opens it (A08); the level that mends the outline (A11); the visible text at the start of an aria-label (A15). With nothing to draft from, there is no fix — an empty name would silence the warning and name nothing.
  • What is not used, removed — a state, derived value, action, store member or component (U01–U05), code after a return (U06), an allow that allows nothing (U07) — with rename to _name as the second fix, for what is kept on purpose; a loop keyed by item.id when its body reads the item's id (U09).
  • derived for a state that copies a live value nothing changes (X06), a positional argument named for the one prop it can be (C05), and the prop marked _ in the component's declaration (C06).

A family's findings in one file are fixed at once as one change: the editor's Fix all V07 in this file, or wf fix --code V07.

Allowing one finding. A // wf-allow(CODE) comment accepts a finding on purpose: on a line of its own it covers the next line of code, at the end of a line that line. It names codes or families, comma-separated — // wf-allow(U01, A). Only what lints could lower may be allowed, and an allow that covers nothing is itself reported (U07), so allows do not outlive what they were for. lints says the same for a whole project.

The code — T05 — is the thing to search this page for. The families:

E syntax and structure: what cannot be read, or refers to nothing
T types
C components and their props
R routes and navigation
X state and reactivity
I translations
F forms and bindings
D data, assets and what is kept in the browser
A accessibility
S search and sharing
P persisted values
U declared and never used
V vocabulary

Every example below is compiled by the test suite, draws exactly the code it is filed under, and shows what the compiler prints for it.

Syntax and structure

Errors, except where an entry says otherwise.

E001 — Text the compiler cannot read

page P(path: "/", title: "T", description: "D") {
    Heading("Hello).h1
}
error[E001]: Unterminated string literal
 --> src/App.wf:2:13
  |
2 |     Heading("Hello).h1
  |             ^
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e001-text-the-compiler-cannot-read

Fix: Close the string, comment or splice the message names. A character that begins nothing (| alone, a stray @) is usually a typo for the one beside it on the keyboard.

E002 — A syntax error

page P(path: "/", title: "T", description: "D") {
    Heading("Hello").h1
    Text("a" Text("b")
}
error[E002]: Expected `,` between arguments, got `Text`
 --> src/App.wf:3:14
  |
3 |     Text("a" Text("b")
  |              ^^^^
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e002-a-syntax-error

Fix: The message names what was expected and what was found, at the place the parser stopped. One file can report several: after an error the parser picks up again at the next declaration.

E003 — Indentation that lines up with no block

In a .wfx file, a line that comes back out to an indentation no enclosing block has:

page Home(path: "/")
    Row
        Text("a")
      Text("b")

Fix: Line the line up with the block it belongs to.

E004 — A file in the WebFluent 2 grammar

Page Home (path: "/") {
    Text("Hi")
}
error[E004]: `Page` is a WebFluent 2 declaration; this is WebFluent 3
 --> src/App.wf:1:1
  |
1 | Page Home (path: "/") {
  | ^^^^
  = help: Run `wf migrate` to convert the project to the current grammar
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e004-a-file-in-the-webfluent-2-grammar

Fix: Run wf migrate once; it rewrites the project in place.

E005 — A file the formatter will not change

wf fmt checks that its result has the same tokens as the file. When it would not — something only the formatter's reading of the file would change — the file is left as it is and named. Fix: format the lines it names by hand; please report the file, since it is a bug in wf fmt.

E101 — A component nothing declares

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Buton("Save")
}
error[E101]: unknown component `Buton`: no `component Buton` is declared
 --> src/App.wf:3:5
  |
3 |     Buton("Save")
  |     ^^^^^
  = help: did you mean `Button`? Or declare `component Buton { … }`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e101-a-component-nothing-declares

Fix: Check the spelling, or declare it with component Buton { … }.

E102 — A name declared twice

page P(path: "/", title: "T", description: "D") {
    state count = 0
    derived count = 1
    Heading("Count {count}").h1
}
error[E102]: `count` is declared twice: as a state at line 2, and as a `derived` here
 --> src/App.wf:3:5
  |
3 |     derived count = 1
  |     ^^^^^^^
  = help: A name means one thing — rename one of the two `count`s
  = note: `count` is first declared here at src/App.wf:2:5
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e102-a-name-declared-twice

Fix: Rename one of the two. The finding points at the second and names where the first is.

E103 — A flag, case, part, event or slot the component does not have

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Button("Save").huge
}
error[E103]: Button has no flag or enum case `huge`
 --> src/App.wf:3:19
  |
3 |     Button("Save").huge
  |                   ^^^^^
  = help: Its flags are .bounce, .button, .collapse, .danger, .disabled, .dropInside, .expand, .fadeIn, .fadeOut, .fast, .full, .info, .lg, .md, .normal, .outlined, .pill, .primary, .pulse, .reset, .rounded, .scaleIn, .scaleOut, .secondary, .shake, .slideDown, .slideLeft, .slideRight, .slideUp, .slow, .sm, .spin, .submit, .success, .trapFocus, .warning
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e103-a-flag-case-part-event-or-slot-the-component-does-not-have

Fix: Use one the message lists — it names every flag, case, event or slot the component takes.

E104 — A layout with nowhere to put the page

component Shell { Text("frame") }
page P(path: "/", title: "T", description: "D", layout: Shell) {
    Heading("Home").h1
}
error[E104]: `Shell` declares no default slot, so the page has nowhere to go
 --> src/App.wf:2:57
  |
2 | page P(path: "/", title: "T", description: "D", layout: Shell) {
  |                                                         ^^^^^
  = help: Add `slot` to the component and place `children` where the page belongs
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e104-a-layout-with-nowhere-to-put-the-page

Fix: Add slot to the component and place children where the page belongs.

E105 — A statement a render block cannot hold

page P(path: "/", title: "T", description: "D") {
    state n = 0
    Heading("Count").h1
    n = n + 1
}
error[E105]: `n` is not an element or a statement a render block can hold
 --> src/App.wf:4:5
  |
4 |     n = n + 1
  |     ^
  = help: Code that does something goes in `on click { … }`, an `action` or an `effect`; an element's name is capitalised
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e105-a-statement-a-render-block-cannot-hold

Fix: Put it in on click { }, an action or an effect.

E106 — Script in an attribute

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Button("Save", onclick: "save()")
}
error[E106]: `onclick` on Button would put script in an attribute
 --> src/App.wf:3:20
  |
3 |     Button("Save", onclick: "save()")
  |                    ^^^^^^^
  = help: Write the handler instead: `Button(…) { on click { … } }`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e106-script-in-an-attribute

Fix: Button("Save") { on click { save() } }.

E107 — A URL a browser would run

page P(path: "/", title: "T", description: "D") {
    Heading("Go").h1
    Link("Go", to: "javascript:alert(1)")
}
error[E107]: `to` on Link names the `javascript` scheme, which a browser runs
 --> src/App.wf:3:16
  |
3 |     Link("Go", to: "javascript:alert(1)")
  |                ^^
  = help: A URL here may be relative, or name http, https, mailto, tel, sms or ftp
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e107-a-url-a-browser-would-run

Fix: A relative URL, or http, https, mailto, tel, sms, ftp.

E108 — An env name that is not public

env.STRIPE_SECRET read in a page: env.STRIPE_SECRET is not public, and a page reads what is in the bundle. Fix: rename it PUBLIC_… or list it in public_env — only if anyone may read it (chapter 30).

E109 — An element this output cannot draw

A Button, an Input, a handler or a Router in a project whose output_type is pdf or slides. Fix: draw it as text, or build the page as a site.

E110 — A project script that cannot be a plain script

A .js file under src/ with an import or export, one a file in public/ at the same address would replace, or one declaring a name the program, the language or the browser already has. Fix: take off the import/export (a top-level function is global as it is), keep one of the two files, or rename the clashing name.

E111 — A setting that cannot work

A meta.scripts module with no as, a build.elements naming a component that does not exist, an offline.fallback no page has, an openapi.json that is not there. Fix: what the message says; it names the setting.

E112 — A setting nothing reads

Warning. A key in webfluent.app.json that is not a setting, with the nearest one that is: `build.minfy` is not a setting, and nothing reads it — did you mean `minify`? Fix: correct the key.

E113 — An argument that cannot be positional

component Card(_ name: String, price: Number = 0) { Text(name) }
page P(path: "/", title: "T", description: "D") {
    Heading("Shop").h1
    Card("Laptop", 999)
}
error[E113]: Only the first argument may be positional
 --> src/App.wf:4:20
  |
4 |     Card("Laptop", 999)
  |                    ^^^
  = help: Name the others: `Button("Save", tone: .primary)`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e113-an-argument-that-cannot-be-positional

Fix: Name the others: Card("Laptop", price: 999).

E114 — emit outside a component

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Button("Save") { on click { emit saved() } }
}
error[E114]: `emit` fires a component's event; a page has none
 --> src/App.wf:3:33
  |
3 |     Button("Save") { on click { emit saved() } }
  |                                 ^^^^
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e114-emit-outside-a-component

Fix: Fire the event from the component that declares it; a page calls an action instead.

E115 — A project script the compiler could not read

Warning. A .js file under src/ the compiler's scanner could not read — a string that never closes, say. The file is still linked, but its names are not in scope for the program. Fix: what the message says, at the line it names.

E116 — A tag that cannot be a custom element's

page P(path: "/", title: "T", description: "D") {
    Heading("Pricing").h1
    Element("pricingtable")
}
error[E116]: `pricingtable` is not a custom element's tag
 --> src/App.wf:3:5
  |
3 |     Element("pricingtable")
  |     ^^^^^^^
  = help: A custom element's tag is lower case with a hyphen: `Element("stripe-pricing-table", …)`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e116-a-tag-that-cannot-be-a-custom-element-s

Fix: A custom element's tag is lower case with a hyphen: Element("pricing-table").

E901 — The compiler wrote JavaScript a browser would refuse

The build reads back every script it writes; this one would not run. It is a bug in WebFluent, not in your program. Fix: please report it, with the source that produced it.

E902 — Output its own security policy refuses

The build holds every page it writes to the content security policy it ships beside it; something on this page — an inline script, a style=, a script from an origin the policy never named — would be blocked. A page's own head { script(src: …) } or head { link(rel: "stylesheet", href: …) } is held to it too, where it is written: the tag is added on the live page, and the browser would refuse it there without a word. Fix: what the message says; a library's origin goes in meta.scripts, a stylesheet's in meta.stylesheets.

E117 — A key combination no keyboard sends

page P(path: "/", title: "T", description: "D") {
    on key("ctrl+shfit+k") { log("search") }
    Heading("Search").h1
}
error[E117]: `on key("ctrl+shfit+k")` names `shfit`, which is not a modifier
 --> src/App.wf:2:12
  |
2 |     on key("ctrl+shfit+k") { log("search") }
  |            ^
  = help: Modifiers `mod` (⌘ on a Mac, Ctrl elsewhere), `ctrl`, `shift`, `alt`, `meta` (`cmd`), then one key: a letter, a digit, or a name like `Enter`, `Escape`, `ArrowDown`, `Tab`, `F2`; a chord is its steps with a space between, `mod+k mod+s`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e117-a-key-combination-no-keyboard-sends

Fix: Modifiers ctrl, shift, alt, meta (or cmd), then one key: a letter, a digit, or a name — Enter, Escape, ArrowDown, Tab, F2.

E118 — A name the language already has

page Deploy(path: "/", title: "Deploy", description: "Pick a target.") {
    state env = "staging"
    Heading("Deploy to {env}").h1
}
error[E118]: `env` is a name the language already has: the build's settings, read as `env.PUBLIC_API`
 --> src/App.wf:2:5
  |
2 |     state env = "staging"
  |     ^^^^^^^^^^^^^^^^^^^^^
  = help: Name it something of your own — `target` or `stage`: inside its scope every `env` would mean this, and the language's would have no name
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#e118-a-name-the-language-already-has

A name the language gives a meaning is not a program's to declare — not as a state, derived, persist or let, a prop, a route or action parameter, a loop item, a lambda's parameter, a binding of if let, match or a slot, nor a top-level const, data, image, store or api. Declared, it would hide what the language means by it for as far as its scope reaches: a state env hid the build's settings in its page, and a loop's t hid the translation function inside the loop. The names are:

  • env, the build's settings; params, the route's; locale and dir, the reader's language;
  • the values the browser keeps current: now, viewport, query, hash, theme, network, update;
  • the language's functions: t, format, ago, log, navigate, fetch, uuid, sanitize, setLocale, setTheme, optimistic, beacon, animate, replayAnimation, every, after, ws, sse, broadcast, rtc;
  • the words a statement is written with: state, persist, derived, effect, action, use, resource, validate, socket, stream, channel, peer, head, slot, part, if, else, for, in, by, show, match, children, emit, sequence, on, style, transition, let, return, await, try, catch;
  • at the top level, WF, the runtime the compiled pages call;
  • in a store, the names every JavaScript object has — valueOf, toString, constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, __proto__ and its __define…/__lookup… kin: a store is an object whose members are its own, and a valueOf member replaced the one JavaScript calls itself, so the page threw as soon as it was drawn;
  • and, as a prop, __proto__, which in the object a call passes sets its prototype instead (a prop named toString or valueOf is fine).

The names a handler brings with it — event, e, value, key — and the browser's globals (window, history, …) are a program's to use, and so is any name as a field, a map key or an enum case: type Box { env: String } and .env are fine. Fix: Name it something of your own — state target = "staging" — and its reads with it. The message suggests one where there is a usual choice: target or stage for env, search for query, scheme for theme, item or a word for what it holds for t.

Components

C01 — A required prop or field left out

type Todo { id: String, title: String, done: Bool = false }
page P(path: "/", title: "T", description: "D") {
    state todo = Todo(title: "Write the docs")
    Heading(todo.title).h1
}
error[C01]: `Todo(…)` leaves out `id` of `Todo`, which has no default
 --> src/App.wf:3:18
  |
3 |     state todo = Todo(title: "Write the docs")
  |                  ^^^^
  = help: Give it, or declare a default in the type: `field: Type = value`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#c01-a-required-prop-or-field-left-out

Fix: Give it, or declare a default in the type (id: String = "") or on the prop.

A built-in that cannot be placed without a prop is held to it the same way: a Portal with no into: would draw nowhere, and the fix passes into: null for you to name the node.

C02 — A prop the component does not declare

component Badge2(_ label: String) { Text(label) }
page P(path: "/", title: "T", description: "D") {
    Heading("Badges").h1
    Badge2("New", colour: "red")
}
error[C02]: `Badge2` declares no prop `colour`
 --> src/App.wf:4:19
  |
4 |     Badge2("New", colour: "red")
  |                   ^^^^^^
  = help: It is passed anyway, but nothing in the component reads it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#c02-a-prop-the-component-does-not-declare

Fix: Correct the spelling, or declare the prop on the component.

C03 — A part outside the component it belongs to

page P(path: "/", title: "T", description: "D") {
    Heading("Plans").h1
    Select.Option("Pro", value: "pro")
}
error[C03]: `Select.Option` is a part of `Select`, and it is not inside one
 --> src/App.wf:3:5
  |
3 |     Select.Option("Pro", value: "pro")
  |     ^^^^^^^^^^^^^
  = help: Place it in a `Select { … }`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#c03-a-part-outside-the-component-it-belongs-to

Fix: Place the part inside its owner: Select(bind: plan) { Select.Option(…) }.

C04 — An attribute a built-in does not declare

Warning.

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Button("Save", colour: "red")
}
warning[C04]: Button has no prop `colour`; it is written to the element as an attribute
 --> src/App.wf:3:20
  |
3 |     Button("Save", colour: "red")
  |                    ^^^^^^
  = help: A typo here does nothing on screen; check the component's props
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#c04-an-attribute-a-built-in-does-not-declare

Fix: Check the prop's spelling — wf registry lists what the built-in takes. aria-*, data-* and the global attributes (id, role, title, …) are expected and draw nothing.

C05 — A positional argument a built-in does not take

Warning.

page P(path: "/", title: "T", description: "D") {
    Heading("Rule").h1
    Divider("thin")
}
warning[C05]: Divider takes no positional argument
 --> src/App.wf:3:13
  |
3 |     Divider("thin")
  |             ^
  = help: Name it: the registry lists the props it takes
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#c05-a-positional-argument-a-built-in-does-not-take

Fix: Name it, with one of the props the built-in takes.

C06 — A positional argument bound by order

Warning.

component Tag2(label: String) { Text(label) }
page P(path: "/", title: "T", description: "D") {
    Heading("Tags").h1
    Tag2("new")
}
warning[C06]: `Tag2` declares no positional prop; the argument binds to `label`
 --> src/App.wf:4:10
  |
4 |     Tag2("new")
  |          ^
  = help: Mark the prop: `component Tag2(_ label: …)`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#c06-a-positional-argument-bound-by-order

Fix: Mark the prop the call means: component Tag2(_ label: String).

C07 — A drag or a gesture on a component with no one element

component Rows2(_ name: String) {
    for x in [1, 2] { Text(name) }
}
page P(path: "/", title: "T", description: "D") {
    Heading("Files").h1
    Rows2("a", drag: "a")
}
error[C07]: `drag:` on `Rows2` has no element to land on: its body is a `for`
 --> src/App.wf:6:16
  |
6 |     Rows2("a", drag: "a")
  |                ^^^^
  = help: Wrap what `Rows2` draws in one element (a `Stack`, a `Row`), or write `drag:` inside it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#c07-a-drag-or-a-gesture-on-a-component-with-no-one-element

drag:, dragGroup:, dropValue:, dropAxis:, dropInside: and the handlers of the gestures — on receive(d), on dragmove(d), on longpress, on swipe — written on your component's call belong to the one element it draws. A body that is an if, a for, a show, a match or several elements has no one element to give them.

Fix: Wrap what the component draws in one element — a Stack, a Row — or write the drag inside it, on the element it is for.

State and reactivity

X01 — An assignment to something that cannot change

const LIMIT = 3
page P(path: "/", title: "T", description: "D") {
    Heading("Limit {LIMIT}").h1
    Button("More") { on click { LIMIT = 4 } }
}
error[X01]: `LIMIT` is a `const`, and nothing may assign to it
 --> src/App.wf:4:33
  |
4 |     Button("More") { on click { LIMIT = 4 } }
  |                                 ^^^^^
  = help: Declare it `state` if it changes; a constant is the same on every page
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#x01-an-assignment-to-something-that-cannot-change

Fix: Hold what changes in a state. A derived value changes when what it reads does; a prop is its caller's (emit an event so the caller changes it); a route parameter is the address's (navigate); a loop variable is one pass's copy (change the item through its list).

X02 — An effect that feeds itself

page P(path: "/", title: "T", description: "D") {
    state visits = 0
    effect { visits = visits + 1 }
    Heading("Visits {visits}").h1
}
error[X02]: this effect writes `visits`, which it also reads, so each run starts the next and the page never settles
 --> src/App.wf:3:5
  |
3 |     effect { visits = visits + 1 }
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = help: Work the value out with `derived` instead, or write it from the action that changes what it reads
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#x02-an-effect-that-feeds-itself

Fix: Work a value out with derived; change state from the action or handler that changes what it depends on.

X04 — A derived value that changes something

page P(path: "/", title: "T", description: "D") {
    state count = 0
    action next() {
        count = count + 1
        return count
    }
    derived ticket = next()
    Heading("Ticket {ticket}").h1
}
error[X04]: `ticket` is worked out by calling `next(…)`, which assigns `count`
 --> src/App.wf:7:5
  |
7 |     derived ticket = next()
  |     ^^^^^^^^^^^^^^^^^^^^^^^
  = help: A derived value only reads; call the action from a handler, and derive from what it sets
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#x04-a-derived-value-that-changes-something

Fix: Call the action from a handler; derive from the state it sets.

X05 — use of a store nothing declares

store Cart { state items = [] }
page P(path: "/", title: "T", description: "D") {
    use Carts
    Heading("Cart").h1
}
error[X05]: `use Carts` names a store nothing declares
 --> src/App.wf:3:5
  |
3 |     use Carts
  |     ^^^
  = help: The stores are `Cart`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#x05-use-of-a-store-nothing-declares

Fix: Name a store that exists — here Cart.

X06 — A state that copies a live value once

page P(path: "/", title: "T", description: "D") {
    state posted = @2026-03-14T09:30:00Z
    state clock = now(every: 1.seconds)
    derived age = posted.until(clock)
    Heading("Posted {age.minutes()} minutes ago").h1
}
warning[X06]: `clock` copies `now` once, when the page opens, and never follows it
 --> src/App.wf:3:5
  |
3 |     state clock = now(every: 1.seconds)
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = help: Nothing assigns `clock`: declare it `derived`, which is worked out again whenever `now` changes
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#x06-a-state-that-copies-a-live-value-once

Fix: Declare it derived — the quick fix does. derived clock = now(every: 1.seconds) is worked out again on every tick, and so is everything that reads it. now, viewport, query, hash, theme, network and update all keep themselves current; a state only takes the value it starts with. A state the program assigns, binds or changes in place is a starting value on purpose — state tab = query.tab ?? "all" that a click then changes — and draws nothing. Neither does a live value read inside a lambda, which reads it each time it is called.

Forms and bindings

F01 — A bind: to something that cannot be written

page P(path: "/", title: "T", description: "D") {
    state first = "Ada"
    derived name = first.toUpperCase()
    Heading("Name").h1
    Input(bind: name, label: "Name")
}
error[F01]: `bind:` on `Input` writes back to what it names, and `name` is a `derived` value
 --> src/App.wf:5:11
  |
5 |     Input(bind: name, label: "Name")
  |           ^^^^^^^^^^
  = help: Bind a `state` (or a store's state, or a field of a loop's item); show a value with `value:`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#f01-a-bind-to-something-that-cannot-be-written

Fix: Bind the state the reader edits (bind: first); show a value that is worked out with value:.

F02 — A bind: to a state the control cannot hold

page P(path: "/", title: "T", description: "D") {
    state qty = 1
    Heading("Order").h1
    Input(bind: qty, label: "Quantity")
}
error[F02]: `bind:` on `Input` holds a `String`, but `qty` is `Number`
 --> src/App.wf:4:11
  |
4 |     Input(bind: qty, label: "Quantity")
  |           ^^^^^^^^^
  = help: A number field is `Input(…).number`, which holds a number
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#f02-a-bind-to-a-state-the-control-cannot-hold

Fix: A number field is Input(…).number; a text field holds a String.

F03 — A validate block no control can satisfy

page P(path: "/", title: "T", description: "D") {
    state email = ""
    validate email { required  email }
    Heading("Join").h1
    Form { Button("Join", type: .submit) }
}
error[F03]: `validate email` guards a state no control binds, so its rules can never be met
 --> src/App.wf:3:5
  |
3 |     validate email { required  email }
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = help: Bind it to the control the reader fills in: `Input(bind: email, …)`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#f03-a-validate-block-no-control-can-satisfy

Fix: Bind the state to the control the reader fills in: Input(bind: email, label: "Email").email.

F04 — A checkbox that shows a state and never changes it

Warning.

page P(path: "/", title: "T", description: "D") {
    state agree = false
    Heading("Terms").h1
    Checkbox(checked: agree, label: "I agree")
}
warning[F04]: this `Checkbox` shows `checked:` but nothing changes it, so a click toggles the box and not the state
 --> src/App.wf:4:5
  |
4 |     Checkbox(checked: agree, label: "I agree")
  |     ^^^^^^^^
  = help: Write `bind:` to keep the box and the state together, or handle `on change`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#f04-a-checkbox-that-shows-a-state-and-never-changes-it

Fix: Checkbox(bind: agree, label: "I agree").

F05 — A select whose value is none of its options

Warning.

page P(path: "/", title: "T", description: "D") {
    state plan = "basic"
    Heading("Plan").h1
    Select(bind: plan, label: "Plan") {
        Select.Option("Free", value: "free")
        Select.Option("Pro", value: "pro")
    }
}
warning[F05]: `plan` starts as `basic`, which is none of this select's options
 --> src/App.wf:4:5
  |
4 |     Select(bind: plan, label: "Plan") {
  |     ^^^^^^
  = help: Start it as one of them (`free`, `pro`), so what is shown is what is held
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#f05-a-select-whose-value-is-none-of-its-options

Fix: Start the state as one of the options' values.

Routes

R01 — A route to nothing

page Home(path: "/", title: "T", description: "D") {
    Heading("Home").h1
    Link("About us", to: "/abuot")
}
page About(path: "/about", title: "About", description: "D") {
    Heading("About").h1
}
error[R01]: `/abuot` is not a route: no page's `path` matches it
 --> src/App.wf:3:5
  |
3 |     Link("About us", to: "/abuot")
  |     ^^^^
  = help: The nearest is `/about`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#r01-a-route-to-nothing

Fix: Link to a route a page has; the message names the nearest. A spliced link is checked by its shape — "/team/{t.slug}" must match a route like /team/:slug — a link with no leading / is refused (where it leads would depend on the page it is on), and a link to a file (/report.pdf) or another site is left alone.

R02 — A route parameter and a page parameter that do not match

page Profile(path: "/u/:id", title: "T", description: "D") {
    Heading("Profile").h1
}
error[R02]: the route `/u/:id` names `:id`, and the page declares no parameter `id`
 --> src/App.wf:1:1
  |
1 | page Profile(path: "/u/:id", title: "T", description: "D") {
  | ^^^^
  = help: Declare it: `page Profile(path: "/u/:id", id: String)`, then read `id`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#r02-a-route-parameter-and-a-page-parameter-that-do-not-match

Fix: Declare the parameter the route names — page Profile(path: "/u/:id", id: String) — and read it by name; a declared parameter the route has no :name for is never filled.

R03 — An app with no Router, or with two

app {
    Navbar { Navbar.Brand { Text("Ledger") } }
}
page Home(path: "/", title: "T", description: "D") {
    Heading("Home").h1
}
error[R03]: `app` places no `Router`, so no page is ever drawn
 --> src/App.wf:2:5
  |
2 |     Navbar { Navbar.Brand { Text("Ledger") } }
  |     ^^^^^^
  = help: Put `Router` where the current page belongs: `app { Navbar { … } Router }`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#r03-an-app-with-no-router-or-with-two

Fix: Place Router where the page belongs: app { Navbar { … } Router }. (A program with no app at all routes its pages by itself.)

R04 — A relative URL on a nested route

Warning.

page Post(path: "/blog/first", title: "T", description: "D") {
    Heading("First post").h1
    Image(src: "images/cover.png", alt: "")
}
warning[R04]: `src: "images/cover.png"` is relative, so on `/blog/first` it is fetched from `/blog/images/cover.png`
 --> src/App.wf:3:5
  |
3 |     Image(src: "images/cover.png", alt: "")
  |     ^^^^^
  = help: Write it from the site's root: `"/images/cover.png"`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#r04-a-relative-url-on-a-nested-route

Fix: Write it from the site's root: "/images/cover.png".

Data and assets

D01 — A file the project does not have

Warning. Image(src: "/img/hero.png") when public/img/hero.png does not exist (also poster:, captions:, transcript:, and a worker's spawn("/workers/css.js")): the page asks for it and gets a 404. Fix: put the file in public/, or correct the path.

D02 — A persisted value storage cannot keep

page P(path: "/", title: "T", description: "D") {
    persist pick = (x) => x + 1
    Heading("Pick").h1
}
error[D02]: `persist pick` keeps a function, which cannot be written to the browser's storage
 --> src/App.wf:2:5
  |
2 |     persist pick = (x) => x + 1
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = help: Keep what it is made from — a name, an id, the data — and build it again when it is read
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#d02-a-persisted-value-storage-cannot-keep

Fix: Keep what it is made from — a name, an id, the data — and build the rest when it is read.

D03 — A persisted value every instance shares

Warning.

component Panel(_ title: String) {
    persist open = false
    Card { Text(title) }
}
page P(path: "/", title: "T", description: "D") {
    Heading("Panels").h1
    Panel("One")
    Panel("Two")
}
warning[D03]: `persist open` is in `Panel`, which is placed more than once, so every one of them reads and writes one stored value
 --> src/App.wf:2:5
  |
2 |     persist open = false
  |     ^^^^^^^
  = help: Give each its own: `persist open = false { key: id }`, with `id` a prop that tells them apart
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#d03-a-persisted-value-every-instance-shares

Fix: Give each instance its own key: persist open = false { key: title }, with a prop that tells them apart.

D04 — A persisted shape changed with no new version

Warning. The build remembers what each persist held (in .wf-cache/persist.json); when that changes — persist items = [] becomes persist items = { open: [], done: [] } — and version: does not, a returning reader's browser hands the old shape to code written for the new one. Fix: raise version: and add migrate 1 -> 2 { … } to bring the old value forward.

D05 — An asset from another origin with no integrity hash

Warning. A font, stylesheet or script in meta.fonts, meta.stylesheets or meta.scripts from another origin, with no hash in meta.integrity. Fix: add its sha384-… hash to meta.integrity (the CDN usually publishes it). Google Fonts is exempt: its stylesheet differs per browser.

Types

Errors. The type checker's findings; a program that declares no types raises none of them, since what it cannot resolve is Any.

T01 — A value of the wrong type

page P(path: "/", title: "T", description: "D") {
    state count: Number = 0
    Heading("Counter").h1
    Button("Reset") { on click { count = "zero" } }
}
error[T01]: `count` is `String`, but `Number` is wanted
 --> src/App.wf:4:34
  |
4 |     Button("Reset") { on click { count = "zero" } }
  |                                  ^^^^^^^^^^^^^^
  = help: Convert it: `Number(value)`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t01-a-value-of-the-wrong-type

Fix: Give the state a value of its type, or convert: Number(text), "{n}". If the state really holds either, declare it Any.

T02 — A case the enum does not have

enum Tone { calm, loud }
page P(path: "/", title: "T", description: "D") {
    state tone: Tone = .quiet
    Heading("Tone").h1
}
error[T02]: `Tone` has no case `.quiet`
 --> src/App.wf:3:17
  |
3 |     state tone: Tone = .quiet
  |                 ^^^^
  = help: `Tone` takes .calm, .loud
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t02-a-case-the-enum-does-not-have

Fix: Use one of the cases the message lists, or add the case to the enum.

T04 — A value that may be null, read as if it were not

type User { name: String, email: String? }
page P(path: "/", title: "T", description: "D") {
    state user = User(name: "Ada", email: null)
    Heading("Profile").h1
    Text(user.email.toUpperCase())
}
error[T04]: `user.email` may be null, so `.toUpperCase()` may fail
 --> src/App.wf:5:10
  |
5 |     Text(user.email.toUpperCase())
  |          ^^^^^^^^^^
  = help: Unwrap it first: `if let x = value { … }`, `value ?? fallback`, `value?.{method}()`, or a check for `!= null`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t04-a-value-that-may-be-null-read-as-if-it-were-not

Fix: Unwrap it: if let e = user.email { e.toUpperCase() }, user.email?.toUpperCase(), or (user.email ?? "").toUpperCase().

T05 — A field or method that does not exist

type User { name: String }
page P(path: "/", title: "T", description: "D") {
    state user = User(name: "Ada")
    Heading(user.nmae).h1
}
error[T05]: `User` has no field `nmae`
 --> src/App.wf:4:18
  |
4 |     Heading(user.nmae).h1
  |                  ^^^^
  = help: Did you mean `name`? Its fields are `name`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t05-a-field-or-method-that-does-not-exist

Fix: Correct the spelling to one of the fields listed, or add the field to the type.

T06 — A member a store or service does not have

store Cart { state items = [] }
page P(path: "/", title: "T", description: "D") {
    use Cart
    Heading("{Cart.count} items").h1
}
error[T06]: `Cart` has no member `count`
 --> src/App.wf:4:15
  |
4 |     Heading("{Cart.count} items").h1
  |               ^^^^
  = help: Its members are `items`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t06-a-member-a-store-or-service-does-not-have

Fix: Use a member the store declares, or declare it: derived count = items.length. For a service, the endpoint must be declared in its api.

T07 — A condition that is always true

page P(path: "/", title: "T", description: "D") {
    state items = ["a"]
    Heading("List").h1
    if items { Text("There are items") }
}
error[T07]: `if` reads `items` as a condition, but a list is always true
 --> src/App.wf:4:5
  |
4 |     if items { Text("There are items") }
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = help: Ask about its length: `items.length > 0`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t07-a-condition-that-is-always-true

Fix: Ask the question you mean: items.length > 0, user != null.

T08 — A loop over something that is not a list

page P(path: "/", title: "T", description: "D") {
    state total = 3
    Heading("Loop").h1
    for n in total { Text("{n}") }
}
error[T08]: `for` loops over a list, but `total` is `Number`
 --> src/App.wf:4:5
  |
4 |     for n in total { Text("{n}") }
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = help: Give it a list, or `.split(…)` a string first
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t08-a-loop-over-something-that-is-not-a-list

Fix: Loop over a list — for n in 1..=total for a range of numbers.

T09 — An emit that does not match its event

component Stepper(_ start: Number) {
    event change(value: Number)
    Button("+") { on click { emit change(1, 2) } }
}
error[T09]: `emit change` passes 2 arguments, but the event takes 1
 --> src/App.wf:3:30
  |
3 |     Button("+") { on click { emit change(1, 2) } }
  |                              ^^^^^^^^^^^^^^^^^
  = help: `event change(value: Number)`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t09-an-emit-that-does-not-match-its-event

Fix: Pass what the event declares, in order, or change the declaration.

T10 — A call with the wrong arguments

page P(path: "/", title: "T", description: "D") {
    state count = 0
    action add(by: Number) { count = count + by }
    Heading("Count").h1
    Button("+") { on click { add(1, 2) } }
}
error[T10]: `add` takes 1 argument, but 2 are given
 --> src/App.wf:5:30
  |
5 |     Button("+") { on click { add(1, 2) } }
  |                              ^^^
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t10-a-call-with-the-wrong-arguments

Fix: Pass the arguments the action, endpoint or function takes.

T11 — A match on something that cannot be matched

page P(path: "/", title: "T", description: "D") {
    state n = 1
    Heading("Match").h1
    match n {
        loading { Spinner }
        else { Text("?") }
    }
}
error[T11]: `match` needs a resource or an enum, but `n` is `Number`
 --> src/App.wf:4:5
  |
4 |     match n {
  |     ^^^^^^^^^
  = help: Use `if` for a condition; `match` chooses among a resource's states or an enum's cases
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t11-a-match-on-something-that-cannot-be-matched

Fix: Use if for a condition; match takes a resource, a connection or an enum.

T12 — A secret where it would escape

page P(path: "/", title: "T", description: "D") {
    state token: Secret = ""
    Heading("Token").h1
    Text(token)
}
error[T12]: `token` is a `Secret`, and `Text` would show it
 --> src/App.wf:4:10
  |
4 |     Text(token)
  |          ^^^^^
  = help: A secret must not reach the page; send it as a value, or show one field of what it unlocks
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t12-a-secret-where-it-would-escape

Fix: Do not show, splice, log or persist a Secret. Send it in a request — or keep it on the server.

T13 — A name nothing declares

page P(path: "/", title: "T", description: "D") {
    state count = 0
    Heading("Counter").h1
    Button("+") { on click { cuont = count + 1 } }
}
error[T13]: nothing declares `cuont`
 --> src/App.wf:4:30
  |
4 |     Button("+") { on click { cuont = count + 1 } }
  |                              ^^^^^
  = help: Did you mean `count`? Declare it — a `state`, a `const`, an `action`, a prop — or check the spelling. In the browser it would be a ReferenceError
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t13-a-name-nothing-declares

Fix: Correct the spelling, or declare the name. The browser's own globals (window, navigator, Math …) and the language's values (viewport, query, now …) need no declaration; a call to a function nothing declares — uid() for uuid() — is reported the same way. A template rendered with data (wf render) reads its data's keys by name, so it is not held to this.

T14 — A comparison that is always the same

page P(path: "/", title: "T", description: "D") {
    state count = 0
    Heading("Count").h1
    if count == "0" { Text("Nothing yet") }
}
error[T14]: `count` is `Number` and `"0"` is `String`, which are never equal, so this is always false
 --> src/App.wf:4:8
  |
4 |     if count == "0" { Text("Nothing yet") }
  |        ^^^^^
  = help: Convert one side: `Number(text) == n`, or `"{n}" == text`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t14-a-comparison-that-is-always-the-same

Fix: Compare values of one type: count == 0, or convert one side (Number(text) == n). Against an enum, use a case it has.

T15 — A match that misses a case, or has one twice

enum Status { draft, review, live }
page P(path: "/", title: "T", description: "D") {
    state status: Status = .draft
    Heading("Status").h1
    match status {
        .draft { Text("Draft") }
        .live { Text("Live") }
    }
}
error[T15]: this `match` on `status` has no arm for `.review`, and no `else`
 --> src/App.wf:5:5
  |
5 |     match status {
  |     ^^^^^
  = help: Add an arm for each, or `else { … }` for the rest
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t15-a-match-that-misses-a-case-or-has-one-twice

Fix: Give every case its arm, or add else { … } for the rest. A match expression that covers every case needs no else: derived label = match status { .draft { "Draft" } .review { "In review" } .live { "Live" } }.

T16 — A method a number, a string or a list does not have

page P(path: "/", title: "T", description: "D") {
    state names = ["Ada", "Grace"]
    Heading("People").h1
    Text(names.joined(", "))
}
error[T16]: a `[String]` has no method `joined`
 --> src/App.wf:4:16
  |
4 |     Text(names.joined(", "))
  |                ^^^^^^
  = help: Did you mean `join`?
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t16-a-method-a-number-a-string-or-a-list-does-not-have

Fix: The method the message suggests — here join.

T17 — An async result used before it is awaited

page P(path: "/", title: "T", description: "D") {
    state saved = false
    action save() { saved = true }
    Heading("Save").h1
    Button("Save", disabled: save.pending) { on click { save() } }
}
error[T17]: `save` awaits nothing, so `.pending` is always false
 --> src/App.wf:5:30
  |
5 |     Button("Save", disabled: save.pending) { on click { save() } }
  |                              ^^^^
  = help: `.pending` is true while a call of an async action runs; an action that awaits nothing finishes before the page repaints
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t17-an-async-result-used-before-it-is-awaited

Fix: .pending is for an action that awaits — drop it here. An async action's result is read with await inside an action or a handler; a value that arrives over the network is a resource, which derived values and elements can read.

T18 — Arithmetic on something that is not a number

page P(path: "/", title: "T", description: "D") {
    state price = "12"
    Heading("Total {price * 2}").h1
}
error[T18]: `*` takes numbers, but `price` is `String`
 --> src/App.wf:3:27
  |
3 |     Heading("Total {price * 2}").h1
  |                           ^
  = help: Convert it: `Number(value)`; `+` joins text when one side is a string
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t18-arithmetic-on-something-that-is-not-a-number

Fix: Hold numbers as numbers (state price = 12), or convert: Number(price) * 2.

T19 — A value that may be null, shown as text

Warning.

type User { name: String, nickname: String? }
page P(path: "/", title: "T", description: "D") {
    state user = User(name: "Ada", nickname: null)
    Heading("Hello").h1
    Text("Also known as {user.nickname}")
}
warning[T19]: `user.nickname` may be null, and in text it would show as `null`
 --> src/App.wf:5:26
  |
5 |     Text("Also known as {user.nickname}")
  |                          ^^^^^^^^^^^^^
  = help: Say what to show instead: `{user.nickname ?? ""}`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t19-a-value-that-may-be-null-shown-as-text

Fix: Say what to show instead: {user.nickname ?? "nothing yet"}, or show the line only when there is one: if let n = user.nickname { … }.

T21 — A resource match with no error arm

Warning.

page P(path: "/", title: "T", description: "D") {
    resource users = fetch("/api/users")
    Heading("Users").h1
    match users {
        loading { Spinner }
        ready(list) { Text("{list.length} users") }
    }
}
warning[T21]: this `match` on `users` has no `error` arm, so a failed request shows nothing
 --> src/App.wf:4:5
  |
4 |     match users {
  |     ^^^^^
  = help: Add `error(e) { Alert(e.message).danger }`, or `else { … }`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t21-a-resource-match-with-no-error-arm

Fix: error(e) { Alert(e.message).danger }, or an else.

T22 — A handler that names more values than its event sends

Error.

component Entry(_ key: String) {
    event menu(key: String, x: Number, y: Number)
    Button(key) { on contextmenu(e) { e.preventDefault()  emit menu(key, e.clientX, e.clientY) } }
}

page P(path: "/", title: "T", description: "D") {
    state at = ""
    Heading("Files").h1
    Entry("notes.wf") { on menu(key, x, y, z) { at = "{key} at {x}, {y}" } }
    Text(at)
}
error[T22]: `on menu` names 4 values, but `event menu(key: String, x: Number, y: Number)` sends 3 values
 --> src/App.wf:9:44
  |
9 |     Entry("notes.wf") { on menu(key, x, y, z) { at = "{key} at {x}, {y}" } }
  |                                            ^
  = help: Name at most 3, in order: `on menu(key, x, y)`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t22-a-handler-that-names-more-values-than-its-event-sends

A handler names the values its event sends, in the order the event declares them; it may name fewer, never more. A DOM event — click, input, keydown — sends one value, the event itself, so on click(e, x) is this error too.

Fix: Drop the extra names. For a DOM event, name the event alone and read what it carries from it: on click(e) { … e.clientX … }.

T20 — A list or a record shown as text

Warning.

page P(path: "/", title: "T", description: "D") {
    state tags = ["a", "b"]
    Heading("Tags").h1
    Text("Tagged {tags}")
}
warning[T20]: `tags` is `[String]`, and in text it shows as its items run together with commas
 --> src/App.wf:4:19
  |
4 |     Text("Tagged {tags}")
  |                   ^^^^
  = help: Show a field of it, or `.join(", ")` a list of text
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#t20-a-list-or-a-record-shown-as-text

Fix: {tags.join(", ")} for a list of text; a field of a record.

Translations

These need the project's translation files, so the examples are described rather than run.

I01 — A message no translation has

t("nav.blgo") when no locale's file holds nav.blgo: the page shows nav.blgo. Fix: the key the message suggests, or add it to the files.

I02 — A message and its call that disagree

t("greeting", { nme: user.name }) against "greeting": "Hello, {name}!": nme is not used, and {name} shows as written. Fix: pass what the message shows, by the name it shows it.

I03 — A message one locale has and another does not

Warning. nav.blog in en.json and not in ar.json: a reader in Arabic sees nav.blog. Fix: translate it, or remove it from the others.

I04 — A locale the project does not have

setLocale("fr") when i18n.locales is ["en", "ar"]. Fix: add the locale and its file, or name one that exists.

Accessibility

Warnings. What makes a page unusable with a screen reader, a keyboard or a voice.

A01 — An image with no alt text

page P(path: "/", title: "T", description: "D") {
    Heading("Photo").h1
    Image(src: "/team.jpg")
}
warning[A01]: Image missing "alt" attribute
 --> src/App.wf:3:5
  |
3 |     Image(src: "/team.jpg")
  |     ^^^^^
  = help: Add alt text: Image(src: "...", alt: "Description of image")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a01-an-image-with-no-alt-text

Fix: Image(src: "/team.jpg", alt: "The team at the launch"). A purely decorative image takes alt: "".

A02 — An icon button with no name

page P(path: "/", title: "T", description: "D") {
    Heading("Close").h1
    IconButton(icon: "close")
}
warning[A02]: IconButton missing accessible label
 --> src/App.wf:3:5
  |
3 |     IconButton(icon: "close")
  |     ^^^^^^^^^^
  = help: Add a label: IconButton(icon: "close", label: "Close dialog")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a02-an-icon-button-with-no-name

Fix: IconButton(icon: "close", label: "Close") — the label is read aloud, never shown.

A03 — An input with no label

page P(path: "/", title: "T", description: "D") {
    state name = ""
    Heading("Name").h1
    Input(bind: name).text
}
warning[A03]: Input missing "label" or "placeholder" attribute
 --> src/App.wf:4:5
  |
4 |     Input(bind: name).text
  |     ^^^^^
  = help: Add a label: Input(label: "Username").text
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a03-an-input-with-no-label

Fix: Input(bind: name, label: "Name"). A placeholder alone disappears as the reader types.

A04 — A control with no label

page P(path: "/", title: "T", description: "D") {
    state agree = false
    Heading("Terms").h1
    Checkbox(bind: agree)
}
warning[A04]: Checkbox missing "label" attribute
 --> src/App.wf:4:5
  |
4 |     Checkbox(bind: agree)
  |     ^^^^^^^^
  = help: Add a label: Checkbox(bind: value, label: "Description")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a04-a-control-with-no-label

Fix: Checkbox(bind: agree, label: "I agree to the terms").

A05 — A button with no text

page P(path: "/", title: "T", description: "D") {
    Heading("Actions").h1
    Button { Icon("check") }
}
warning[A05]: Button has no text content
 --> src/App.wf:3:5
  |
3 |     Button { Icon("check") }
  |     ^^^^^^
  = help: Add text: Button("Save").primary
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a05-a-button-with-no-text

Fix: Give it text — Button("Save"), or a Text in its block beside the icon, Button { Icon("check") Text("Save") } — or an aria-label: when it shows only an icon. A Text anywhere in the block names it — in a branch, in a component the block places, or reading state; a Tooltip's words, which show only on hover, do not.

A06 — A link with no text

page P(path: "/", title: "T", description: "D") {
    Heading("Links").h1
    Link(to: "/about")
}
warning[A06]: Link has no text content
 --> src/App.wf:3:5
  |
3 |     Link(to: "/about")
  |     ^^^^
  = help: Add text: Link("About", to: "/about")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a06-a-link-with-no-text

Fix: Give it text that names where it goes — not "click here".

A07 — An empty heading

page P(path: "/", title: "T", description: "D") {
    Heading("Title").h1
    Heading("").h2
}
warning[A07]: Heading has empty text content
 --> src/App.wf:3:5
  |
3 |     Heading("").h2
  |     ^^^^^^^
  = help: Headings should have meaningful text
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a07-an-empty-heading

Fix: Give the heading text, or remove it; style text with style { } rather than an empty heading.

A08 — A dialog with no title

page P(path: "/", title: "T", description: "D") {
    state open = false
    Heading("Modal").h1
    Modal(visible: open) { Text("Hello") }
}
warning[A08]: Modal missing "title" attribute
 --> src/App.wf:4:5
  |
4 |     Modal(visible: open) { Text("Hello") }
  |     ^^^^^
  = help: Add a title: Modal(visible: state, title: "Dialog Title")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a08-a-dialog-with-no-title

Fix: Modal(visible: open, title: "Delete this?") — the title is its accessible name.

A09 — Media nobody can follow without sound

page P(path: "/", title: "T", description: "D") {
    Heading("Demo").h1
    Video(src: "/demo.mp4").controls
}
warning[A09]: Video missing "captions"
 --> src/App.wf:3:5
  |
3 |     Video(src: "/demo.mp4").controls
  |     ^^^^^
  = help: Add captions: Video(src: "...", captions: "/captions.en.vtt")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a09-media-nobody-can-follow-without-sound

Fix: Video(src: …, captions: "/demo.en.vtt").controls; an Audio takes transcript:.

A10 — A table with no header row

page P(path: "/", title: "T", description: "D") {
    Heading("Table").h1
    Table(caption: "Prices") { Table.Body { Table.Row { Table.Cell("Pen")  Table.Cell("2") } } }
}
warning[A10]: Table has no header row (`Table.Head`)
 --> src/App.wf:3:5
  |
3 |     Table(caption: "Prices") { Table.Body { Table.Row { Table.Cell("Pen")  Table.Cell("2") } } }
  |     ^^^^^
  = help: Add one: `Table { Table.Head { Table.Row { Table.Cell("Name") } } … }`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a10-a-table-with-no-header-row

Fix: Put the first row in Table.Head, so its cells are <th scope="col">.

A11 — A skipped heading level

page P(path: "/", title: "T", description: "D") {
    Heading("Title").h1
    Heading("Deep").h4
}
warning[A11]: Heading level skips from h1 to h4
 --> src/App.wf:3:5
  |
3 |     Heading("Deep").h4
  |     ^^^^^^^
  = help: Use h2 instead, or add the missing intermediate headings
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a11-a-skipped-heading-level

Fix: Use the next level down (h2 after h1). Size text with style { font-size }, not with the heading level.

A12 — A page without exactly one h1

page P(path: "/", title: "T", description: "D") {
    Heading("Section").h2
}
warning[A12]: Page has no h1 heading
 --> src/App.wf:1:1
  |
1 | page P(path: "/", title: "T", description: "D") {
  | ^^^^
  = help: Add a main heading: Heading("Page Title").h1
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a12-a-page-without-exactly-one-h1

Fix: One Heading(…).h1 per page, naming what the page is. A layout's h1 counts for the page it frames.

A13 — Colours without enough contrast

theme Faint {
    color-text: #BBBBBB
    color-background: #FFFFFF
}
page P(path: "/", title: "T", description: "D") {
    Heading("Contrast").h1
}
warning[A13]: text on a card or surface has a contrast ratio of 1.83:1, below the 4.5:1 WCAG AA minimum
 --> src/App.wf:2:1
  |
2 |     color-text: #BBBBBB
  | ^
  = help: Darken --color-text or lighten --color-surface until they clear 4.5:1
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a13-colours-without-enough-contrast

Fix: Darken the text or lighten the background until the ratio clears 4.5:1 (3:1 for large text).

A14 — A role whose children are the wrong kind

page P(path: "/", title: "T", description: "D") {
    Heading("Tabs").h1
    Row(role: "tablist") {
        Button("One")
        Button("Two")
    }
}
warning[A14]: role "tablist" requires children with role "tab", but holds a Button
 --> src/App.wf:3:5
  |
3 |     Row(role: "tablist") {
  |     ^^^
  = help: Give each child role: "tab", or use the built-in that owns this structure
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a14-a-role-whose-children-are-the-wrong-kind

Fix: Give each child the role the parent requires (role: "tab"), or use the built-in that owns the structure — Tabs.

A15 — An aria-label that does not contain the visible text

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Button("Save", aria-label: "Submit the form")
}
warning[A15]: Button shows "save" but its aria-label says "Submit the form"
 --> src/App.wf:3:5
  |
3 |     Button("Save", aria-label: "Submit the form")
  |     ^^^^^^
  = help: Start the aria-label with the visible text, so what a user says matches what they see
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a15-an-aria-label-that-does-not-contain-the-visible-text

Fix: Start the aria-label with the visible words: aria-label: "Save the draft".

A16 — One id on several elements

Warning.

page P(path: "/", title: "T", description: "D") {
    state rows = ["a", "b"]
    Heading("Rows").h1
    for r in rows by r { Text(r, id: "row") }
}
warning[A16]: `id: "row"` is inside a `for`, so the page has several elements with one id
 --> src/App.wf:4:26
  |
4 |     for r in rows by r { Text(r, id: "row") }
  |                          ^^^^
  = help: Make it unique — `id: "row-{item.id}"` — or drop it; a label's `for` and `aria-*` find one element by id
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a16-one-id-on-several-elements

Fix: Make it unique — id: "row-{r}" — or drop it.

A17 — A frame with no title

Warning.

page P(path: "/", title: "T", description: "D") {
    Heading("Preview").h1
    Frame(src: "https://example.com/")
}
warning[A17]: Frame missing "title" attribute
 --> src/App.wf:3:5
  |
3 |     Frame(src: "https://example.com/")
  |     ^^^^^
  = help: Say what it shows: Frame(src: "/preview/", title: "Preview")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#a17-a-frame-with-no-title

Fix: Frame(src: "https://example.com/", title: "Example") — the title is how a screen reader announces it.

Search and sharing

Warnings. What a search engine or a link preview would act on.

S01 — A page with no title

page P(path: "/", description: "D") {
    Heading("No title").h1
}
warning[S01]: Page P has no title
 --> src/App.wf:1:1
  |
1 | page P(path: "/", description: "D") {
  | ^^^^
  = help: Add one: page Name(path: "/", title: "What this page is")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#s01-a-page-with-no-title

Fix: page P(path: "/", title: "What this page is").

S02 — A page with no description

page P(path: "/", title: "T") {
    Heading("No description").h1
}
warning[S02]: Page P has no description
 --> src/App.wf:1:1
  |
1 | page P(path: "/", title: "T") {
  | ^^^^
  = help: Add one: page Name(path: "/", title: "…", description: "A sentence a search result can show")
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#s02-a-page-with-no-description

Fix: Add a description: of about 150 characters: what a reader gets from the page.

S03 — A description too long for a search result

page P(path: "/", title: "T", description: "A description that goes on for far longer than any search result will ever show, because it keeps adding clauses, qualifications and asides until the snippet is cut mid-sentence.") {
    Heading("Long").h1
}
warning[S03]: Page P's description is 178 characters; a search result shows about 160
 --> src/App.wf:1:1
  |
1 | page P(path: "/", title: "T", description: "A description that goes on for far longer than any search result will ever show, because it keeps adding clauses, qualifications and asides until the snippet is cut mid-sentence.") {
  | ^^^^
  = help: Shorten it, or accept that it will be cut mid-sentence
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#s03-a-description-too-long-for-a-search-result

Fix: Shorten it to about 160 characters.

S04 — Two pages on one route

page A(path: "/about", title: "A", description: "D") { Heading("A").h1 }
page B(path: "/about", title: "B", description: "D") { Heading("B").h1 }
error[S04]: Pages A and B both claim the route /about
 --> src/App.wf:2:1
  |
2 | page B(path: "/about", title: "B", description: "D") { Heading("B").h1 }
  | ^^^^
  = help: Give each page its own path
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#s04-two-pages-on-one-route

Fix: Give each page its own path:. Only one of them would ever render.

What is kept in the browser

Warnings. persist values across releases.

P01 — A persisted value with no way forward

store Cart {
    persist items: [String] = [] {
        version: 2
    }
}
warning[P01]: `items` is at version 2, and nothing brings version 1 forward
 --> src/App.wf:2:5
  |
2 |     persist items: [String] = [] {
  |     ^^^^^^^
  = help: A reader who last visited then loses what they had. Add `migrate 1 -> 2`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#p01-a-persisted-value-with-no-way-forward

Fix: Add a step for each older version: migrate 1 -> 2 { old.map(…) }.

P02 — A migration that never runs

store Cart {
    persist items: [String] = [] {
        version: 1
        migrate 1 -> 2 { old }
    }
}
warning[P02]: `migrate 1 -> 2` on `items` is past the declared version
 --> src/App.wf:2:5
  |
2 |     persist items: [String] = [] {
  |     ^^^^^^^
  = help: A step above `version:` never runs; raise the version or drop the step
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#p02-a-migration-that-never-runs

Fix: Raise version: to the step's target, or drop the step.

P03 — A persisted value in a route-scoped store

store Filters(scope: .route) {
    persist search = ""
}
warning[P03]: `search` is persisted in a store scoped to the route
 --> src/App.wf:2:5
  |
2 |     persist search = ""
  |     ^^^^^^^
  = help: The route change drops the store and the next read builds it again from storage, so the value comes straight back. Use `state` for what the route owns, or widen the scope for what outlives it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#p03-a-persisted-value-in-a-route-scoped-store

Fix: Keep route-scoped data in state, or move the value to a store with a wider scope.

Declared and never read

Warnings. A name beginning _ is taken as unused on purpose.

U01 — A state nothing reads

page P(path: "/", title: "T", description: "D") {
    state unused = 0
    Heading("Unused").h1
}
warning[U01]: `unused` is declared but never read
 --> src/App.wf:2:5
  |
2 |     state unused = 0
  |     ^^^^^
  = help: Nothing reads the state; remove it, or name it `_unused` to keep it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u01-a-state-nothing-reads

Fix: Remove it, read it, or name it _unused to keep it on purpose.

U02 — A derived value nothing reads

page P(path: "/", title: "T", description: "D") {
    state n = 1
    derived doubled = n * 2
    Heading("{n}").h1
}
warning[U02]: `doubled` is declared but never read
 --> src/App.wf:3:5
  |
3 |     derived doubled = n * 2
  |     ^^^^^^^
  = help: Nothing reads the derived value; remove it, or name it `_doubled` to keep it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u02-a-derived-value-nothing-reads

Fix: Remove it or read it; a leading _ keeps it.

U03 — A component nothing places

component Orphan { Text("Nobody places me") }
page P(path: "/", title: "T", description: "D") {
    Heading("Home").h1
}
warning[U03]: `Orphan` is declared but never placed
 --> src/App.wf:1:1
  |
1 | component Orphan { Text("Nobody places me") }
  | ^^^^^^^^^
  = help: Place it in a page, name it as a layout, or remove it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u03-a-component-nothing-places

Fix: Place it, name it as a layout:, publish it in build.elements, or remove it.

U04 — A store member nothing reads

store Cart {
    state items = []
    state coupon = ""
}
page P(path: "/", title: "T", description: "D") {
    use Cart
    Heading("{Cart.items.length}").h1
}
warning[U04]: `Cart.coupon` is declared but never read
 --> src/App.wf:3:5
  |
3 |     state coupon = ""
  |     ^^^^^
  = help: Nothing reads the state, inside the store or as `Cart.coupon`; remove it, or name it `_coupon` to keep it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u04-a-store-member-nothing-reads

Fix: Remove the member, or name it with a leading _.

U05 — An action nothing calls

page P(path: "/", title: "T", description: "D") {
    state n = 0
    action reset() { n = 0 }
    Heading("{n}").h1
}
warning[U05]: `reset` is declared but never read
 --> src/App.wf:3:5
  |
3 |     action reset() { n = 0 }
  |     ^^^^^^
  = help: Nothing reads the action; remove it, or name it `_reset` to keep it
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u05-an-action-nothing-calls

Fix: Call it, remove it, or name it _reset.

U06 — Code after return

page P(path: "/", title: "T", description: "D") {
    state n = 0
    action reset() {
        n = 0
        return n
        log("reset")
    }
    Heading("Count {n}").h1
    Button("Reset") { on click { reset() } }
}
warning[U06]: this never runs: the `return` above leaves first
 --> src/App.wf:6:9
  |
6 |         log("reset")
  |         ^^^^^^^^^^^^
  = help: Remove it, or move it above the `return`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u06-code-after-return

Fix: Remove it, or move it above the return.

U07 — An allow that allows nothing

A // wf-allow(CODE) comment accepts one finding on purpose: on a line of its own it covers the next line of code, and at the end of a line it covers that line. It names codes (U01) or families (U), comma-separated, and only what lints could lower — a warning, or an error that ships no broken page. An allow that covers nothing it names is reported, so allows cannot outlive what they were written for.

page P(path: "/", title: "T", description: "D") {
    // wf-allow(U01)
    state shown = 0
    Heading("Count {shown}").h1
}
warning[U07]: `wf-allow(U01)` allows nothing: no `U01` is reported on the line it covers
 --> src/App.wf:2:5
  |
2 |     // wf-allow(U01)
  |     ^^^^^^^^^^^^^^^^
  = help: Remove it — what it was written for is gone
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u07-an-allow-that-allows-nothing

Fix: Remove the allow — what it was written for is gone.

U08 — A condition that is always the same

page P(path: "/", title: "T", description: "D") {
    Heading("Beta").h1
    if false { Text("Coming soon") }
}
warning[U08]: `false` is always false, so one branch never runs
 --> src/App.wf:3:8
  |
3 |     if false { Text("Coming soon") }
  |        ^^^^^
  = help: Use the value that decides, or remove the branch that cannot run
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u08-a-condition-that-is-always-the-same

Fix: Use the value that decides, or remove the branch that cannot run.

U09 — An unkeyed loop whose items hold state

Warning.

page P(path: "/", title: "T", description: "D") {
    state rows = [{ id: 1, name: "a" }]
    Heading("Rows").h1
    for r in rows { Input(bind: r.name, label: "Name") }
}
warning[U09]: this `for` has no `by`, and each item holds a `Input`, which starts again whenever the list changes
 --> src/App.wf:4:5
  |
4 |     for r in rows { Input(bind: r.name, label: "Name") }
  |     ^^^
  = help: Key it: `for r in … by r.id`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u09-an-unkeyed-loop-whose-items-hold-state

Fix: Key it: for r in rows by r.id.

U10 — An else no value reaches

enum Tone { calm, loud }
page P(path: "/", title: "T", description: "D") {
    state tone: Tone = .calm
    Heading("Tone").h1
    match tone {
        .calm { Text("Calm") }
        .loud { Text("Loud") }
        else { Text("?") }
    }
}
warning[U10]: every case of `Tone` has its own arm, so this `match`'s `else` is never reached
 --> src/App.wf:5:5
  |
5 |     match tone {
  |     ^^^^^
  = help: Remove the `else`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#u10-an-else-no-value-reaches

Fix: Remove the else.

Vocabulary

Warnings.

V01 — A bare word that means nothing

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Button(primary)
}
warning[V01]: nothing in scope declares `primary`, so it reads as nothing — a flag is written `.primary`
 --> src/App.wf:3:12
  |
3 |     Button(primary)
  |            ^^^^^^^
  = help: did you mean `.primary`?
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v01-a-bare-word-that-means-nothing

Fix: Write the flag with its dot — Button("Save").primary — or declare the name. The word is also a name nothing declares, so the build stops on it as T13; the editor shows both.

A name a branch binds is declared through the whole of its block, however deep the element that reads it: if let why = e.reason { Row { Pill(why) } }, and a case's payload, .failed(why) { … }, draw nothing.

V03 — Markup put in as markup

page P(path: "/", title: "T", description: "D") {
    state body = "<b>Hi</b>"
    Heading("Markup").h1
    Unsafe.Html(body)
}
warning[V03]: `Unsafe.Html` puts markup in as markup
 --> src/App.wf:4:5
  |
4 |     Unsafe.Html(body)
  |     ^^^^^^^^^^^
  = help: Anything the project did not write itself belongs in `sanitize(…)` first
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v03-markup-put-in-as-markup

Fix: Wrap markup from outside the project in sanitize(…): Unsafe.Html(sanitize(body)).

V02 — A flag no stylesheet styles

A flag on one of your components whose class no stylesheet — the engine's or one of the project's .css files — defines, so it changes nothing on screen. The built-ins cannot draw it: the registry and the engine's stylesheet are held to each other. Fix: define the class in a .css file under src/, or drop the flag.

V04 — A class that is the engine's

page P(path: "/", title: "T", description: "D") {
    Heading("Classes").h1
    Card(class: "wf-btn") { Text("x") }
}
warning[V04]: `class:` names `wf-btn`, one of the engine's own classes
 --> src/App.wf:3:5
  |
3 |     Card(class: "wf-btn") { Text("x") }
  |     ^^^^
  = help: `wf-` classes are the built-ins': one added here brings another built-in's rules with it. Name a class of your own, or use the flag that sets the look
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v04-a-class-that-is-the-engine-s

Fix: Name a class of your own and style it in a .css file under src/, or use the flag that sets the look you wanted (.primary, .elevated).

V05 — A CSS property no browser knows

Warning.

page P(path: "/", title: "T", description: "D") {
    Heading("Hello") {
        style { colr: red }
    }
}
warning[V05]: `colr` is not a CSS property, so the browser ignores it
 --> src/App.wf:3:17
  |
3 |         style { colr: red }
  |                 ^^^^
  = help: Did you mean `color`?
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v05-a-css-property-no-browser-knows

Fix: The property the message suggests — here color. A custom property is written --name.

V06 — A design token the theme does not declare

page P(path: "/", title: "T", description: "D") {
    Heading("Hello") {
        style { color: $brnad }
    }
}
error[V06]: `$brnad` is no token the theme declares, so it is never set
 --> src/App.wf:3:17
  |
3 |         style { color: $brnad }
  |                 ^^^^^
  = help: Declare it in your `theme { … }`, or use one the theme has
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v06-a-design-token-the-theme-does-not-declare

Fix: A token the theme has — a short name resolves through the property (padding: $lg, border: 1px solid $border) — or declare it in your theme { … }.

V07 — A number where CSS wants a length

Warning.

page P(path: "/", title: "T", description: "D") {
    state pct = 40
    Heading("Progress").h1
    Card { style { width: {pct} } }
}
warning[V07]: `width: {pct}` is a number with no unit, which the browser drops
 --> src/App.wf:4:20
  |
4 |     Card { style { width: {pct} } }
  |                    ^^^^^^^^^^^^
  = help: Give it one: `width: {pct}px` (or `%`, `rem`)
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v07-a-number-where-css-wants-a-length

Fix: Give it a unit: width: {pct}%.

V08 — An icon the runtime does not draw

page P(path: "/", title: "T", description: "D") {
    Heading("Launch").h1
    Icon("rocket")
}
warning[V08]: `rocket` is not an icon the runtime draws; it will show as the word
 --> src/App.wf:3:10
  |
3 |     Icon("rocket")
  |          ^
  = help: The icons: close, menu, search, home, user, settings, check, plus, minus, edit, trash, star, heart, mail, bell, download, upload, eye, link, calendar, filter, chevron-down, chevron-right, chevron-left, info, warning, arrow-left, arrow-right, logout, copy, sun, moon, folder, folder-plus, folder-off, file, file-plus, refresh, alert-circle, clock, dot, diff, book, git-pull-request, pin, git-branch, more-horizontal, window-minimize, window-maximize, outline, help-circle, collapse-all, locate, chevron-up, globe, external-link, panel-down, panel-up, code, monitor, git-commit, play, layers, insert, grid, list, square, pill, image, sparkle, lock
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v08-an-icon-the-runtime-does-not-draw

Fix: Use one of the built-in icons the message lists.

V09 — An event the element does not fire

page P(path: "/", title: "T", description: "D") {
    Heading("Save").h1
    Button("Save") { on clik { log("saved") } }
}
warning[V09]: `clik` is not an event Button fires
 --> src/App.wf:3:22
  |
3 |     Button("Save") { on clik { log("saved") } }
  |                      ^^
  = help: A handler names a DOM event (`click`, `input`, `change`, `submit`, `keydown`, `pointerdown`, `scroll`, …) or an event the element declares
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v09-an-event-the-element-does-not-fire

Fix: Name a DOM event (click, input, change, submit, keydown, …) or one the component declares.

V10 — A Host tag it does not make

A Host is made of one of a few elements: div, span, canvas, svg, section, figure, pre, p, ul or table — the tag is written into the document, so it is an allow-list rather than whatever the string says. Any other tag is a div.

page P(path: "/", title: "T", description: "D") {
    Heading("Chart").h1
    Host(tag: "video", mount: (n) => n)
}
warning[V10]: `Host` makes no `video`, so it is a `div`
 --> src/App.wf:3:10
  |
3 |     Host(tag: "video", mount: (n) => n)
  |          ^^^
  = help: It makes `div`, `span`, `canvas`, `svg`, `section`, `figure`, `pre`, `p`, `ul`, `table`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v10-a-host-tag-it-does-not-make

Fix: Make it one of the tags Host makes, or use the element the built-ins offer for it (Video, Audio, Image).

V11 — A component named like a built-in

A call of a built-in's name draws the built-in, so a component declared with that name is never drawn: its body, its props, its slots go unused, and what the page shows is the built-in instead. It happens to a component written before WebFluent gained an element of the same name — a Footer from WebFluent 2, a Chart from before 5.2.

component Chart(_ title: String) {
    Text(title)
}

page P(path: "/", title: "T", description: "D") {
    Heading("Sales").h1
    Chart(kind: .bar, data: [3, 5, 2])
}
warning[V11]: `Chart` is a built-in component, so a call of that name draws the built-in, never this one
 --> src/App.wf:1:1
  |
1 | component Chart(_ title: String) {
  | ^^^^^^^^^
  = help: Rename it — `MyChart`, `ReportChart` — and its call sites
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v11-a-component-named-like-a-built-in

Fix: Rename the component and its call sites.

V12 — A long list that is not windowed

A VirtualList draws only the rows in view of the one keyed for that is the whole of its body. With anything else beside the for, or a for with no by — which has no way to keep a row's nodes as it scrolls — it draws every row, as a plain scrolling box does, and the length of the list is what the page pays for.

page P(path: "/", title: "T", description: "D") {
    state rows = ["a", "b"]
    Heading("Files").h1
    VirtualList(rowHeight: 32) {
        for r in rows { Text(r) }
    }
}
warning[V12]: This `VirtualList` draws every row: its body is not one keyed `for`
 --> src/App.wf:4:5
  |
4 |     VirtualList(rowHeight: 32) {
  |     ^^^^^^^^^^^
  = help: Make its body one `for` with a `by`: `VirtualList(rowHeight: 32) { for row in rows by row.id { … } }`
  = docs: https://webfluent.monzeromer.dev/docs/guide/diagnostics#v12-a-long-list-that-is-not-windowed

Fix: Give the for a by, and move anything else — a heading, a count — outside the VirtualList.

Next

Built-ins.