Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .claude/skills/library/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ js/library/
client.js the index and entry files fetched, verified and kept offline.
submit.js the submission document, the pre-check, the issue URL.
requests.js import-free. automata-studio:// links queued until boot ends.
article.js import-free. An essay's Markdown → HTML: the dialect, facts,
figures, footnotes. Shared by the website build and the view.
article-figures.js import-free. The figures an essay draws from a machine's
standard code: space-time diagrams, growth charts.
essay.js what an essay may ask of the index: {{facts}}, figure
arguments (bounded), lib: links, and the build's warnings.
essay-draw.js the view's figures, drawn in essay-figures.worker.js.
js/library-ui.js the view: discover, browse, entry pages, collections,
My Library, submit, about.
scripts/library/ build.mjs (index + site), site.mjs, seed.mjs,
Expand Down Expand Up @@ -102,6 +109,19 @@ There is no server and the app holds no token. `submissionLink` pre-fills the re

StateMate reaches the library two ways: `/library [words]`, and the `search_library` agent tool (synchronous, like every tool — the first call starts the download and says to ask again).

### Essays

**A machine or a collection can carry an essay**: a Markdown file beside it (`machines/…/bb5.md` beside `bb5.automaton`, `collections/busy-beavers.md` beside its `.json`). Its own file, so prose is diffed and reviewed as prose, and editing it never changes the machine's bytes, hash or id. The index lists it as `essay: { path, hash, minutes }` on the entry or collection (`essayRef` in index-model.js refuses a path that could not be one — it ends up in a fetch URL); `writeLibrary` serves it beside its machine, and only when the machine was published.

