sprig and prose docs classic docs
Reference› ADRs› ADR031 BracesInProse

ADR031 BracesInProse

Title: Braces in prose

  • Status: Proposed
  • Proposed: D2026-10-18
  • Follows: ADR030 DescribeInEdgeBlocks
  • Related: ADR016 UsingMarkdown
  • Affects: Cli, Describe, EdgeBlock, Parser, Title

Context

Every block of prose ends at a brace: a describe, a title, and an edge note if 030 is accepted. To find the right brace, the parser counts. Every { inside the text is matched against a }, and the text ends at the } that balances the one that opened it. So a describe may hold paired braces, and the five describes in this universe that show prose syntax rely on it:

syntax Relationship {
  describe {
    A relationship names how two things connect. One name is enough when
    both sides would use the same word.

    relationship isNextTo { }
  }
}

Counting has a cost, and it shows when a closing brace goes missing. The text does not stop. It reads every later block as more paired braces, and the mistake surfaces wherever the braces finally fail to balance, usually at the end of the file. In Schitt's Creek, a describe on Eric that loses its } is reported 343 lines later, by an error that does not say where the describe was.

030 met the same problem from the edge block's side. Its first draft read an edge note the way a describe is read, and a forgotten } after one note swallowed the rest of a 20-target list. Its answer was to refuse braces inside a note. That fixed notes, left describes and titles as they were, and made a note and a describe differ by a rule.

This record asks the question once, for all prose.

Decision

In prose, a brace is written twice. Inside a describe, a title or an edge note, {{ is a literal { and }} a literal }. A single } ends the text, and a single { is refused.

syntax Relationship {
  describe {
    A relationship names how two things connect. One name is enough when
    both sides would use the same word.

    relationship isNextTo {{ }}
  }
}

Prose no longer counts braces. A block of text runs to the first single }, and nothing inside it is read as structure.

The manifest holds the brace, not the escape. relationship isNextTo {{ }} lands as relationship isNextTo { }. A consumer never sees an escape, so none has to learn one. This is the one exception to 016's "Describe accepts whatever the author wrote, verbatim", and it is stated here so that it stays the only one.

A single { in prose is refused, and the message names the likely cause. A block of text that loses its } now runs into the next block's {, which is refused on that line with a message saying the text before it is probably missing its }. That refusal is the whole of the safety net, and it covers every block of prose, not only edge notes.

When an error follows an escape, it says where the escape was. }} right after text is read as an escaped brace, so S1E5 { The two lines.}} does not close the note and its list. When a later error follows a block of text that ended in }}, whether at the next { or at the end of the file, the message points at that }} and says to write } }. In its author's words: "If you silently go to the end of the file, you know where you started when it happened, so you could tell the user about it."

The end-of-file error names the declaration left open, as 030 proposes. This record needs it whether or not 030 is accepted.

Comments are unchanged. {* and *} are read before anything else, so a comment inside prose is still kept as text, as the Comment card says, and its marks are never mistaken for braces.

Today's spelling is accepted, with a warning, for one minor release. A paired brace in prose is reported as a warning, all of them in one compile, and refused after that release. The parser counts braces for that one release and never after.

sprig fix rewrites a universe to the new spelling. It doubles the braces in prose and, if 030 is accepted, takes describe out of edge blocks.

This changes describe and title, which are Stable. That is why it is a record of its own rather than part of 030, and why it ships with a warning release.

Consequences

Any prose with a brace has to change. That is a minor for the parser by 021, and the CLI takes its own entry, for the warning and for the new command. In practice it is small. Five describes in reach hold a brace, all in this universe and all prose showing prose syntax. The field guide's universe, Schitt's Creek and the syntax cards' examples hold none.

The parser gets simpler. There is no depth to count in prose, only the first single }.

Every block of prose gets the safety net. Eric's describe, which today is reported at the end of the file, is refused four lines after it opens, at relationships {. A title or an edge note that loses its } is caught the same way.

If 030 is accepted, its brace rule becomes this one. A note is prose, and refusing a brace inside a note is refusing a single { in prose. 030's "a note and a describe differ by one rule" goes away, and a note can hold a literal brace, written twice.

Spacing matters in one place. A block of text whose closing } is followed directly by another } reads as an escape, where prose is otherwise not sensitive to spacing. The error names the escape and sprig fix writes closing braces apart. Nothing in reach writes }} straight after text.

