`docs/style.md`. The rules were only ever in the comments of whichever file had last had to think about them, which means the reasoning survived and the rule did not: nothing told you that a packing is nested to survive the formatter until you had already flattened one. It records what is actually followed rather than what would be nice — the formatter as the authority and the three of its behaviours that have already decided how something here is written, data that keeps its shape, comments that say why rather than what, the line between Gleam and FFI, names that carry digests, and the voice a commit message is written in. The README keeps the commands and points at it for the reasons.
- commit
- 81549a9cf08bcb2d13c785adf3bf62867f35ffd3
- parent
- 619a0c3c7682e51c4f04586b984216d626ecba16
- author
- Dami <dami.etoile@gmail.com>
| M | README.md | 16 | ++++ |
| A | docs/ | 114 | ++++++++++++++++++++++++++++++ |
@@ -74,6 +74,22 @@ Nothing in `test/` needs a canvas or a DOM. `typeface_ffi.mjs` falls back to
7474 each face's own fixed advance when there is no canvas to measure with, which is7575 what makes the headless run possible at all.7676 77+```sh78+gleam format --check src test79+```80+81+Passes, and is meant to keep passing. See `docs/style.md` for what that costs82+and why the code is written to want what the formatter does.83+84+`gleam check` is the closest thing Gleam has to a linter — there is no `gleam85+lint`, and the language server is the same compiler, so it reports exactly what86+`check` prints and nothing more. The project's own modules are clean. The87+warnings you will see are all from `build/packages/`, and they are all in the88+Erlang server stack that `lustre_dev_tools` brings with it — mist, glisten,89+wisp and friends. Nothing here imports them, and they are compiled on the90+production path too, because dev dependencies are in the manifest regardless of91+target.92+7793 ## Building7894 7995 ```sh@@ -0,0 +1,114 @@
1+# Style2+3+Conventions this repository actually follows, and the reasons, so that a change4+that breaks one is a decision rather than an accident.5+6+## The formatter is the authority7+8+`gleam format --check src test` passes and is meant to keep passing. Nothing9+here is exempt and nothing is annotated to be skipped, because `gleam format`10+has no configuration and no escape hatch — there is no pragma that makes it11+leave a region alone. A file that argues with it loses on whichever day someone12+runs it.13+14+So the code is written to want what the formatter does. Where the two would15+disagree, the shape of the code changes rather than the rule. Three of its16+behaviours are worth knowing before writing anything it will touch, because17+each one has already decided how something here is written.18+19+**It reflows a list of scalars to fill the width.** A list of integers written20+as a grid comes back as a paragraph. It does *not* reflow a list whose elements21+are themselves lists — those stay one to a line, however short they are. There22+is no magic trailing comma; a comma at the end changes nothing, and a comment23+inside the list pins nothing but itself.24+25+**It strips trailing zeros from floats.** `0.40` becomes `0.4`. A column of26+weights cannot be aligned on the decimal point and should not be attempted; see27+`score` in `difficulty.gleam`, where the four coefficients are simply written28+out and the comment carries what the alignment would have shown.29+30+**A nested call in the last argument explodes every argument.** A constructor31+whose final argument is a bare list literal keeps its other arguments hugged on32+one line and hangs the list beneath it. Wrap that same list in a function call33+and all seven arguments go one per line. Data tables are therefore written with34+bare literals and any transformation happens at the reading end, not the35+writing end.36+37+## Data that has a shape keeps it38+39+A board is a picture and is written as one. In `families.gleam` a packing is a40+list of rows, not a flat list of cells, and every cell is a one-letter constant:41+42+```gleam43+Family("classic", "CLASSIC", 4, 5, 1, 3, [44+ [u, g, g, u],45+ [u, g, g, u],46+ [u, r, r, u],47+ [u, s, s, u],48+ [s, e, e, s],49+]),50+```51+52+`e` empty, `s` single, `u` upright, `r` recumbent, `g` goal. One letter rather53+than a word because every cell has to be the same width or the rows stop lining54+up, and a packing that does not line up is a list of numbers again. The values55+are the shape codes `domain` encodes with, so this is a spelling of the wire56+format and not a private one.57+58+The nesting is what survives the formatter, and `families.grid` flattens at the59+point of use. Flatten one of these back out to save the `list.flatten` and the60+next `gleam format` turns the board into a paragraph. That is the general rule:61+**when a literal's layout carries meaning, give it a structure the formatter62+already keeps, rather than a comment asking to be spared.**63+64+## Comments say why65+66+The code says what it does. A comment that repeats it is noise. What a comment67+is for is the reasoning that cannot survive in the code: what was tried, what68+broke, what the alternative was and why it lost.69+70+Written declaratively and without hedging. Not "we probably want to avoid this71+because it might cause issues" but "activation waits for every page running the72+previous version to go, so nothing is still reading what is being thrown away."73+A reader should be able to tell a measured fact from a judgement, and neither74+should be apologised for.75+76+A decision that has already gone wrong once gets the incident, briefly, because77+that is the part nobody reconstructs: the version was a number kept by hand and78+by the tenth deploy it had drifted. Numbers earn their place this way — prefer79+the number and what it counts to an adjective.80+81+## Gleam decides, FFI executes82+83+Every `_ffi.mjs` file is named for the one platform gap it fills — 32-bit words,84+font metrics, rounding, the pixel pipeline, the platform — and none of them85+knows what a theme, a family or a rung is. Logic that could live in Gleam does.86+An FFI file that starts making a decision is a sign the boundary moved and87+should be moved back.88+89+## Names carry digests90+91+Everything under `/assets/` is fingerprinted, so a URL never holds different92+bytes and two builds cannot contend for one address. Four things keep their93+names on purpose and the reasons are in `tools/bundle.mjs`. Anything derived —94+a cache version, an asset name — is computed from content rather than95+incremented by hand, because a number kept by a person is a number that96+eventually disagrees with what it names.97+98+## Commit messages99+100+A declarative one-line subject that states the idea, not the diff:101+102+```103+The shell was written once, and once was not enough104+```105+106+not `fix(sw): re-cache index.html on navigate`. The body is prose, wrapped, and107+explains what was wrong, why it was wrong, and why the fix takes the shape it108+does — the same job the comments do, at the scale of a change. Present tense.109+No prefixes, no ticket numbers, no trailers.110+111+## Prose112+113+The `·` middot is never used. Em dashes are, and so are numerals: a figure with114+a caption beats a sentence that describes the figure.