sprig and prose docs classic docs
Reference› ADRs› ADR030 DescribeInEdgeBlocks

ADR030 DescribeInEdgeBlocks

Title: Describe in edge blocks

Context

029 let a target in a relationships block carry a block of its own, holding facts about that one connection from the end that wrote it. It shipped in 0.14.0, Experimental, and the block holds a describe and nothing else.

event GwenSpeaks {
  relationships {
    happensIn {
      S1E5 {
        describe {
          The two lines Gwen delivers in this episode.
        }
      }
    }
  }
}

The describe was kept for room. 029 deferred relationships inside an edge block rather than refusing them, because an edge that carries connections is a subject and wants a record of its own, and it kept the block a block so that a later record could add one more kind without breaking anything: "an edge block is one block that can later accept one more kind."

Modelling since has pointed the other way. The two wants on record for structure on an edge are where one of Gwen's scenes happened (029) and a pairing that held as of an episode, appearsIn { S2E11 } (024, on the validity axis of 018). Both already have an answer that is not edge structure. The authoring guidance, State or events, asks whether a fact needs to be "pointed at, ordered or connected", and gives it a name if it does; "if all it needs is a sentence saying why it is true, write the sentence on the edge." Schitt's Creek, where both wants were found, has since given Gwen's two scenes names of their own, and nothing in it asks for structure on an edge (see Evidence). This record does not decide that edges will never carry structure, and leaves the way open below, but the shift is why the keyword is in question now.

If the block only ever holds a sentence, describe inside it qualifies nothing. It is the one thing the block can hold, so to a reader who knows the rule it says nothing the brace has not already said.

The habit agrees. One of the mistakes its author makes most often with edge blocks is leaving the describe out, because by the time the block is open, the edge is the thing being described. That is a reason rather than the reason, but it is evidence about how the block is read, and the parser at 0.15.0 answers it with the wrong advice:

[SP031] "The" cannot go in the block after "S1E5", which holds only a describe. If "S1E5" was meant to start a new connection, the "happensIn" block before it is missing its "}".

Nothing is missing a brace. The hint was written for the mistake 029 expected, a stray name after an unclosed block, and it also fires on the one its author actually makes.

The keyword does still do one job. Everywhere in prose, a brace opens text only after describe or title, so a reader, or a tool, can tell text from structure without knowing where it is. That job is the cost of this record, and most of what follows is about it.

Decision

The block after a target holds that connection's prose directly.

event GwenSpeaks {
  relationships {
    happensIn {
      S1E5 {
        The two lines Gwen delivers in this episode.
      }
    }
  }
}

The text is captured as a describe's is: taken from between the braces, trimmed and dedented. It lands where an edge note lands today, as the optional describe on the entry of the end that wrote it. The manifest does not change, and nothing that reads one has to.

Everything else 029 decided stands. The note stays where it was written, and the mirror carries none. Each end may write its own. Where a note is shown is a consumer's rule. An empty block adds nothing. A target written twice is still SP030, an edge block still works in every relationships block, and it stays Experimental.

A note may not contain a brace. A { anywhere inside an edge block is refused, unless it opens a comment, and a } always ends the block. That one rule catches today's form, S1E5 { describe { … } }, a misspelled describe, a block the edge does not take, such as relationships, a note with a block after it, and a note whose own } was forgotten, because the next target's brace then lands inside it. Read as text instead, every edge block written since 0.14 would keep compiling with describe { inside its note, a change of meaning nothing would report. Refusing a block is also what keeps 029's deferral additive. The refusal is a new error, BraceInNoteError, taking the next free code when this ships. Its message names the likely cause: for describe, remove describe { and the brace that closes it; after any other name, the note before that name is probably missing its }.

A describe may hold braces as long as they pair up, and a note may not hold them at all. A note is a sentence about one connection, and inside one a brace is far more often a missing } than writing. Refusing it is what puts that mistake on the next line instead of at the end of the file.

Today's form is accepted, with a warning, for one minor release. Every describe inside an edge block is reported as a warning, all of them in one compile, and the form is refused after that release, the way the in clause was reported as SP022 before it was removed. A universe migrates at its author's pace rather than one error per compile.

SP031 retires. A name inside an edge block is text now, so the condition it reports cannot occur. By 023 its code is not repointed at the new refusal. It becomes a DeprecatedError, deprecated by this record, with a description of what it used to mean. SP003 and SP005 can no longer be caused by an edge block either.

