Diagnostics
Every error and warning the compiler reports — what it means, a program that draws it, the message, and the fix.
wf build, wf serve and your editor report the same findings. Each names the file, the line and the column, says what is wrong in a sentence, and usually offers a fix on the line under it:
text
Error: [T04] `user.email` may be null, so `.toUpperCase()` may fail at src/pages/Profile.wf:12:10
Unwrap it first: `if let x = value { … }`, `value ?? fallback`, …
- 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.
The code in brackets — T04 — is the thing to search this page for. Every example below is compiled by the test suite and draws exactly the code it is filed under.
Structural errors
Errors without a code, because each is a reference to something that does not exist, or code where there must be none:
| You wrote | The compiler says | Fix |
|---|---|---|
|
|
unknown component |
Check the spelling, or declare it |
|
|
Button has no flag or enum case |
Use a listed flag |
|
|
|
Use a listed case |
|
|
Only the first argument may be positional |
Name the others: |
|
|
|
Add |
|
two |
duplicate component |
Rename or remove one |
|
|
|
Handle a declared event, or a DOM event |
|
|
|
Fill a declared slot, or declare |
|
|
|
Put it in |
|
|
|
|
|
|
|
A relative URL, or |
|
|
|
Rename it |
A parse error — a missing brace, a stray character — names what it expected and what it found, at the line it stopped.
Warnings without a code
| You wrote | The compiler says |
|---|---|
|
|
Button has no prop |
|
|
|
|
a key in |
|
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
wf · draws T01
page P(path: "/", title: "T", description: "D") {
state count: Number = 0
Heading("Counter").h1
Button("Reset") { on click { count = "zero" } }
}
text
Error: [T01] `count` is `String`, but `Number` is wanted at src/App.wf:4:34
Convert it: `Number(value)`
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
wf · draws T02
enum Tone { calm, loud }
page P(path: "/", title: "T", description: "D") {
state tone: Tone = .quiet
Heading("Tone").h1
}
text
Error: [T02] `Tone` has no case `.quiet` at src/App.wf:3:17
`Tone` takes .calm, .loud
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
wf · draws T04
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())
}
text
Error: [T04] `user.email` may be null, so `.toUpperCase()` may fail at src/App.wf:5:10
Unwrap it first: `if let x = value { … }`, `value ?? fallback`, `value?.{method}()`, or a check for `!= null`
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
wf · draws T05
type User { name: String }
page P(path: "/", title: "T", description: "D") {
state user = User(name: "Ada")
Heading(user.nmae).h1
}
text
Error: [T05] `User` has no field `nmae` at src/App.wf:4:18
Its fields are `name`
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
wf · draws T06
store Cart { state items = [] }
page P(path: "/", title: "T", description: "D") {
use Cart
Heading("{Cart.count} items").h1
}
text
Error: [T06] `Cart` has no member `count` at src/App.wf:4:15
Its members are `items`
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
wf · draws T07
page P(path: "/", title: "T", description: "D") {
state items = ["a"]
Heading("List").h1
if items { Text("There are items") }
}
text
Error: [T07] `if` reads `items` as a condition, but a list is always true at src/App.wf:4:5
Ask about its length: `items.length > 0`
Fix: Ask the question you mean: items.length > 0, user != null.
T08 — A loop over something that is not a list
wf · draws T08
page P(path: "/", title: "T", description: "D") {
state total = 3
Heading("Loop").h1
for n in total { Text("{n}") }
}
text
Error: [T08] `for` loops over a list, but `total` is `Number` at src/App.wf:4:5
Give it a list, or `.split(…)` a string first
Fix: Loop over a list — for n in 1..=total for a range of numbers.
T09 — An emit that does not match its event
wf · draws T09
component Stepper(_ start: Number) {
event change(value: Number)
Button("+") { on click { emit change(1, 2) } }
}
text
Error: [T09] `emit change` passes 2 arguments, but the event takes 1 at src/App.wf:3:30
`event change(value: Number)`
Fix: Pass what the event declares, in order, or change the declaration.
T10 — A call with the wrong arguments
wf · draws T10
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) } }
}
text
Error: [T10] `add` takes 1 argument, but 2 are given at src/App.wf:5:30
Fix: Pass the arguments the action, endpoint or function takes.
T11 — A match on something that cannot be matched
wf · draws T11
page P(path: "/", title: "T", description: "D") {
state n = 1
Heading("Match").h1
match n {
loading { Spinner }
else { Text("?") }
}
}
text
Error: [T11] `match` needs a resource or an enum, but `n` is `Number` at src/App.wf:4:5
Use `if` for a condition; `match` chooses among a resource's states or an enum's cases
Fix: Use if for a condition; match takes a resource, a connection or an enum.
T12 — A secret where it would escape
wf · draws T12
page P(path: "/", title: "T", description: "D") {
state token: Secret = ""
Heading("Token").h1
Text(token)
}
text
Error: [T12] `token` is a `Secret`, and `Text` would show it at src/App.wf:4:10
A secret must not reach the page; send it as a value, or show one field of what it unlocks
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
wf · draws T13
page P(path: "/", title: "T", description: "D") {
state count = 0
Heading("Counter").h1
Button("+") { on click { cuont = count + 1 } }
}
text
Error: [T13] nothing declares `cuont` at src/App.wf:4:30
Declare it — a `state`, a `const`, an `action`, a prop — or check the spelling. In the browser it would be a ReferenceError
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.
Accessibility
Warnings. What makes a page unusable with a screen reader, a keyboard or a voice.
A01 — An image with no alt text
wf · draws A01
page P(path: "/", title: "T", description: "D") {
Heading("Photo").h1
Image(src: "/team.jpg")
}
text
Warning [A01]: Image missing "alt" attribute at src/App.wf:3:5
Add alt text: Image(src: "...", alt: "Description of image")
Fix: Image(src: "/team.jpg", alt: "The team at the launch"). A purely decorative image takes alt: "".
A02 — An icon button with no name
wf · draws A02
page P(path: "/", title: "T", description: "D") {
Heading("Close").h1
IconButton(icon: "close")
}
text
Warning [A02]: IconButton missing accessible label at src/App.wf:3:5
Add a label: IconButton(icon: "close", label: "Close dialog")
Fix: IconButton(icon: "close", label: "Close") — the label is read aloud, never shown.
A03 — An input with no label
wf · draws A03
page P(path: "/", title: "T", description: "D") {
state name = ""
Heading("Name").h1
Input(bind: name).text
}
text
Warning [A03]: Input missing "label" or "placeholder" attribute at src/App.wf:4:5
Add a label: Input(label: "Username").text
Fix: Input(bind: name, label: "Name"). A placeholder alone disappears as the reader types.
A04 — A control with no label
wf · draws A04
page P(path: "/", title: "T", description: "D") {
state agree = false
Heading("Terms").h1
Checkbox(bind: agree)
}
text
Warning [A04]: Checkbox missing "label" attribute at src/App.wf:4:5
Add a label: Checkbox(bind: value, label: "Description")
Fix: Checkbox(bind: agree, label: "I agree to the terms").
A05 — A button with no text
wf · draws A05
page P(path: "/", title: "T", description: "D") {
Heading("Actions").h1
Button { Icon("check") }
}
text
Warning [A05]: Button has no text content at src/App.wf:3:5
Add text: Button("Save").primary
Fix: Give it text, or an aria-label: when it shows only an icon.
A06 — A link with no text
wf · draws A06
page P(path: "/", title: "T", description: "D") {
Heading("Links").h1
Link(to: "/about")
}
text
Warning [A06]: Link has no text content at src/App.wf:3:5
Add text: Link("About", to: "/about")
Fix: Give it text that names where it goes — not "click here".
A07 — An empty heading
wf · draws A07
page P(path: "/", title: "T", description: "D") {
Heading("Title").h1
Heading("").h2
}
text
Warning [A07]: Heading has empty text content at src/App.wf:3:5
Headings should have meaningful text
Fix: Give the heading text, or remove it; style text with style { } rather than an empty heading.
A08 — A dialog with no title
wf · draws A08
page P(path: "/", title: "T", description: "D") {
state open = false
Heading("Modal").h1
Modal(visible: open) { Text("Hello") }
}
text
Warning [A08]: Modal missing "title" attribute at src/App.wf:4:5
Add a title: Modal(visible: state, title: "Dialog Title")
Fix: Modal(visible: open, title: "Delete this?") — the title is its accessible name.
A09 — Media nobody can follow without sound
wf · draws A09
page P(path: "/", title: "T", description: "D") {
Heading("Demo").h1
Video(src: "/demo.mp4").controls
}
text
Warning [A09]: Video missing "captions" at src/App.wf:3:5
Add captions: Video(src: "...", captions: "/captions.en.vtt")
Fix: Video(src: …, captions: "/demo.en.vtt").controls; an Audio takes transcript:.
A10 — A table with no header row
wf · draws A10
page P(path: "/", title: "T", description: "D") {
Heading("Table").h1
Table(caption: "Prices") { Table.Body { Table.Row { Table.Cell("Pen") Table.Cell("2") } } }
}
text
Warning [A10]: Table missing header row (Thead) at src/App.wf:3:5
Add a header: Table { Thead { Tcell("Column Name") } ... }
Fix: Put the first row in Table.Head, so its cells are <th scope="col">.
A11 — A skipped heading level
wf · draws A11
page P(path: "/", title: "T", description: "D") {
Heading("Title").h1
Heading("Deep").h4
}
text
Warning [A11]: Heading level skips from h1 to h4 at src/App.wf:3:5
Use h2 instead, or add the missing intermediate headings
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
wf · draws A12
page P(path: "/", title: "T", description: "D") {
Heading("Section").h2
}
text
Warning [A12]: Page has no h1 heading at src/App.wf:1:1
Add a main heading: Heading("Page Title").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
wf · draws A13
theme Faint {
color-text: #BBBBBB
color-background: #FFFFFF
}
page P(path: "/", title: "T", description: "D") {
Heading("Contrast").h1
}
text
Warning [A13]: body text on the page background has a contrast ratio of 1.92:1, below the 4.5:1 WCAG AA minimum at src/App.wf:3:1
Darken --color-text or lighten --color-background until they clear 4.5:1
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
wf · draws A14
page P(path: "/", title: "T", description: "D") {
Heading("Tabs").h1
Row(role: "tablist") {
Button("One")
Button("Two")
}
}
text
Warning [A14]: role "tablist" requires children with role "tab", but holds a Button at src/App.wf:3:5
Give each child role: "tab", or use the built-in that owns this structure
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
wf · draws A15
page P(path: "/", title: "T", description: "D") {
Heading("Save").h1
Button("Save", aria-label: "Submit the form")
}
text
Warning [A15]: Button shows "save" but its aria-label says "Submit the form" at src/App.wf:3:5
Start the aria-label with the visible text, so what a user says matches what they see
Fix: Start the aria-label with the visible words: aria-label: "Save the draft".
Search and sharing
Warnings. What a search engine or a link preview would act on.
S01 — A page with no title
wf · draws S01
page P(path: "/", description: "D") {
Heading("No title").h1
}
text
Warning [S01]: Page P has no title at src/App.wf:1:1
Add one: page Name(path: "/", title: "What this page is")
Fix: page P(path: "/", title: "What this page is").
S02 — A page with no description
wf · draws S02
page P(path: "/", title: "T") {
Heading("No description").h1
}
text
Warning [S02]: Page P has no description at src/App.wf:1:1
Add one: page Name(path: "/", title: "…", description: "A sentence a search result can show")
Fix: Add a description: of about 150 characters: what a reader gets from the page.
S03 — A description too long for a search result
wf · draws S03
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
}
text
Warning [S03]: Page P's description is 178 characters; a search result shows about 160 at src/App.wf:1:1
Shorten it, or accept that it will be cut mid-sentence
Fix: Shorten it to about 160 characters.
S04 — Two pages on one route
wf · draws S04
page A(path: "/about", title: "A", description: "D") { Heading("A").h1 }
page B(path: "/about", title: "B", description: "D") { Heading("B").h1 }
text
Warning [S04]: Pages A and B both claim the route /about at src/App.wf:2:1
Give each page its own path
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
wf · draws P01
store Cart {
persist items: [String] = [] {
version: 2
}
}
text
Warning [P01]: `items` is at version 2, and nothing brings version 1 forward at src/App.wf:2:5
A reader who last visited then loses what they had. Add `migrate 1 -> 2`
Fix: Add a step for each older version: migrate 1 -> 2 { old.map(…) }.
P02 — A migration that never runs
wf · draws P02
store Cart {
persist items: [String] = [] {
version: 1
migrate 1 -> 2 { old }
}
}
text
Warning [P02]: `migrate 1 -> 2` on `items` is past the declared version at src/App.wf:2:5
A step above `version:` never runs; raise the version or drop the step
Fix: Raise version: to the step's target, or drop the step.
P03 — A persisted value in a route-scoped store
wf · draws P03
store Filters(scope: .route) {
persist query = ""
}
text
Warning [P03]: `query` is persisted in a store scoped to the route at src/App.wf:2:5
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
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
wf · draws U01
page P(path: "/", title: "T", description: "D") {
state unused = 0
Heading("Unused").h1
}
text
Warning [U01]: `unused` is declared but never read at src/App.wf:2:5
Nothing reads the state; remove it, or name it `_unused` to keep it
Fix: Remove it, read it, or name it _unused to keep it on purpose.
U02 — A derived value nothing reads
wf · draws U02
page P(path: "/", title: "T", description: "D") {
state n = 1
derived doubled = n * 2
Heading("{n}").h1
}
text
Warning [U02]: `doubled` is declared but never read at src/App.wf:3:5
Nothing reads the derived value; remove it, or name it `_doubled` to keep it
Fix: Remove it or read it; a leading _ keeps it.
U03 — A component nothing places
wf · draws U03
component Orphan { Text("Nobody places me") }
page P(path: "/", title: "T", description: "D") {
Heading("Home").h1
}
text
Warning [U03]: `Orphan` is declared but never placed at src/App.wf:1:1
Place it in a page, name it as a layout, or remove it
Fix: Place it, name it as a layout:, publish it in build.elements, or remove it.
U04 — A store member nothing reads
wf · draws U04
store Cart {
state items = []
state coupon = ""
}
page P(path: "/", title: "T", description: "D") {
use Cart
Heading("{Cart.items.length}").h1
}
text
Warning [U04]: `Cart.coupon` is declared but never read at src/App.wf:3:5
Nothing reads the state, inside the store or as `Cart.coupon`; remove it, or name it `_coupon` to keep it
Fix: Remove the member, or name it with a leading _.
U05 — An action nothing calls
wf · draws U05
page P(path: "/", title: "T", description: "D") {
state n = 0
action reset() { n = 0 }
Heading("{n}").h1
}
text
Warning [U05]: `reset` is declared but never read at src/App.wf:3:5
Nothing reads the action; remove it, or name it `_reset` to keep it
Fix: Call it, remove it, or name it _reset.
Vocabulary
Warnings.
V01 — A bare word that means nothing
wf · draws V01
page P(path: "/", title: "T", description: "D") {
Heading("Save").h1
Button(primary)
}
text
Warning [V01]: nothing in scope declares `primary`, so it reads as nothing — a flag is written `.primary` at src/App.wf:3:12
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.
V03 — Markup put in as markup
wf · draws V03
page P(path: "/", title: "T", description: "D") {
state body = "<b>Hi</b>"
Heading("Markup").h1
Unsafe.Html(body)
}
text
Warning [V03]: `Unsafe.Html` puts markup in as markup at src/App.wf:4:5
Anything the project did not write itself belongs in `sanitize(…)` first
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.
On this page
Structural errors Warnings without a code Types Accessibility Search and sharing What is kept in the browser Declared and never read VocabularyChecked by the test suite
Every code block in the guide is parsed, checked and type-checked on each release.