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 with kind: "library", not the filename. (A reader may also accept index.spub.html; writers emit index.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.json as 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 root config/ folder, which holds library-scoped configuration documents (a Default-View.spub.html of 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 own config/ path.
  • Collections — files with kind: "collection" — live in the folder too (a collections/ 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/.json requiring 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: mode is how a catalog surface shows the library — "shelf" (covers / index cards) or "list" (compact rows; the default) — card is the index-card template (which meta fields the card shows, in order, and whether the cover image renders on it), and snippet is 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 — plus resume, 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, is meta.uri (HTML spelling <link rel="canonical">). meta.owner and 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 generatedreindex 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 catalog section 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:

  1. Cover imagemeta.cover resolvable → render it (alt from meta.coverAlt, else the title), fit object-fit: cover into the card box.
  2. Cover view — else a non-abstract view of the work matching use.intent: "cover" → render the work through it, clipped to the card box. (snippet is the analogous compact-text-row intent.)
  3. Generated card — else a card from the work’s meta alone: the library.display.card fields (default title · author · year), stacked, start-aligned, themed with the work’s own resolved default view’s theme roles (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 list from 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 the folder package 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.