SP007 takes over SP031's hint. When a target does not resolve and its name is a relationship word in reach, the message says the block before it is probably missing its }.

The end-of-file error names the declaration left open. When a file ends inside one, SP016 says which declaration it was and the line it opened on. Nothing about that is specific to edge blocks, but this record is what makes it necessary (see Consequences).

A comment inside an edge block is text, as it is inside a describe or a title.

This record does not decide whether an edge will carry structure. 029's deferral stands as written. What becomes of it belongs to a later record, and refusing braces inside a note is what keeps that record free to say yes.

Consequences

Every edge block written so far has to change. Today's form compiles with a warning for one release and is refused after it. That is a minor for the parser by 021, and the CLI takes its own entry, as it did for 024, because it prints the warning and then the error. The change is mechanical: rewritten by a script, Schitt's Creek's 50 edge blocks compiled to the same manifest byte for byte. No universe in this repository has an edge block yet.

The manifest does not change. The field guide, the docs projector and any other consumer have nothing to do. This is the cheapest part of the record: it changes how a note is written, not what a note is.

Text stops being marked by a keyword. Today a brace opens text only after describe or title. After this, a brace after a name opens text at the third level of a relationships block and structure everywhere else, so { Pond } is a target one level up and a note one level down. Anything that highlights or formats prose has to track the nesting of a relationships block to know which. In fairness, a relationships block is already read by position, because word { targets } holds names rather than declarations, so this extends a special case that exists rather than inventing one. It is still the largest cost here, and the one that cannot be engineered away; it can only be argued to be contained.

A missing brace reads differently, again. 029 said its hint "works only because the edge block is parsed narrowly". This record gives up that narrowness, and what replaces it depends on which brace is missing.

A note's own } is usually the easy case. The next brace, most often the next noted target's, lands inside the note and is refused on that line, with a message naming the note. That is close to where SP031 reports the same mistake today, and the message says more. When the note is the last one in its list, no brace comes before the list closes, and the mistake becomes the hard case.

A target list's } is the hard case. With appearsIn left open before isNiblingOf { Gwen }, isNiblingOf becomes one more target and Gwen its note. Nothing about that is wrong yet, so the parser reads on, the braces never balance, and the file ends inside the concept. The end-of-file error names the concept, which narrows the search to one declaration but not to a line. Today SP031 names the line. That is the cost of this record that no rule here removes.

The SP007 hint helps only when the braces still balance, which happens when the next word was written inside the open block rather than after it. Then the word becomes a target, fails to resolve, and SP007 says the block before it is missing its }. Even that cannot help when the word is also a name in reach, which the language allows: 025's evidence compiles dimension neighbors beside relationship neighbors. There the target resolves and nothing is reported. No universe in reach has such a name, Schitt's Creek included.

Leaving the describe out stops being a mistake. It becomes how the block is written, rather than an error with a better explanation.

A note and a describe differ by one rule. A describe may hold paired braces, and a note may not hold any. Nothing in reach writes a brace in a note, but a note that wants one has no way to write it.

Comments inside an edge block become text. Today a {* … *} beside the describe is a comment, seen by the next editor of the file. After this it is part of the note, seen by every reader, and the Comment card's "inside a describe or a title" gains a third case. Nothing in reach has one.

Edge blocks stop matching side blocks. 029 called the edge block "the side-block arrangement one level finer." A side block holds a describe and relationships, because a side carries both. An edge block would look exactly like one and hold something else. That is arguably the more honest arrangement, since the two would hold different things, but they would no longer look it.

The way to structure stays open, at a price. Because a brace inside a note is refused, a later record could admit relationships in an edge block without changing the meaning of anything that compiles. It would pay in spelling: a note alone would be bare, and a note beside structure would go back into a describe, so one note would have two forms depending on its neighbours.

Expects loses a sentence. In 011's keyword design, written "in the syntax a member would use", describe { } asks for a describe. An expected edge note would be S1E5 { }, which is also how a member writes no note, so "this edge should carry a note" has no spelling there. 011's sketch of expects as an ordinary relationship is unaffected; a note on one of its targets is a note like any other.

Docs, universe and tests change together. The change reaches the Relationships card's Each edge, separately and its example, the Describe card's paragraph on edges, the Comment card, syntax EdgeBlock's describe and the errors it can cause, and SP031's entry in universe/errors.prose. State or events needs nothing, since "in a block after its target" stays true. In the parser, most of edge-blocks.test.js changes, and nothing else does.