Verbatim has one exception. An author who writes {{ sees { in every consumer. That is the point, and it is also a rule a reader of the source has to know.

A doubled brace in prose changes meaning. Text that means two braces, such as a template placeholder written {{name}}, now reads as one. Nothing in reach has a doubled brace in prose.

A target list left open is still found at the end of the file. The stray { Gwen } that 030 describes is a valid note, with no brace inside it to refuse. Naming the declaration left open is what helps there.

Docs, universe and tests change together. The Describe card's "kept exactly as written" gains the escape, as does the Title card. The five describes are rewritten, syntax Describe and syntax Title say what a brace costs, and the parser test that keeps { braces } in a describe changes.

What to watch. How doubled braces read where they appear. Today that is only prose about prose syntax, which 015 says belongs in a document block once one exists. If that block arrives, most of the escapes in this universe can move there.

Alternatives considered

Keep counting braces. The status quo. Prose stays verbatim and paired braces need no escape. Rejected, because a lost } in any block of prose is found at the end of the file, and refusing braces only where that bites, as 030 does, leaves describes and titles with the same failure.

Escape with a backslash, \{ and \}. The conventional escape, and it has no ambiguity with closing braces: { The note.}} closes two blocks. Rejected for audience and for feel, in its author's words. On audience: "prose isn't a programming language, so I'm not making assumptions about my audience, which means if I deviate from traditional escaping, it's not working against as much muscle memory." On feel: "\{ and \} are ugly and sharp... if I'm being honest. Specifically the \ part. sprig and prose, the name, is soft sounding. If you read its whole URL (sprig-and-prose.io), it just rolls off the tongue. So I want it to have a certain feeling to it, and \ does not evoke that feeling." Neither is a lesser reason here. 017 already holds prose to the test of reading as prose, and 005 replaced a typeface because it was "not enjoyable to read". The ambiguity the backslash avoids is the one the error now explains.

No braces in prose at all, and no escape. The simplest rule. Rejected, because this universe documents prose in prose, and until a document block exists, describe is the only place that can go.

Refuse braces only in edge notes, as 030 does. Rejected as the general answer. It covers notes and nothing else, so describes and titles keep the end-of-file failure, and a note and a describe follow different rules.

Keep the escape in the manifest, and let consumers unescape it. Rejected. Every consumer would have to know the escape, which is the burden 015 refused to put on consumers for markdown.

Evidence

Compiled against the parser at 0.15.0, and against a prototype of this record that reads prose to the first single }, refuses a single {, names the escape when an error follows one, and names the declaration left open at the end of a file. "Counting" is today's rule: 0.15.0 for describes, and 030's first draft for bare notes.

mistake or input counting this record
Eric's describe in Schitt's Creek loses its } SP016 at the end of the file, 343 lines on, naming nothing refused at relationships {, four lines on
the S2E11 note on the cafe worker loses its } SP016 at the end of the file, 238 lines on, naming nothing refused at S3E9, the next note
Eric's appearsIn list loses its } SP016 at the end of the file, naming nothing SP016 at the end of the file, naming character Eric
S1E5 { The two lines.}}, closing a note and its list compiles SP016 at the end of the file, naming concept Gwen and the }} on line 9
describe { Text.}}, closing a describe and its concept compiles refused at the next {, naming the }} on line 6
relationship isNextTo {{ }} in a describe the text relationship isNextTo {{ }} the text relationship isNextTo { }

The fourth and fifth rows are the cost of doubling: two spellings that compile today and are refused here, each with a message naming the escape.

Every block of prose in reach was read for braces. The Sprig universe has 150 describes and titles, and five hold a brace, all showing prose syntax. The field guide's universe has 219 and none, Schitt's Creek 175 and none, and the syntax cards' examples 63 and none. No prose in reach holds {{ or }}, and no block of text anywhere in reach closes directly against another }.

Schitt's Creek, rewritten into bare notes as 030 describes, compiles under the prototype to the same manifest as today, byte for byte, because none of its prose uses a brace. The Sprig universe as it stands is refused in each of the three files that hold the five describes, one refusal per file, which is what the warning release and sprig fix are for.