Skip to content

specsite v0.1: Starlight plugin, content loader and HTML snapshot CLI - #1

Merged
pandres95 merged 16 commits into
mainfrom
feat/v0.1
Oct 13, 2026
Merged

pandres95 merged 16 commits into
mainfrom
feat/v0.1

Conversation

@pandres95

Copy link
Copy Markdown
Member

specsite v0.1: publish a repository's Markdown specs as living Starlight pages and as single-file HTML snapshots, from one pipeline.

What is in it

  • Starlight plugin specsite({...}), configured in astro.config with an allowlist of documents (files and globs). It adds a sidebar group, a small stylesheet in Starlight's own variables, and a Mermaid renderer bundled with the site and loaded only on pages that have a diagram.
  • Content loader specsiteLoader(docsLoader()) for src/content.config.ts. The pages become ordinary entries of the docs collection: each is rendered by the site's own Markdown processor (renderMarkdown), so heading anchors, asides, Expressive Code, the table of contents, previous/next, edit links and Pagefind all apply unchanged. Nothing is written to src/content/docs and nothing generated is committed.
  • split: 'h2': one page per H2 section plus an index page; every heading keeps its GitHub anchor, and links to it from any document go to the page that holds it.
  • Identifiers: definitions found by four rules in priority order (bold at the start of a block, first cell of a table row, heading, list item), anchored at the lower-cased ID (REQ-DI-4 → #req-di-4) on the page that holds them; every other mention, in code or running text, links there across pages and documents. Undefined references and same-kind duplicate definitions are reported with file:line; check: 'error' fails the build. Definitions rank first in search for their ID.
  • Links: relative links between listed documents become site URLs; links to other repository files go to the repository host (repoUrl, branch).
  • Source line and edit link per page, from git at build time.
  • CLI specsite html <file.md> -o out.html [--brand] [--accent] [--with file=url] [--check]: the same pipeline into one self-contained page (contents rail, light/dark, inline CSS; Google Fonts is the only external request and is optional). Ported from a pandoc-based md2artifact.py, so there is one implementation.
  • Tests and CI: unit tests for identifier detection and linking, link rewriting and the CLI; example/ is an invented project (Tidepool) built by CI with the Swimming on Code theme.
  • Repo: README (with a Roadmap: backlinks, tombstones, a needs.json export), MIT licence (replacing the initial commit's dual licence), CONTRIBUTING with DCO sign-off, SECURITY placeholder.

Why a content loader

Starlight renders only its docs collection, and the content layer is the supported way to feed a collection from files outside src/content. A wrapping loader keeps Starlight's own loader for hand-written pages and adds the generated ones beside them, rendered by the site's configured processor. Injecting routes with <StarlightPage> would need no change to content.config.ts, but would bypass the site's Markdown pipeline (Expressive Code, asides, heading links) and Starlight's autogenerated sidebar and pagination.

Tried on a real project

Built against a private spec-driven project's docs site (Astro 7.3.5, Starlight 0.42.4), locally only: SPEC.md split at H2 plus PLAN.md and OVERVIEW.md gave 19 pages, 675 identifiers and 2,164 links; the specsite step took about 0.6 s and the whole site build 8 s.

🤖 Generated with Claude Code

pandres95 and others added 16 commits October 13, 2026 00:00
specsite is a Swimming on Code project; its projects are MIT unless a
stronger licence requires otherwise. One LICENSE file replaces the dual
Apache-2.0/MIT pair from the initial commit.

Signed-off-by: Pablo Andrés Dorado Suárez <hola@pablodorado.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- specsite(): a Starlight plugin configured with an allowlist of Markdown
  documents; it adds them to the sidebar, its stylesheet and a lazily
  loaded Mermaid renderer.
- specsiteLoader(): wraps the docs collection's loader and adds one entry
  per page, rendered by the site's own Markdown processor, then passed
  through the shared pipeline. Nothing is written to src/content/docs.
- split: 'h2' cuts a document into section pages that keep their GitHub
  heading anchors.
- Identifiers are anchored where defined (bold, table row, heading, list
  rules, in priority order) at the lower-cased ID, and every other
  mention links there across pages and documents; undefined references
  and duplicate definitions are reported with file:line, and fail the
  build under check: 'error'.
- Relative links between listed documents become site URLs; links to
  other repository files go to the repository host.
- specsite html renders one document as a self-contained page through
  the same pipeline, ported from a pandoc-based md2artifact.py.

Signed-off-by: Pablo Andrés Dorado Suárez <hola@pablodorado.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture is an invented project (Tidepool) with SPEC.md, PLAN.md,
OVERVIEW.md and two amendments, published by example/site with the
Swimming on Code theme. CI type-checks, runs the tests, builds the
fixture site and renders a snapshot with the CLI.

Signed-off-by: Pablo Andrés Dorado Suárez <hola@pablodorado.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README (what, why, install, configuration reference, identifiers,
links, the HTML snapshot, roadmap), CONTRIBUTING with DCO sign-off, a
placeholder SECURITY policy, fixture screenshots, and the Swimming on
Code wordmark in charcoal and white for the README footer.

Signed-off-by: Pablo Andrés Dorado Suárez <hola@pablodorado.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A list item or table row that opens with an ID already defined more
strongly elsewhere (a traceability list, a summary table) is a
reference, not a conflicting definition. Duplicate reports now point at
the line where each definition has its rule's shape.

Signed-off-by: Pablo Andrés Dorado Suárez <hola@pablodorado.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npm 11 left out the wasm32 bindings' nested @emnapi packages, which
npm 10 (Node 22 in CI) requires for npm ci. Also records the optional
peer dependencies on astro and Starlight.

Signed-off-by: Pablo Andrés Dorado Suárez <hola@pablodorado.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The files list still named LICENSE-APACHE and LICENSE-MIT; the package is
MIT with a single LICENSE file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
REQ-LD-2: one block with the repository (owner/name), the build commit
(HEAD short hash, linked when the repo URL is known), the document's
spec_version/version and status from front matter, and an ISO 8601
render time. The file's last-touching commit stays as a secondary
"last changed in" line. The snapshot no longer shows front-matter
date, which read as the render time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view 3)

REQ-LD-8 asks for duplicate identifiers. A second definition by any of
the strict rules (bold, table first cell, heading, section title) is now
a duplicate-definition problem with file:line; only list-item
definitions stay lenient, as references in a traceability list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A link to a listed document whose fragment matches no heading, ID
definition or other element id now reports a broken-anchor problem at
file:line, strict under check: 'error' (REQ-LD-8 "broken references").
The link still falls back to the document. Links to unlisted files stay
repository URLs and are not checked; the README says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sanitize: true (Starlight plugin) and --sanitize (specsite html) run
rehype-sanitize with GitHub's allowlist, extended to keep ids as written,
data-* and specsite-* classes (plus Starlight's heading and aside markup
on the site). Scripts, event handlers, style and javascript: URLs are
dropped. On the site, Expressive Code frames are kept as rendered only
when there is one per code block in the source. Default stays off;
SECURITY.md says sources are taken to be the owner's own Markdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iew 6)

The hex check lived only in the CLI, so API callers could close the
<style> element through accent. renderSnapshot and pageTemplate now throw
on a non-hex accent, sharing the CLI's regex. The brand SVG is documented
as trusted configuration, inlined as given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The heading rule took the first code anywhere in a heading, so
"## Replaces `REQ-2`" defined REQ-2. An ID now defines only as the
heading's first meaningful child (in code, optionally bold or emphasised,
or as leading text). Split-page title IDs follow the same rule, and the
duplicate locator matches headings the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Basis block fields (snapshot and row builder), table/heading/title
duplicates with list items staying lenient, bold-over-table priority
across documents, broken anchors in listed documents, a heading that
only mentions an ID, opt-in sanitising, and accent validation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mbers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pandres95
pandres95 merged commit 579e8ef into main Oct 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant