Wit

Gotchas & anti-patterns

The page to read when you feel like you are fighting the parser. Most of the time Wit gets out of your way — natural prose that resembles syntax stays prose. But a handful of constructs do mis-fire, a few are outright bugs to route around, and the last section is the do/avoid checklist. Each item is re-verified against the current build.

Prose that safely looks like syntax

These all stay prose — the parser is careful not to fire by accident:

  • Em-dash is not the parameter separator. The separator is the exact three bytes space-hyphen-space; an em-dash and numeric ranges stay prose: The key — value pair, 1995 — 2010. (em-dash-vs-hyphen).
  • A leading greater-than is not a blockquote. A line beginning > is ordinary text (blockquote-leader).
  • A mid-sentence "digit-period-space" is not an ordered list.It happened in 1970. It was quiet. stays one paragraph (year-period-space).
  • Digit-flanked asterisks are arithmetic, not emphasis.5*6*7 and 2*3*4*5 stay literal (mid-line-arithmetic).
  • A home path is not a comment. ~/Documents/notes.txt in prose is text; a line comment needs a tilde then a space(tilde-home-path).
  • An apostrophe after italic is safe. word'scloses the italic and keeps the 's (apostrophe-after-italic).
  • Nesting has no depth cap (deep-nesting), and multiple blank lines collapse to a single paragraph break (multi-blank-line).

The one that surprises everyone: an email in prose

An @ that follows a word character is not read as opening a node — but word@ (a word pressed against a following @) is read as a node close. So an email address written plainly in prose splits the paragraph and drops the local part:

source
Contact keeper@example.org for archive access.
result

Contact

example.org for archive access.

That renders as two paragraphs — Contact and example.org for archive access. — because keeper@ was consumed as a close token. To show an address literally, wrap it in frozen inline code.

The colon-scatter false positive

A space-free token buried in real prose still lifts as a parameter:

@thing the variable name:Tauraj is bad thing@
name

is lifted even though you meant it as prose. Fix by escaping the colon (name:) or adding a space (name: …). The same bites URLs, ratios, and clock times written inside a scatter body.

The record comma trap

An unquoted comma inside a record value splits the field — E_MALFORMED_RECORD:

#cite: { author - Boud, D., year - 2024 }    ~ splits into a keyless field
#cite: { author: "Boud, D.", year: 2024 }    ~ quote to keep the comma

Glued statements are not recognised

A (if …), (each …), or (end) is recognised only when its ( follows whitespace or a line start. Glue one to a preceding ) and it renders literally:

(each @xs as x)(if @x is a)Y(end)(end)      ~ inner (if …) prints as text
(each @xs as x) (if @x is a) Y (end) (end)  ~ works

Keep a space or newline between adjacent statements.

The greedy-bind and mixing traps

A bodyless pipes call greedily swallows forward to the next matching close — E_UNCLOSED_NODE. Give it an explicit name@, a blank line after it, or place it last. And record-arg is exclusive: combining { … } with parens or pipes collected before it is E_MIXED_PARAM_SOURCE. Both are covered in Parameters.

A comment as a container's first line

A ~comment on the first line of a container body, with content right after, collapses the following blocks into one paragraph:

source
@section
~ note
@h2 T h2@
Body
section@
result

T

Body

Emits the heading wrongly nested in a paragraph — <section><p><h2>T</h2>Body</p></section>. Fix: a blank line after the comment → <section><h2>T</h2><p>Body</p></section> (see Comments; block comments close with the exact ~~/ fence).

Multi-word key access via spaces fails

A data-access segment ends at a space, so a multi-word key written with spaces does not resolve — @keeper.years at post reads only @keeper.years. Reach multi-word keys through the space-free camel or snake spelling (see Data access).

Index access renders unresolved

@list.0 and @rec.0.field currently emit <span class="wit-unresolved"> rather than the element. Fix: reach into collections with (each) or @table |rows @ref|.

The loop variable is a reference not a capture

Inside (each @c as item), reference the element with @itemnot ::item::, which is capture interpolation and stays unresolved in a loop.

A single-line def with captures

A single-line #def that declares ||captures|| renders the literal capture list. Fix: use the block form for any def with captures.

Form-fill needs two or more lines

A bare opener with a single key: value line is read as prose, not a form-fill call. Fix: add a second field, or use the record-arg form. A key: with an empty same-line value consumes the block beneath; continuation must be strictly deeper indented.

The multi-line-call bugs and two cosmetic quirks

  • A parens call across a newline breaks — the remainder leaks into the text . Keep parens calls on one line.
  • A newline inside a single |…| pipe value does not span lines — E_UNCLOSED_NODE (06-parameters-pipes/multi-line-value). Use a body form for multi-line values.
  • Following a template that has no body slot with body content silently drops that body (05-nodes-parens/parens-then-body). Pass bodies with pipes.
  • Cosmetic: @figure can wrap a block @figcaption in a <p>, and a frozen @@table will not resolve @ref rows — use a plain @table when rows read from data.

Delimiter and param traps

Delimiter and param traps
TrapWhat happensFix
colon delimiter in a data-def recordLSP rejects it as E_MALFORMED_RECORDuse the hyphen delimiter
multi-word colon value in parensbreaks the callquote it or use space style
a dotted path inside parens or record valuesconfuses close detectionuse pipes or form-fill for path values
an at-sign inside record values like an emailbreaks parsingkeep at-strings out of record values

Anti-patterns

Do / avoid
DoAvoid
content in the body — metadata in paramsstuffing content into params
lift duplicated lists into data plus eachhand-repeating list items
wrap opaque nodes in named defsscattering raw node type X calls
name citations by ideanumbering citations by hand
hyphen delimiter in data-defscolon delimiter in data-defs
conditionals and each over scriptsreaching for scripts first
place pipes-form calls lastleaving an open pipes call before a body use
schema-array tablesinline CSV for structured data

The fixes, at a glance

  • Email or word@ in prose → wrap it in frozen inline code.
  • Colon false positive → : or a space.
  • Record comma → quote the value.
  • Leading comment in a container → blank line after it.
  • Bodyless pipes → explicit name@, blank line, or last.
  • Glued statements → keep a space.
  • Spaced multi-word access → camel or snake spelling.
  • Index access → (each) or a data-driven @table.
  • Loop element → @item, not ::item::.
  • Def with captures → block form.
  • Single-line form-fill → add a field or use record-arg.
  • Data in prose → @field, not {{…}}.

See also

Parameters

· Colon-scatter · Escapes · Data access · Conditionals · Iteration · Errors