Publish in step with the docs. The docs compile card examples with the parser they install from npm, and the new form fails against a parser without this change, so the Relationships card can move only once the docs install a parser that has it. Today's form keeps compiling, with a warning, through that release, which gives the card the release to move in.

What to watch. Whether a real note ever needs a brace. Whether notes keep saying what structure already holds: seven of Schitt's Creek's appearance notes place a scene among the others, as the "earliest known", the "second time", "again" or the "only episode", and the episodes' follows edges could already work out most of that. It is 029's watch item arriving as order rather than as dates. And whether describe keeps appearing out of habit after the warning release, from authors or from anything that generates prose.

Alternatives considered

Keep describe. The status quo, and the strongest alternative. The keyword is redundant, and redundancy is what makes mistakes detectable: it is why SP031 could promise to catch an unclosed block, why text is recognisable without context, why edge blocks match side blocks, and why 029's deferral costs nothing to keep open. Its costs fall only on the notes that get written, and its benefits reach every relationships block. That trade looked better before Schitt's Creek was counted. There a sixth of the written edges carry a note, and the notes hold half of the universe's describe text, so the cost is paid constantly. This record proposes against it because the mistake its author reports making is the one the redundancy causes, and because refusing braces inside a note recovers most of what the redundancy catches. A forgotten } after a note is still found at the next brace. Only a target list left open, or the last note in a list left open, is found later than today, at the end of the file. If those cases bite in practice, this is the better outcome.

Keep describe and fix SP031's hint. An omitted describe would get advice about the describe instead of about a brace. The two can mostly be told apart: a stray target list is names and dots, while a sentence usually has punctuation or ends in a full stop no path can end in, and the validator knows whether the name before the block is a relationship word. Not proposed as the answer, because it explains the mistake better instead of removing it. Worth doing regardless if this record is rejected.

Check only the opening of a note. The first draft of this record: refuse a name and a brace at the start of a note, and read the rest as text, the way a describe is read. Rejected once Schitt's Creek was counted. There, a forgotten } after one of the cafe worker's notes let that note swallow the rest of a 20-target list, and the file failed at its end, 238 lines from the cause, naming nothing. A note followed by a block became text without a word.

Escape braces in all prose, proposed separately as 031. A brace in any describe, title or note would be written twice and a single { refused everywhere, so a note would follow the same rule as every other block of prose. If 031 is accepted, this record's refusal of a brace in a note becomes a case of it, and the rule that sets a note apart from a describe goes away.

Accept both. Bare prose as a shorthand, with describe still allowed. Rejected as a lasting arrangement. Two spellings for one fact is the inconsistency this record is trying to remove, a reader would meet both in one universe, and it builds the two-form arrangement described under Consequences before anything needs it. The warning release is the same arrangement for one release, and only so a universe can migrate.

Read everything in the block as text. No refusal at all, and the smallest grammar. Rejected. Today's form would keep compiling with describe { … } inside its note and no diagnostic, a silent change of meaning. And relationships { … } would become text, so a later record admitting structure would change the meaning of universes that compile. This version closes the way 029 kept open.

Refuse today's form at once, with no warning release. Rejected. The parser stops at the first error in a file, so a file with many edge blocks would take one compile per block to migrate by hand. Schitt's Creek has 24 in characters.prose.

Mark the note some other way, such as a colon after the target, or quotes around the note. Rejected. Prose is not sensitive to lines, so only a brace can say where a note ends, and quotes are the shape of a literal, which 014 refused and title shed.

Reject structure in an edge block first, and decide the syntax after. The two questions are separable: refusing relationships for good would remove the room describe was kept for, without touching the syntax. Schitt's Creek is evidence for doing it. Not done here, because its author is not ready to close that way, and refusing braces inside a note lets the syntax be decided without closing it.

Evidence

Compiled against the parser at 0.15.0, and against a prototype of this record that refuses a brace inside a note and names the declaration left open at the end of a file. The prototype used a placeholder code for the refusal, named the open declaration only for concepts, which is all these inputs needed, and refused today's form outright, where this record would warn for a release. Where 0.15.0 needs a describe for the comparison, the input was written with one.

input 0.15.0 this record
S1E5 { The two lines… } SP031, with the missing-brace hint the note The two lines…
S1E5 { describe { The two lines… } } the note The two lines… refused at describe, after the warning release
S1E5 { descibe { The lines. } } SP031, without the hint refused at descibe
sitsOn { Bench, with isNextTo { Pond } written inside the open block SP031, with the missing-brace hint [SP007] Target "isNextTo" was not found.
the same, with dimension neighbors, relationship neighbors and neighbors { Pond } SP031, with the missing-brace hint 0 errors: sitsOn to neighbors, with the note Pond
{* check against the episode *} before the note dropped, as a comment kept at the start of the note
a note, then relationships { isNextTo { Pond } } SP005 refused at relationships
relationships { isNextTo { Pond } } alone SP005 refused at relationships
S1E5 { # } SP029 the note #

A prototype with no refusal at all compiled today's form into the note describe { The two lines Gwen delivers in this episode. }, with no diagnostic. That row is the reason the form is refused rather than read.

The same kinds of mistake, made in Schitt's Creek's characters.prose. The cafe worker's appearsIn holds 20 targets, 15 of them with notes, and the note on S2E11 is the fourth. Eric's appearsIn is followed by isNiblingOf { Gwen }. "First draft" is the opening check under Alternatives considered, and line numbers in its column and this record's are in the rewritten file.

mistake 0.15.0 first draft this record
the } after the S2E11 note forgotten SP031 at S3E9, the next target SP016 at the end of the file, 238 lines on, naming nothing refused at S3E9: If "S3E9" was meant to be the next target, the note after "S2E11" is missing its "}".
the } after the S2E7 note forgotten, two bare targets before the next note SP031 at S2E10, the next target SP016 at the end of the file, naming nothing refused at S2E11, the next note's brace
the } after Eric's S2E2 note forgotten, the last note in its list SP031 at S4E3, the next target SP016 at the end of the file, naming nothing SP016 at the end of the file: "character Eric", opened on line 219, is never closed.
the } closing Eric's appearsIn forgotten SP031 at Gwen, two lines on, naming the appearsIn block SP016 at the end of the file, naming nothing SP016 at the end of the file: "character Eric", opened on line 219, is never closed.
the describe on the S2E11 note left out SP031 at The, with the missing-brace hint the note the note