- **The dialect is [js/library/article.js](js/library/article.js)**, small on purpose: `##`/`###` (a `#` is a section too — a page has one title, the entry's), paragraphs, lists, quotes, tables, fenced code, `$…$`/`$$…$$`, `[^n]` notes, links to `http(s)`, `#` or `lib:<id>`. Code spans and math are set aside **before** anything else, and everything is escaped before any tag is written: nothing an author writes reaches the page as markup. Anything it cannot answer stays visible and is returned in `warnings`.
- **The one promise holds inside the prose.** `{{steps}}`, `{{ones}}`, `{{cells}}`, `{{states}}`, `{{transitions}}`, `{{size}}`, `{{standard}}`, `{{title}}` — and `{{steps <id>}}` for another entry — are read off the index by `essayFacts` (essay.js), the analysis that earned the badges, never typed. They are set in the mono with a dotted rule so a reader can tell the machine's answers from the author's words. A name is looked up with `Object.hasOwn`: `{{constructor}}` is not a fact.
- **Figures are drawn from the machine**: `::: spacetime steps= h=`, `::: growth y=log scale=linear`, `::: diagram id=`, `::: machines <id> <id> …`, each followed by its caption and a closing `:::`. `essayFigureSpec` parses and **bounds** the arguments (200,000 steps for a space-time diagram; a growth chart runs to the halt under 10⁸) for both faces. The website draws them at build time; the view draws them in a worker (essay-draw.js — one worker, started on first use, results kept by input so going back does not rerun BB(5)), falling back to drawing in place where there is no Worker. A space-time diagram draws the head's path only when every step has its row: sampled, it joins positions many steps apart into a zigzag the machine never made.
- **The build checks every essay** against what is published (`essayWarnings`): an unknown fact, a figure the machine cannot draw, a `lib:` link to nothing, a note never written. They are **warnings** on the entry's report — the PR comment — not errors: a sentence to fix is no reason to unpublish a machine that passed.
- **An essay is credited as its machine is** (`guard.mjs`): it is Markdown, with no credit of its own, so the machine's author or a maintainer may write it and nobody else may attach one to their machine; an essay with no machine beside it is refused. A collection's essay is the maintainers', like the collection. Essays arrive by pull request — the issue form carries the short write-up (`meta.library.readme`), which an essay replaces on the page.
- **In the view** (`essaySection` in library-ui.js) the essay is fetched by `fetchEssayText` — the same verified, cached path as a machine's file, so a tampered download is refused and an essay read once reads offline. Two things differ from the website because the essay shares a document with the app: **every id is prefixed** (`lib-essay-`, via `idPrefix`), and **in-page links are handled in the view** — a footnote or a `lib:` link (written as `#library=`/`#collection=`) must not write the address bar's hash, which carries share links. Figures and plates go in as empty `data-essay-slot` nodes and are filled once the markup is in place. Anything scrolled to carries `scroll-margin-top`, or the sticky status bar covers it.

[tests/essay.test.js](tests/essay.test.js) pins the dialect, the escaping, the fact lookup, the figure bounds and the index's `essayRef`; [tests/library.test.js](tests/library.test.js) the build, the report's warnings, the guard, the website's page and the view (a tampered essay is refused, ids are prefixed, the essay comes before Behaviour and replaces the notes).

### Busy beavers and the standard format

`writeStandardTM` in [js/interop/standard-tm.js](js/interop/standard-tm.js) is the inverse of `readStandardTM`, exact on its output: start state is A, the halt is a state that accepts and has no moves, and anything the notation cannot say (an S move, a wildcard, a non-digit symbol, an accepting working state) answers `null` rather than an approximation. `analyzeDocument` stores it as `facts.standard`, so every one-tape TM listing carries its code — Copy, "View on bbchallenge.org" (`bbchallengeUrl`, `&status=halt` when it halts) — and pasting a code into search finds the machine. **A TM's size (states × symbols) is read off that code** (`standardSize` in card-html.js), never from the drawn state count, which includes the halt state — guessing whether to subtract it was wrong whenever the halting analysis ran out of budget. A collection that is mostly TMs draws an "At a glance" table (size, steps, non-blank, code). `library.config.json`'s `featured` list puts collections first on the home page.
Expand Down
75 changes: 74 additions & 1 deletion css/library.css
Original file line number Diff line number Diff line change
Expand Up @@ -488,11 +488,84 @@
.lib-inline-link { padding: 0; border: none; background: none; color: var(--accent); font: inherit; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.lib-inline-link.is-update { color: var(--gold); }

/* ── essays ──
An entry's or a collection's long form (js/library/article.js), rendered
from the same Markdown as the website's page and set the same way: a
reading column in the serif, the contents in the margin, figures boxed and
numbered, notes at the foot. The markup carries the website's class names
(board, fact, essay-*), so every rule here is scoped to the essay's body
and inked from the app's theme. A {{fact}} — a number the library computed
— is set in the mono with a dotted rule under it, so the machine's answers
read apart from the author's words. */
.lib-essay-meta { font-family: var(--mono); font-size: .66rem; color: var(--text3); }
.lib-essay-grid { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 48px; align-items: start; }
.lib-essay-grid.is-no-toc { grid-template-columns: minmax(0, 1fr); }
.lib-essay-toc { position: sticky; top: 12px; display: flex; flex-direction: column; gap: 10px; padding-top: 6px; }
.lib-essay-toc[hidden] { display: none; }
.lib-essay-toc ol { margin: 0; padding: 0; list-style: none; counter-reset: toc; display: flex; flex-direction: column; gap: 7px; }
.lib-essay-toc li { counter-increment: toc; display: grid; grid-template-columns: 22px 1fr; }
.lib-essay-toc li::before { content: counter(toc) "."; font-family: var(--mono); font-size: .64rem; color: var(--text3); padding-top: 3px; }
#v-library .lib-essay-toc-link { all: unset; cursor: pointer; font-family: var(--serif); font-size: .98rem; line-height: 1.3; color: var(--text2); }
#v-library .lib-essay-toc-link:hover { color: var(--accent); }
#v-library .lib-essay-toc-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.lib-essay-body { max-width: 680px; font-size: 1.18rem; line-height: 1.66; }
.lib-essay-body > p:first-child::first-letter { float: left; font-size: 3.3em; line-height: .86; padding: 6px 8px 0 0; color: var(--text); }
.lib-essay-body h2, .lib-essay-body h3 { font-family: var(--serif); font-weight: 400; color: var(--text); }
/* Anything the contents or a note scrolls to clears the Library's sticky status bar. */
.lib-essay-body [id] { scroll-margin-top: 56px; }
.lib-essay-body h2 { font-size: 1.55rem; margin: 1.6em 0 .5em; letter-spacing: -.005em; }
.lib-essay-body h2:first-child { margin-top: 0; }
.lib-essay-body h3 { font-size: 1.22rem; font-style: italic; margin: 1.3em 0 .4em; }
.lib-essay-body p { margin: 0 0 .9em; }
.lib-essay-body ul, .lib-essay-body ol { margin: 0 0 1em; padding-left: 1.3em; }
.lib-essay-body li { margin: .25em 0; }
.lib-essay-body blockquote { margin: 1.2em 0; padding: 2px 0 2px 20px; border-left: 2px solid var(--h, var(--accent)); color: var(--text2); font-style: italic; }
.lib-essay-body code { font-family: var(--mono); font-size: .76em; padding: 1px 5px; border-radius: 3px; background: var(--lib-well); border: 1px solid var(--border); overflow-wrap: anywhere; }
.lib-essay-body .textlink-inline { color: var(--text); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); }
.lib-essay-body .textlink-inline:hover { color: var(--accent); border-bottom-color: var(--accent); }
.lib-essay-body .essay-math { margin: 1em 0; overflow-x: auto; }
.lib-essay-body .essay-code { margin: 1em 0; padding: 12px 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--lib-well); overflow-x: auto; line-height: 1.5; }
.lib-essay-body .essay-code code { border: 0; padding: 0; background: none; font-size: .76rem; }
.lib-essay-body .fact { font-family: var(--mono); font-size: .78em; color: var(--text); border-bottom: 1px dotted var(--h, var(--accent)); cursor: help; white-space: nowrap; }
.lib-essay-body .fact.is-missing { color: var(--red); border-bottom-color: var(--red); }
/* In a table the cell already sets the face and size; a fact only adds its rule.
A header's small caps must not reach into its math: S(n) is not S(N). */
.lib-essay-body .board .fact { font-size: inherit; }
.lib-essay-body .board th .katex { text-transform: none; letter-spacing: normal; font-size: 1.5em; }
.lib-essay-body .essay-fig { margin: 1.8em 0; display: flex; flex-direction: column; gap: 10px; }
.lib-essay-body .figcaption-text { font-family: var(--serif); font-style: italic; font-size: 1rem; line-height: 1.4; color: var(--text2); }
.lib-essay-body .fig-n { font-style: normal; font-family: var(--mono); font-size: .68rem; letter-spacing: .06em; color: var(--text3); text-transform: uppercase; margin-right: 4px; }
.lib-essay-fig { aspect-ratio: var(--fig-aspect, 16 / 10); border-radius: 8px; }
.lib-essay-fig > svg { display: block; width: 100%; height: 100%; }
.lib-essay-fig.is-chart { background-image: none; }
.lib-essay-fig.is-drawing { background-image: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--text) 5%, transparent) 50%, transparent 70%); background-size: 200% 100%; animation: lib-essay-drawing 1.4s linear infinite; }
@keyframes lib-essay-drawing { from { background-position: 100% 0; } to { background-position: -100% 0; } }
.lib-essay-body .essay-st .rn-1 { fill: color-mix(in srgb, var(--text) 70%, transparent); }
.lib-essay-body .st-head { fill: none; stroke: var(--h, var(--accent)); stroke-width: 1.2; stroke-linejoin: round; opacity: .9; vector-effect: non-scaling-stroke; }
.lib-essay-body .gr-grid { stroke: var(--border); stroke-width: 1; }
.lib-essay-body .gr-axis { stroke: var(--text3); stroke-width: 1; }
.lib-essay-body .gr-tick { fill: var(--text3); font-family: var(--mono); font-size: 11px; }
.lib-essay-body .gr-line { fill: none; stroke: var(--h, var(--accent)); stroke-width: 1.8; stroke-linejoin: round; }
.lib-essay-body .gr-end { fill: var(--h, var(--accent)); stroke: var(--lib-well); stroke-width: 2; }
.lib-essay-body .table-wrap { overflow-x: auto; margin: 1.2em 0; }
.lib-essay-body .board { width: 100%; border-collapse: collapse; font-size: .84rem; font-variant-numeric: tabular-nums; }
.lib-essay-body .board th, .lib-essay-body .board td { padding: 8px 12px 8px 0; border-bottom: 1px solid var(--border); text-align: left; color: var(--text); font-family: var(--sans); }
.lib-essay-body .board th { font-family: var(--mono); font-size: .6rem; font-weight: 400; letter-spacing: .14em; text-transform: uppercase; color: var(--text3); }
.lib-essay-body .board .num { text-align: right; font-family: var(--mono); font-size: .78rem; }
.lib-essay-body .fn-ref { font-family: var(--mono); font-size: .62em; line-height: 0; margin-left: 1px; }
.lib-essay-body .fn-ref a, .lib-essay-body .fn-back { color: var(--accent); text-decoration: none; }
.lib-essay-body .essay-notes { margin-top: 2.2em; padding-top: 14px; border-top: 1px solid var(--border); font-size: .96rem; line-height: 1.5; color: var(--text2); }
.lib-essay-body .essay-notes ol { margin: 0; padding-left: 1.3em; }
.lib-essay-body .lib-plates { margin: 0; }

@media (max-width: 1100px) {
.lib-mast.has-frontis { grid-template-columns: minmax(0, 1fr); }
.lib-frontis { max-width: 620px; }
.lib-families { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.lib-entry-body { grid-template-columns: minmax(0, 1fr); }
.lib-essay-grid { grid-template-columns: minmax(0, 1fr); gap: 20px; }
.lib-essay-toc { position: static; }
.lib-essay-toc ol { flex-direction: row; flex-wrap: wrap; gap: 6px 18px; }
}

@media (max-width: 900px) {
Expand All @@ -508,6 +581,6 @@
}

@media (prefers-reduced-motion: reduce) {
.lib-shimmer { animation: none; }
.lib-shimmer, .lib-essay-fig.is-drawing { animation: none; }
.lib-fig, .lib-live .sk-e, .lib-live .sk-ah, .lib-live .sk-n { transition: none; }
}
Loading
Loading