jekyll-site-dev
Development reference for the alexgude.com Jekyll site. Use when modifying plugins in _plugins/, writing or editing content in _posts/ or _books/, working with the markdown output pipeline, book famil
SKILL.md.claude/skills/jekyll-site-dev/SKILL.md
GitHubJekyll site development
Use this skill to locate the authoritative implementation notes, policy
guides, tests, and verification commands for the alexgude.com Jekyll site.
The site runs Jekyll in Docker. Run site operations through make; do not run
jekyll or bundle directly.
Start with the relevant source of truth
Most subsystem documentation lives next to the code it describes. Use the following order when investigating a change:
- Read this file for the stable architecture and workflow overview.
- Read the relevant code-resident documentation for implementation behavior.
- Read the relevant policy reference for authoring or operational rules.
- Read the matching tests and their shared helper before changing behavior.
- Run the narrowest relevant check, then the full checks required by the change.
Code comments and tests are authoritative for implementation behavior. This skill summarizes where to find them; do not duplicate volatile implementation details here.
Query code-resident documentation
Custom YARD tags declared in .yardopts mark documentation that belongs with
the code:
| Tag | Use it for |
|---|---|
@validator |
Build validators, the data errors they catch, and their fatal failures. |
@pipeline |
The stages and data flow of a processing pipeline. |
@pattern |
Reusable implementation and testing patterns. |
@gotcha |
Non-obvious behavior that can cause subtle bugs. |
List all tagged objects:
make doc-index
make doc-index QUERY='has_tag?(:gotcha)'
Display one object’s full docstring:
make doc-show OBJ=Jekyll::Books::Core::BookLinkResolver
make doc-show OBJ='Jekyll::Books::Core::BookLinkResolver#resolve'
For a top-level method, use OBJ='#method_name'. A constant, class method, or
instance method can be passed by its full object path. Reading the source file
reported by the index is equivalent when that is easier.
The skill-doc tests keep these pointers current:
_tests/src/test_skill_docs_yard_objects.rbchecks every referenced tag and YARD object._tests/src/test_skill_docs_paths.rbchecks every referenced repository path.
Repository map
_posts/contains blog posts._books/contains thebookscollection and book reviews._layouts/and_includes/define page structure and shared markup._plugins/src/contains the plugins, organized by domain:infrastructure/contains low-level utilities, text and URL handling, the link cache, generated static files, and markdown formatting helpers.ui/contains reusable cards, ratings, citations, and markdown-card components.content/contains domain logic for books, posts, authors, series, and markdown output.seo/contains JSON-LD, SEO metadata, and standard.site output.
_tests/mirrors_plugins/src/for plugin tests._tests/bin/mirrors_bin/for CI-gate tests._scripts/atproto/contains the AT Protocol publishing tools.
The link cache is built by LinkCacheGenerator and stored in
site.data['link_cache']. Resolvers use it for O(1) lookups of books and
authors. Keep it serializable; live document objects belong elsewhere.
The SEO subsystems communicate through _includes/head.html:
JsonLdInjectorpopulatessite.data['generated_json_ld_scripts']with<script type="application/ld+json">tags. Its dispatch is keyed by layout throughLAYOUT_GENERATORS; an unknown layout raises.SeoMetaInjectorpopulatessite.data['seo_meta']with title, Open Graph, Twitter, description, canonical, and robots values. Its layout knowledge is limited to title suffixes (LAYOUT_TITLE_SUFFIX), article classification (ARTICLE_LAYOUTS), and indexing (NOINDEX_LAYOUTS,NO_CANONICAL_LAYOUTS). Thetest_every_known_layout_has_article_classificationtest requires every layout inLAYOUT_GENERATORSto be explicitly classified.StandardSiteWellKnownGeneratoremits.well-known/site.standard.publicationfor AT Protocol / Bluesky verification. The matching link tags usestandard_site.publication_urifrom configuration and_data/standard_site.jsongenerated by CI.
JSON-LD entity identity (JsonLdUtils::ENTITY_KINDS): a bare page URL
identifies the web page. An entity the page describes gets the page URL plus
a fragment, so one URI never names both a page and a thing:
| Entity | @id |
url |
|---|---|---|
| Reviewed Book | book-family URL + #book |
book-family URL |
| Book author | author page URL + #person |
author page URL |
| BookSeries | series page URL + #series |
series page URL |
| Site owner | author.url (_config.yml) + #person |
author.url |
| WebSite | site root + #website |
site root |
Only /resume/ and author pages have page nodes with an @id. These nodes
use the bare page URL and carry page text (name, description). A Review or
BlogPosting identifies its page through mainEntityOfPage; other page nodes
carry only url. Person nodes carry identity only.
author.url is required: JsonLdUtils.site_owner_ref stops the build if it
is missing. /resume/ is a ProfilePage whose mainEntity is the site owner.
An author page is a CollectionPage whose about is the book author.
Google's ProfilePage classification is for a person affiliated with the
site; the book author wrote the books, not the site, so that classification
does not apply.
The book-family URL comes from BookIdentityResolver, so archived reviews
share the canonical review's Book @id. A book's place in its series is the
Book's position, never the shared BookSeries node's. A book whose series
has no series page stops the build, like an unresolved author.
Every book and author page requires wikidata_qid, the only field that stores
its QID. JsonLdUtils.same_as_list builds the Wikidata URL from this QID and
places it first in the Book's or author Person's sameAs; same_as_urls
holds the other links. The link cache carries the author's QID so book reviews
build the same sameAs for credited authors. FrontMatterValidator stops the
build if the QID is missing or malformed, if a non-null same_as_urls value
is not a list, or if same_as_urls contains a Wikidata URL.
On a book, it also stops the build if isbn is set but not a string: YAML 1.1
reads an unquoted isbn: 0306406152 as an octal Integer with different digits,
so quote the ISBN or write it with hyphens.
An archived review is distinct content, so its HTML canonical link points to
its own URL. The front-matter canonical_url only groups reviews into a book
family for book links and the Book @id.
Code-resident topics
Read the named exemplar before modifying the corresponding subsystem:
- Build validators (
@validator): classes that raiseJekyll::Errors::FatalExceptionfor invalid data. Read these when adding a validator, debugging aFatalException, or determining why a build failed. - Markdown output pipeline (
@pipeline): the complete pre-render → post-render →llms.txtflow begins atJekyll::MarkdownOutput::MarkdownBodyHook. Read it when changingrender_mode, markdown output assembly, or thellms.txtindex. - Plugin patterns (
@pattern):Jekyll::Infrastructure::Links::LinkTagBasedocuments the thin-tag and delegate structure;Jekyll::UI::DisplayTagRenderabledocuments render-mode branching;Jekyll::Infrastructure::Links::LinkResolverSupportandJekyll::Infrastructure::Links::LinkResolverSkeletondocument the two link-resolver mixins; andJekyll::Infrastructure::PluginLoggerUtils.log_liquid_failuredocuments the error-logging convention. - Gotchas (
@gotcha): read the tagged code comments for document URL access, page-payload snapshots, cache pollution, canonical URL filtering,generate_link_cachein tests, and other behavior that is easy to miss. - Testing: the header of
_tests/test_helper.rbdocuments test structure, naming,MockDocument,MockSite, factory methods, common patterns, and SimpleCov behavior. Factory methods have their own@paramand@returndocumentation. Use the production pattern docs when writing tests for Finder/Renderer orchestration or render-mode behavior._tests/src/seo/test_json_ld_rendered_site.rbbuilds a small fixture site with the real layouts, includes, and plugins in a separate Ruby process and parses the written HTML; copy it when a test must cross the hook and template boundary. - CI/CD and hooks: the header of
.github/workflows/jekyll.ymldocuments workflow stages. The header of_bin/pre-commit.shdocuments hook stages and thegit stash --keep-indexmechanism. Read them before modifying a workflow or commit hook. The hook restores unstaged work from the stash commit it created, never by stash position, and leaves the stash with recovery commands when the restore fails._scripts/tests/test_pre_commit.pyruns it in a temporary repository with stubdockeranduvexecutables; run it withmake test-scriptsafter any hook change. - CI gates:
_bin/check_*scripts run as real subprocesses against throwaway fixture directories. The header of_tests/bin/bin_script_helper.rbdocuments the fixture builders and test approach.test_bin_scripts_are_wired_up.rbfails when a newcheck_*script has no test, or when no step'srun:command injekyll.ymlinvokes it. A mention in a comment or step name does not count. - External links:
.github/workflows/link-check.ymlruns Lychee monthly with a JSON report._scripts/ci/lychee_report.pydecides the result: it drops theat://parse errors, writes the issue body, and exits 0 (clean), 1 (broken links), or 2 (no usable report). Its tests run against real Lychee reports in_scripts/tests/fixtures/lychee/; regenerate them with the Lychee version thatlycheeverse/lychee-action@v2installs.
Policy and operational references
These documents describe policy rather than implementation. Read the one that matches the change:
Build Performance — measured decisions about HTML compression and build caches. Required when changing build or serve targets.
Book Families &
canonical_url— re-review workflow and canonical URL rules. Required for book reviews, especially re-reviews.Content Authoring — excerpts, Liquid tags, links, and front matter enforced by the AT Protocol pipeline. Required for blog posts and book reviews.
Markdown Output — pre-render hook behavior, page payload updates, and fatal generation failures. Required for the Markdown output pipeline.
Links, Backlinks, and Previews — raw Liquid scanning, related-book scoring, canonical URL filtering, hover and footnote previews, reference-link validation, and strict-Liquid constraints. Required for link resolution, the link cache, previews,
check_reference_links.rb, orcheck_strict.rb.AT Protocol / standard.site — domain-as-handle DNS wiring, the publish script, the well-known generator, link tags, CI flow, and operational procedures for orphaned records, outages, and secret rotation. Required for
_scripts/atproto/, the standard.site plugin, or publish steps in CI.
Content and plugin rules
Keep these boundaries intact when changing the site:
- Tags in
tags/are thin wrappers. They selectrender_modeand delegate. - Utilities in
[domain]/[util].rborchestrate logic. - Finders fetch data; renderers generate HTML.
- Use
PluginLoggerUtils.log_liquid_failurefor Liquid failures. - Add a matching test in
_tests/for every new class. - When an invariant is violated, raise
Jekyll::Errors::FatalException. Do not allow incorrect output to ship silently.
When changing plugin architecture, CI workflow, hooks, or the markdown-output
pipeline, update the matching document in references/.
Verification
Run commands from the repository root. The content checks are separate; no single command runs all of them:
| Command | Checks |
|---|---|
make test |
All Ruby tests in _tests/. Fails below 95% line or 90% branch coverage. |
make test TEST=_tests/src/path/to/test.rb |
One Ruby test file. The coverage thresholds apply to full runs only, so the exit status reflects the tests alone. |
make check-links |
Builds the site, then runs html-proofer for broken links, missing images, and empty alt attributes. |
make check-refs |
Finds undefined, duplicate, and orphaned Markdown reference links. |
make check-liquid |
Renders documents with strict Liquid checking. |
make check |
Alias for make check-links; it does not run the other content checks. |
make lint |
Runs the repository lint checks. |
make format-md |
Formats every Markdown file in the repository. Do not use it to format a change; it rewrites unrelated files. |
make prettier FILE.md ... |
Formats the named Markdown files with Prettier. |
For a content or plugin change, run the targeted test first and then the
checks that cover the changed behavior. Do not merge a branch straight into
main. Open a pull request, or run make check-links, make check-refs, and
make check-liquid locally before merging. These checks must run before the
change can publish.
The AT Protocol publish step runs on main only. Its PDS is the state store;
the repository does not contain AT-URIs for individual documents. Every other
branch runs publish --dry-run in the test job as a blocking step. It needs
no secrets, so a bsky.social outage fails branch CI; rerun the job. Read
AT Protocol / standard.site before
changing publish behavior or operating the workflow.