In 0.15.0, forgetting either of the note's two braces gave the same result.

Against the prototype, 15 of the 19 tests in edge-blocks.test.js failed and every other test passed. The 33 canonical fixtures, compiled directly rather than through the CLI harness, gave identical manifests and diagnostics, and so did both universes in this repository.

Every universe in this repository was compiled and its edges counted. The Sprig universe has 664 edge entries and no edge blocks; the five notes 029 said could move onto their edges are still in relates blocks. The field guide's universe has 550 and none, and the examples that compile have none.

Schitt's Creek, as its author shared it for this record, has 173 concepts and 314 written edges. Fifty of the edges carry a note, a sixth of them: 26 of 61 appearsIn, 19 of 102 involves, two of four portrays, and one each on mentionedIn, isEvidencedBy and maybeChildOf. The notes run from 9 to 67 words, with a median of 22. Thirty-two are a single sentence, 14 are two, four are longer, and two run to a second paragraph. Together they hold 1,283 words, against 1,281 in every other describe in the universe, so half of its prose sits on edges.

Rewritten by a script into the bare form, the universe is 100 lines shorter, 1,866 against 1,966, and compiles under the prototype to the same manifest byte for byte. Compiled as it stands, the prototype reports six refusals, one per file, because the parser stops at the first error in a file; characters.prose alone holds 24 of the 50 blocks. None of the notes contains a brace, no relationship word is also a concept or dimension name, 47 words against 181 names, and the universe has no comments.

Twelve of the notes point back at their target from inside the block: "In this episode…", "This is the only episode in which you see the motel in winter weather", "This is where the scene plays out from start to finish". The edge is what they describe.

Nothing in it asks for structure on an edge. Gwen's two scenes, 029's example of relationships in an edge block, are two named events evidencing a theme, GwenSpeaksInCabin and GwenSpeaksAtPokerNight, each with a describe holding her lines. An uncertain parent is a word of its own, maybeChildOf, with the doubt in its note. The notes that narrate more than one connection, such as the family portrait going into the trash in S3E12, read as events not yet named, which State or events already covers.

In the other universes in reach, no relationship word is a concept or dimension name either: 31 words against 179 names in the Sprig universe, and 20 against 179 in the field guide's. So the silent case under Consequences exists in the grammar and nowhere yet in practice.

029's survey of the bakeoff kit found that about a quarter of one author's relates blocks were notes on a single edge, counted once, as one author's habit.

The habit is its author's own report: leaving out the describe is one of the mistakes they make most often with edge blocks, because the edge is already the thing being described.