Cheatsheet

For someone who has already read the documentation and wants the shapes back. No teaching.

Commands

php -S localhost:8000                       # serve the root; the page needs a server
node --test                                 # the whole suite, no path and no glob
node tools/build-manifest.mjs               # rewrite cheatsheets.json
node tools/build-manifest.mjs --check       # the same walk as a gate
node api/napkin-sketch/VENDOR-CHECK.mjs     # hash the vendored copy against its record
node tools/cheatsheet.mjs --list            # what there is
node tools/cheatsheet.mjs --generate <slug> # compose and render one
node tools/cheatsheet-site.mjs <site>       # read a site, write cheatsheets out of it

tools/cheatsheet.mjs

--generate <slug>      compose by manifest slug or folder path
--document <path>      compose a JSON document instead of a catalog slug
--check [<slug>]       check without rendering
--list                 name what there is
--format <fmt>         svg (default), png, webp, jpg
--renderer <r>         auto (default), browser, builtin
--theme <name|path>    default, or a JSON theme, whole or partial
--layout <name|path>   sheet, or a JSON layout
--seed <n>             override the document's seed; identical seeds render identically
--scale <n>            device pixels per composition pixel (default 0.3889)
--quality <n>          encoder quality for png, webp, jpg (default 80)
--background <color>   a CSS color, or none for transparent
--max-bytes <n>        the weight a publishable render stays under (default 307200)
--out <path>           a file, a directory, or - for standard output
--data-only            emit the document and the composition's choices, render nothing
--dry-run              report what would be written, touch nothing
--json                 machine readable, naming which renderer answered

A publishable render goes beside its sheet.json; one that is not goes to .tmp/. A --document render has no catalog folder, so it goes to .tmp/ either way.

tools/cheatsheet-site.mjs

<site>        the site root to crawl (required)
--out <dir>   write here instead of resolving cheatsheets/ under the site
--dry-run     report the folder and the pages, touch nothing

Markers:

<div data-cheatsheet>              title, with the file name behind it
<div data-cheatsheet="title">      head from the page's own <title>
<div data-cheatsheet="page">       head from the file name, read as prose
<!-- {% cheatsheet: [heading: page, group: page, name: splice] %} -->
PropertyTakesMeans
headingtitle, pagewhere the heading is read from
groupfolder, pagejoin the folder's cheatsheet, or be one
namea file namewhat the cheatsheet is written as

The comment applies to the element that follows it, with nothing but whitespace between. Where a comment and an attribute both speak about one heading, the comment wins and the disagreement is reported.

Menus, written into the page rather than read out of it:

<div data-cheatsheet-menu="page"></div>       link to all-cheatsheets.html
<div data-cheatsheet-menu="dropdown"></div>   an entry per generated page

Every cheatsheet is also written as all-cheatsheets.html in the generated folder. A link is relative to the page the menu is in; an entry's text is the generated file name read as prose. Anything already between the tags that this tool did not write itself is refused rather than written over - a fill is wrapped in <!-- cheatsheet-site:menu -->, which is how a run recognises its own work - and a value naming neither menu is reported rather than defaulted.

A generated page carries a render of the cheatsheet's own document and the material it was read from, so the page and a media file of the same sheet are two renders of one composition.

api/cheatsheet/site.mjs

The browser entry point: the same two markings read off a loaded document with ordinary DOM methods, for a site that will not run a build step.

import { cheatsheetSection } from "./api/cheatsheet/index.mjs";

const { element, heading, findings } = cheatsheetSection(document);
if (element) document.body.append(element);
ExportAnswers
markedElementsthe outermost marked elements, in document order
linesOfthe rows one element reads as, with a role each
headingOfthe heading a page asked for, and what to say about the asking
collectFromDocumentall of it as data, with the sheet document
cheatsheetSectionthe section element, built and handed back

It finds a marker a script wrote at run time, which a file scan cannot. It fills no menu, because nothing here writes a folder for one to list.

The three models

ModelCarries
documenta title and ordered sections
themepalette roles, type stack and scale, spacing step, curve and shape tokens, stroke weights
layouta named composition, its grid, and the slots a section may occupy

A section asks for a kind of slot - entries or listing - never for one by name. A layout keeps one slot per kind; naming none or keeping two is refused by path.

A section carries entries (term, signature, description) or lines (a worked listing, with a syntax role per line and meaningful indentation).

The compact document shape is a title and one flat code array, which is exactly a formal document of one untitled listing:

{
  "title": "Sample Sheet",
  "subtitle": "What this sheet is for",
  "kind": "read",
  "language": "javascript",
  "seed": "sample-sheet",
  "code": [
    { "text": "const items = [\"one\", \"two\", \"three\"];", "role": "identifier" },
    { "text": "console.log(items.length); // 3" }
  ]
}

A theme may be partial: laid over the recorded theme, then validated, then held to being readable before anything is drawn.

Rendering

const render = await renderSheet(doc, { format: "webp", route: "browser" });
render.data        // the bytes, on every format
render.route       // which renderer answered
render.publishable // whether it draws the face the design language measured

renderSVG is canonical and every other format derives from it; renderFragment is that markup made embeddable, and takes className, label and id - the last because a page may embed more than one sheet and a composition always writes id="cheatsheet" on its root. Writing a render to disk is a second import, api/cheatsheet/files.mjs, the only module in the folder that reads node:fs.

A sheet's footer links out, and variation.links says where, in sheet pixels. renderSVG writes them as a final <g data-name="links">, withoutLinks takes it off again, and a fragment lifts them into a <nav> beside its role="img" picture; linkLayerFor(markup) is that <nav> for a page with a box of its own. A raster carries no link.

The catalog

<category>/<sheet>/sheet.json
<category>/<group>/<sheet>/sheet.json
<category>/meta.json                    the readable title, and an order

cheatsheets.json is committed rather than built at deploy time, so a local php -S localhost:8000 needs no Node. Regenerate and commit it whenever a cheatsheet is added, renamed or moved.

Palette

RoleLightDark
Page#F8F8F9#020433
Body text#020433#F8F8F9
Secondary text#414566#9EBEA7
Borders#6F6F8C#6F6F8C
Link, accent#2E7534#4CAE50

The mark's green #4CAE50 reaches 2.65:1 on the off white ground, so on light it carries nothing a reader has to see an edge of; #2E7534 is the same hue darkened to 5.33:1 and takes those roles instead. One 8px spacing step, a 4px radius. The full record, with every measured ratio, is in DESIGN_LANGUAGE.md.

Rules worth not rediscovering

Part of the cheatsheets documentation. The repository is on GitHub, and the example page it documents is here.

Contents