Libraries
A library is a set of works with a home: a folder (or any store) holding the work files, described by one catalog document — its index. Where a collection only points at works, a library owns them.
The defining property: a library folder is a working website with zero
tooling. Open its index.html from disk and it reads properly — unstyled,
in any browser, with working links; drop the folder on a static host and it
is the site. Everything below serves that.
The file set
my-library/
index.html ← the index: a catalog document, config embedded
library.spub.json ← optional config companion (overlay, wins)
config/ ← library-config cargo: never indexed as works
Default-View.spub.html ← e.g. the owner's default view (a blank-body
preset doc holding render defaults)
Hamlet-by-William-Shakespeare/
Hamlet-by-William-Shakespeare.spub.html
Hamlet-by-William-Shakespeare.es.spub.html
assets/… ← the work's media & cover (excluded by scan)
sources/… ← excluded by scan config
collections/
Desert-Island-Picks.spub.html ← kind: "collection"
art/… ← inert cargo: never indexed, still portable
- The index is named
index.html— the web-server default is what makes the zero-tooling property real. Its ScriptPub-ness is the embedded<script id="scriptpub">config withkind: "library", not the filename. (A reader may also acceptindex.spub.html; writers emitindex.html.) - Links in the index are relative — that is what makes the folder portable.
- Config placement follows the ordinary rule:
embedded in the index, with
library.spub.jsonas the companion (deep-merged over it, companion wins). A store with no natural HTML file may keep only the JSON and generate the index on export. - The library’s own files (
index.html,library.spub.json) are never indexed as works — and neither is anything under the rootconfig/folder, which holds library-scoped configuration documents (aDefault-View.spub.htmlof render defaults, say) that must travel with the folder without ever listing on a shelf. Only the root folder is reserved; a work may nest its ownconfig/path. - Collections — files with
kind: "collection"— live in the folder too (acollections/subfolder by convention, but the path is free). They are scanned and read like anything else, but recognized by their kind: Reindex lists works only, so a collection is never one of the index’s entries. A library may hold many.
The library config
The index’s config is a normal FormatConfig — the Catalog-preset blocks,
views, meta — with kind: "library" and one extra group:
{
"scriptpub": 2,
"kind": "library",
"meta": { "title": "My Library", "description": "…" },
"library": {
"scan": {
"include": ["**/*.spub.html", "**/*.md"],
"exclude": ["**/sources/**", "**/assets/**"]
},
"display": {
"mode": "shelf", // shelf (covers) · list; default "list"
"card": { "fields": ["title", "author", "year"], "showCover": true },
"snippet": { "style": "cover", "resume": false } // cover · spread · cover-page · text; "resume" shows where you left off
},
"organize": {
"scheme": "by-author", // flat · a-z · by-author · by-title · custom
"workFolders": true, // each work in its own slug folder
"naming": "{title}-by-{author}" // slug template for new works
},
"defaults": {
"preset": { "name": "Story", "uri": "scriptpub:preset/story" },
"syntax": "html",
"language": "en"
},
"targets": [
{ "id": "my-kindle", "device": "kindle-paperwhite", "package": "epub",
"works": { "collection": "collections/kindle-picks.spub.html" } }
]
},
"blocks": [ … ], "marks": [ … ]
}
scan— which store paths are works (globs:**,*,?; a slashless glob matches basenames). Unmatched files are inert cargo. Default include: every format-registry extension — the text syntaxes AND the binary formats (.epub,.pdf,.docx), so a folder of foreign files lists as a library with no configuration — with plain.html/.jsonrequiring the.spub.*double extension; default exclude:**/sources/**and**/assets/**(a work’s media files are cargo, never works). A multi-file work’s index claims its folder: every other scanned path under it is that work’s part or cargo, never a work of its own (translations excepted) — so a work folder lists exactly once.display— the library’s portable display defaults:modeis how a catalog surface shows the library —"shelf"(covers / index cards) or"list"(compact rows; the default) —cardis the index-card template (which meta fields the card shows, in order, and whether the cover image renders on it), andsnippetis how each work presents on a shelf beyond the plain card:"cover"(the card),"spread"(an open-book two-page render),"cover-page"(cover beside the first page), or"text"(a text excerpt) — the page variants are real renders of the work at thumbnail scale — plusresume, which opens each snippet at the reader’s saved position and marks progress. A viewer MAY override all of these locally (a reading preference, never document truth); the config states what the library intends. See Rendering.- Local ordering — a catalog surface may let its viewer order the same shared entry component by title, author, published date, date added to the local store, length, or root library folder, in either direction. This is a local view preference and does not rewrite catalog order. Surfaces should group the result and expose range navigation: alphabetical fields use initials, while dates may widen from years to decades or centuries to suit the represented span. A viewer may hide group labels, place them inline with entries, or render them as section dividers; this affects layout only, never the catalog or the ordered result. Viewer-controlled page or cover sizing may be expressed as a minimum/maximum range (including percentages); grid surfaces use those bounds for responsive cell sizing, recomputing their column count as the available width changes so complete rows are filled.
- Length — the canonical local length of a work is the number of Unicode code points in its plain document text. Word and page units are derived display estimates computed from that character count; they are not stored as competing measures of the work.
organize— how Reindex groups the index (scheme) and how the app places and names new works (workFolders,naming). Reindex never moves existing files.defaults— what a new work created in this library is seeded with.targets— the library’s export targets: which works go to which device, in which view and package. Policy only — a target never carries a location.- Identity — the library’s name is
meta.title; its stable identity, when it has one, ismeta.uri(HTML spelling<link rel="canonical">).meta.ownerand friends are descriptive only: nothing in a file grants or denies access — read vs write is a property of the store (a bundled or HTTP-served library is read-only because its store cannot write).
Reindex
The index is generated — reindex in @script-pub/format, a pure
function the same everywhere (browser, build script, Worker, CLI):
reindex(config, work heads, previous index?) → index document
The caller scans the store (matchesScan) and reads each work’s head —
the cheap, body-free, per-format parse that yields its meta (parseHtmlHead
for html; frontmatter / first heading for md, title-page keys for fountain;
binary formats by their metadata decoder, else the filename) — then hands
the heads in. Reindex emits divisions per organize.scheme and one linked
list entry per work, translations nested one list level under their base
(the <slug>.<lang> sibling convention), exactly the shape
docCatalog reads back. It lists
works only: a scanned head whose kind is "collection" or "library"
is recognized by that kind and skipped, never an entry.
scheme: "custom"preserves the previous index’s entry order — new works append, removed works drop, headings are yours.- If the format declares a
catalogsection and the previous index has its span, only that span is regenerated: a foreword above it and notes below it survive verbatim. (The Catalog preset declares one.)
Rendering — shelves, covers, snippets
A library’s own views say how the catalog renders: the
Catalog preset ships List (continuous) and Shelf — a covers grid, one
card per work. Which of the two a surface opens with is
the library.display default, overridable by the
viewer as a local preference.
A viewer MAY additionally offer a Folder view over the same catalog
entries. Folder is not a portable library.display.mode: it is a local
filesystem projection of each entry’s store-relative path. Directory
navigation, file selection, details-panel state, and pane widths do not rewrite
the catalog, move files, or change the library’s List/Shelf default.
How one entry renders is the work’s business. The preference chain is normative — a catalog surface resolves each entry in order:
- Cover image —
meta.coverresolvable → render it (alt frommeta.coverAlt, else the title), fitobject-fit: coverinto the card box. - Cover view — else a non-abstract view of the work matching
use.intent: "cover"→ render the work through it, clipped to the card box. (snippetis the analogous compact-text-row intent.) - Generated card — else a card from the work’s meta alone: the
library.display.cardfields (default title · author · year), stacked, start-aligned, themed with the work’s own resolved default view’sthemeroles (paper/ink/accent) — so a shelf of coverless works still reflects each work’s identity.
Display defaults & the index card
The index card is the generated-card layer of the chain — the “book
spine” a work presents when it has no cover art. Its template lives in
library.display.card: fields (ordered meta keys; unknown keys skip) and
showCover (whether chain step 1 applies at all — a text-first library may
prefer uniform cards over art). Card geometry, hover behaviour, and grid
packing are surface concerns, not format; the format contributes the fields,
the cover reference, and the theme roles.
Storage
The format never knows the store. A store is anything path-keyed that can
list / read (and, when writable, write / delete): a filesystem folder,
a git repo, an object bucket, a browser database, a key-value namespace, an
in-memory map. Hierarchy lives in the path strings and the index document, so
non-hierarchical stores lose nothing. The act of sync, export, and
copy-between-libraries is tooling over list + read + write — not
format; an export target is the portable
policy that tooling reads (which device, which package, which works —
never a location), the way organize and defaults already are.
A library app MAY keep local provenance metadata when it copies a work, such as a source-library id and source-work id, and derive a presence index from its owned records. This supports “already in your library” affordances without changing the copied document’s canonical identity. Such an index is tool/store metadata, not portable ScriptPub document truth; export does not depend on it.
- Access is store capability, never file truth. A library is read-only because its store cannot write; nothing in a config grants or denies access.
- A hosted library needs no listing protocol. A store that can only fetch
(any static host) derives
listfrom the library’s own committed index — the read-only posture trusts the index as committed, and only its in-library (relative) references count. So any served folder — including one produced by an export — is connectable by URL as a read-only library. - Export is the portability mechanism: write every work re-pathed per the
library’s organize convention (translations as
<slug>.<lang>siblings) + a freshly generated index — the output folder is itself a complete, servable, re-connectable library. This is thefolderpackage format; device-specific packages (EPUB) are the same mechanism through an encoder.
The Public Library is simply a library whose store is a folder committed to the app’s repository, served read-only.