Style
Conventions this repository actually follows, and the reasons, so that a change that breaks one is a decision rather than an accident.
The formatter is the authority
gleam format --check src test passes and is meant to keep passing. Nothing
here is exempt and nothing is annotated to be skipped, because gleam format
has no configuration and no escape hatch — there is no pragma that makes it
leave a region alone. A file that argues with it loses on whichever day someone
runs it.
So the code is written to want what the formatter does. Where the two would disagree, the shape of the code changes rather than the rule. Three of its behaviours are worth knowing before writing anything it will touch, because each one has already decided how something here is written.
It reflows a list of scalars to fill the width. A list of integers written as a grid comes back as a paragraph. It does not reflow a list whose elements are themselves lists — those stay one to a line, however short they are. There is no magic trailing comma; a comma at the end changes nothing, and a comment inside the list pins nothing but itself.
It strips trailing zeros from floats. 0.40 becomes 0.4. A column of
weights cannot be aligned on the decimal point and should not be attempted; see
score in difficulty.gleam, where the four coefficients are simply written
out and the comment carries what the alignment would have shown.
A nested call in the last argument explodes every argument. A constructor whose final argument is a bare list literal keeps its other arguments hugged on one line and hangs the list beneath it. Wrap that same list in a function call and all seven arguments go one per line. Data tables are therefore written with bare literals and any transformation happens at the reading end, not the writing end.
Data that has a shape keeps it
A board is a picture and is written as one. In families.gleam a packing is a
list of rows, not a flat list of cells, and every cell is a one-letter constant:
Family("classic", "CLASSIC", 4, 5, 1, 3, [
[u, g, g, u],
[u, g, g, u],
[u, r, r, u],
[u, s, s, u],
[s, e, e, s],
]),
e empty, s single, u upright, r recumbent, g goal. One letter rather
than a word because every cell has to be the same width or the rows stop lining
up, and a packing that does not line up is a list of numbers again. The values
are the shape codes domain encodes with, so this is a spelling of the wire
format and not a private one.
The nesting is what survives the formatter, and families.grid flattens at the
point of use. Flatten one of these back out to save the list.flatten and the
next gleam format turns the board into a paragraph. That is the general rule:
when a literal's layout carries meaning, give it a structure the formatter
already keeps, rather than a comment asking to be spared.
Comments say why
The code says what it does. A comment that repeats it is noise. What a comment is for is the reasoning that cannot survive in the code: what was tried, what broke, what the alternative was and why it lost.
Written declaratively and without hedging. Not "we probably want to avoid this because it might cause issues" but "activation waits for every page running the previous version to go, so nothing is still reading what is being thrown away." A reader should be able to tell a measured fact from a judgement, and neither should be apologised for.
A decision that has already gone wrong once gets the incident, briefly, because that is the part nobody reconstructs: the version was a number kept by hand and by the tenth deploy it had drifted. Numbers earn their place this way — prefer the number and what it counts to an adjective.
Gleam decides, FFI executes
Every _ffi.mjs file is named for the one platform gap it fills — 32-bit words,
font metrics, rounding, the pixel pipeline, the platform — and none of them
knows what a theme, a family or a rung is. Logic that could live in Gleam does.
An FFI file that starts making a decision is a sign the boundary moved and
should be moved back.
Names carry digests
Everything under / is fingerprinted, so a URL never holds different
bytes and two builds cannot contend for one address. Four things keep their
names on purpose and the reasons are in tools/. Anything derived —
a cache version, an asset name — is computed from content rather than
incremented by hand, because a number kept by a person is a number that
eventually disagrees with what it names.
Commit messages
A declarative one-line subject that states the idea, not the diff:
The shell was written once, and once was not enough
not fix(sw): re-cache index.html on navigate. The body is prose, wrapped, and
explains what was wrong, why it was wrong, and why the fix takes the shape it
does — the same job the comments do, at the scale of a change. Present tense.
No prefixes, no ticket numbers, no trailers.
Prose
The · middot is never used. Em dashes are, and so are numerals: a figure with
a caption beats a sentence that describes the figure.