02 · Reviewable change

Will agent edits stay clean, or reformat my whole file?

A one-line change is a one-line diff, every time. Glyph has a single canonical layout, so an agent can't bury a real change under a wall of reformatting.

Why it matters

When an agent opens a pull request, the diff is the review. If a genuine one-line fix arrives as a 200-line reformat, nobody can see what actually changed, and that noise is exactly where a bug slips through. Diff stability means the diff shows the change and nothing but the change.

Two rules do most of the work. Here's each, shown as the diff you'd review.

Example 1: trailing commas, one item per line

Add a route. In TypeScript's common style the previous last line has to churn just to grow a comma. Two lines move for a one-item change:

TypeScript: add "/billing"
 const routes = [
   "/home",
-  "/about"
+  "/about",
+  "/billing"
 ]
3 lines moved for 1 change
Glyph: add "/billing"
 let routes = [
   "/home",
   "/about",
+  "/billing",
 ]
1 line added, exactly the change

Trailing commas are required, and a list that doesn't fit on one line goes one element per line rather than being repacked, so inserting, removing, or reordering an entry never disturbs its neighbors. The same holds for record fields, parameters, match arms, and imports. The threshold has a cost: a rename that pushes a call past 100 columns expands the whole argument list to one per line.

Example 2: one canonical layout

There is exactly one correct formatting, produced by glyph fmt. Whitespace, wrapping, and alignment aren't choices an agent can “tidy,” so a semantic edit can't drag reflow along with it. Change one field's type, and that's the whole diff:

user.glyph: change one field, review one line
 type User = {
   id: string,
-  age: string,
+  age: number,
   email: string,
 }

Because the layout is fixed, two different agents editing the same file produce byte-identical formatting, no reflow churn, no “my formatter vs. yours” noise, and a clean git blame that points at the commit that actually changed a line, not the one that reformatted it.

One canonical layout, so does the formatter move my comments?

It used to, and that was a bug worth naming. Glyph has no block comments and no doc-comment syntax, so a // line is the only way to explain a record field, a union variant, or a match arm. The formatter flushed pending comments only at declaration and statement boundaries, which meant a comment written inside a construct drifted down to the next declaration and read as documentation for whatever it landed on. Writing Minesweeper in Glyph is what surfaced it. These two declarations come from the real app (comments trimmed to fit), run through the old formatter and the new one.

0.1.37: one fmt pass
type Visibility =
  | Hidden
  | Flagged
  | Revealed

// Face down. Prints as `.`.
// Face down and marked by the player.
// Face up. `Cell.adjacent` is what gets printed.
type Cell = { mine: bool, adjacent: int, visibility: Visibility }
// Number of mined neighbours. Only meaningful
// once mines are placed.
three variant comments now document a record
0.1.38: the same file
type Visibility =
  // Face down. Prints as `.`.
  | Hidden
  // Face down and marked by the player.
  | Flagged
  // Face up. `Cell.adjacent` is what gets printed.
  | Revealed

type Cell = {
  mine: bool,
  // Number of mined neighbours. Only meaningful
  // once mines are placed.
  adjacent: int,
  visibility: Visibility,
}
every comment on what it describes

The rule now: a comment is re-emitted above the item it was written above, inside a record body, a union variant list, an array or object literal, an argument or parameter list, and a match. And a construct holding an interior comment always takes the one-element-per-line form, at any size, so the comment has an item to sit above. So type Shape = { w: int, h: int } still collapses to one line, and the moment you document h the record stays expanded.

Why this counts as a correctness bug and not a cosmetic one: nothing failed. The build exited 0, tsc passed, and the mangled output was itself a fixed point, so glyph fmt --check in CI went green on a file whose comments now said something false. That is the same class of failure Glyph exists to remove, just in the formatter rather than the type system.

Can glyph fmt break a program that builds?

It could, in exactly one shape, and that is fixed. An arm meaning “the empty record” is written true => ({}). Arm-body position is the only place in the grammar where a leading { is ambiguous, and the parser settles it by requiring key: or ... right after the brace. An empty object has neither, so when the formatter dropped the parentheses and reprinted true => {}, the arm came back as an empty block and the file stopped building: E0223, the arm produces no value. You ran a formatter on working code and got code that no longer compiled.

The printer now parenthesizes an arm body whose leftmost token is a { that would reparse as a block, which is the empty object and anything built on it (({}).size comes back as ({}.size)). An arm body that already reads unambiguously, like false => { a: "x" }, stays bare, so there is no new punctuation to read anywhere else. fmt is a fixed point on the result and the emitted TypeScript is unchanged. A formatter your program has to survive is worse than a missing feature, because it damages code that already worked.

Where it stands

Shipping today

Required trailing commas, one-item-per-line lists, and glyph fmt's single canonical layout, round-tripping and idempotent, with format-on-save in the editor. Comments are kept above the item they were written above, including inside a record, a literal, an argument list, and a match.