feat(typography): type-scale tokens, simpler PR template, main-only deploy#33
Merged
Conversation
… lesson Demonstrates the fix for "fix one page, break all": every duplicated chrome element (the whole <head>, theme scripts, analytics include, hubbar, masthead, topic chips, feedback widget, closing endnote, quiz JS) moves into two shared layouts. A lesson page is now just a schema-validated `meta` object plus its bespoke body — change the masthead once and every lesson updates. - astro-poc/ is isolated (its own package.json) so the existing CommonJS scripts/*.js keep working; builds to ../dist, never docs/. - build.format:'file' + site/base preserve the exact .html URLs and canonical/ OG tags, so the GitHub Pages "main -> /docs" deploy model is unchanged. - src/schema/lesson.ts is the lesson contract; bad/missing metadata is a build error, replacing the hand-rolled checks the inject-*.py scripts patch in. Build instructions and the migration plan are in README-ASTRO-POC.md. npm is blocked in the authoring sandbox, so the `npm install && npm run build` verification step runs on the maintainer's machine.
- switch the POC to pnpm; add Prettier with prettier-plugin-astro (tabWidth 4, matching .editorconfig) plus `format` / `format:check` scripts - .gitignore: anchor the EPUB reader's local-only /package.json and /pnpm-lock.yaml to the repo root so subdirectory manifests (astro-poc/) track normally — removes the broad package.json/lock ignores and the negation hack, without exposing the local-only reader files
…URLs were missing a slash)
…-LD), Sources, Rehearse, Steps; Quiz id optional
…eserve analytics hooks)
The Engineering Vault site now builds end-to-end under Astro: 49 pages (the hub + 48 teaching pages), output to dist/, faithful to the live design. Deploy model is unchanged (static HTML → GitHub Pages main → /docs once outDir is flipped). Hub (src/pages/index.astro + components): - The 15 hardcoded track cards become ONE typed `tracks` source (src/data/tracks.ts, Zod-validated) that feeds the grid, hero count, filter counts, search index, no-results catalog and card sheet — no more drift across 5 hand-maintained copies. - Chrome → components: SiteHeader, Hero, WhatIsBand, FilterChips, TrackCard, TrackGrid, SearchResults, CardSheet, SiteFooter + shared atoms (ThemeToggle, SocialLinks, GithubStar). The hub client JS is ported verbatim (search, filter, progress->Continue, card sheet, NEW pill); SEARCH_INDEX/CARD_DETAIL derived from `tracks`. Fixed the progress_reset double-fire; dropped the pinch-zoom- blocking viewport. Lessons (src/content/lessons/**.mdx + [...slug] route): - 48 teaching pages → an MDX content collection with schema-validated frontmatter (quiz/interview/sources/mc/steps), rendered through LessonLayout via a route that preserves the exact .html URLs. - Recurring widgets are shared components (Quiz/Interview/Sources/Rehearse/Steps/ MemoryCheck); bespoke SVGs are co-located figure components; the FAQ JSON-LD derives from the interview array (can't drift). - Per-page inline CSS consolidated into lesson.css (#main-scoped); two heavily bespoke lessons keep co-located _*.css imports. Tooling / safety net: - scripts/check-hub-hooks.js asserts all 19 analytics + ARIA hooks on the built hub (PASS — preserves GA4 events with no CI gap). - astro-poc/scripts/check-mdx.mjs + check-frontmatter.mjs validate every lesson's MDX body and YAML frontmatter (48/48 each).
The hub search index is now generated at build from the `lessons` content
collection (one source), restoring deep full-text search that the title+blurb-
only inline derivation had dropped.
- New prerendered endpoint src/pages/assets/search-index.js.ts emits
window.SEARCH_INDEX as /assets/search-index.js. Each lesson entry's searchable
`x` is the lowercased title + chips + quiz questions + interview Q&A + sources
+ the stripped MDX body prose (imports/JSX/HTML/entities/markdown removed).
- index.astro: dropped the inline SEARCH_INDEX derivation; loads the endpoint via
a plain inline-src <script> before the IIFE (CARD_DETAIL stays inline). Deleted
the stale prebuilt public/assets/search-index.js (collided with the route).
- 83 entries: 48 full-text lessons + 35 track reference pills. Verified a
body-only phrase ("exaggerates presence") now matches; entry shape {t,u,g,k,x,c}
unchanged so the ported run() search is untouched.
Scope: the 11 interview-bank pages + reference/glossary pages aren't in the
`lessons` collection yet (not migrated), so they're not indexed — same coverage
as the prior Astro derivation. Build green (49 pages), hub-hooks PASS (19),
check-mdx + check-frontmatter 48/48.
…g <Interview>); BaseLayout extraCss prop; [...slug] route multiplexes collections — 60 pages build green
…ayout, 3 CSS skins) — 70 pages build green; route multiplexes lessons+interview+reference
…ons (retires pandoc build-reference-pages.py); no-dep rehype heading-id plugin gives exact anchor parity — 90 pages build green
…y — 96 pages, FULL docs/ parity
…thored site `astro build` (outDir ../docs, emptyOutDir, build.format:'file') now GENERATES the published site into docs/; GitHub Pages serves it unchanged (main -> /docs). Full parity: all 96 URLs byte-identical, 0 real broken internal links, hub analytics/ARIA hooks intact (19/19), search index 94 entries incl. the 11 interview banks. Removed from docs/ (now generated, or sourced inside the Astro project): - hand-authored .html pages -> regenerated by Astro from src/content + components. - GLOSSARY/RESOURCES/README .md -> source of truth is now src/content/. - EPUB book build sources (docs/*/epub/**) -> EPUB generation is retired. Per-track diagram PNGs were recovered into public/*/diagrams/ so Astro re-emits them at the same URLs (the deep-dive/book pages reference them). Carried into the Astro project: robots.txt + sitemap.xml (public/); the interview banks folded into the search-index endpoint. Kept LOCAL-ONLY (recovered to notes/, gitignored): per-track MISSION.md / NOTES.md / learning-records/ and the deep-dive/fundamentals .md sources. The book lessons are now sourced from their converted .mdx; the .md are backed up locally in case the cleaner markdown is wanted as the future source. Project stays in astro-poc/ (source) -> docs/ (output). Note: check-broken-links.js still assumes relative links, so its base-absolute "misses" (/engineering-learning-hub/…) are GitHub-Pages-valid — follow-up to make it base-aware.
The Astro project moved from astro-poc/ to the repo root (package.json, astro.config.mjs with outDir now ./docs, src/, public/, tsconfig, pnpm files, scripts). docs/ stays the build output — GitHub Pages serves main -> /docs unchanged, output byte-identical (verified). - Removed the obsolete EPUB-reader cruft (reader.html, server.js, the root package.json/pnpm-lock — gitignored local) and dropped the /package.json + /pnpm-lock.yaml ignore anchors so the Astro manifests track at root. - Retired migration-superseded tooling: inject-analytics.js, inject-a11y.py, inject-lesson-feedback.py, inject-lesson-endnote.py (the layouts do this now), build-reference-pages.py (pandoc -> Astro glossary/resources collections), build-card-detail.js + wire-og-cards.js (old hub card system), README-ASTRO-POC.md. - Renamed the CommonJS validators to .cjs (check-broken-links, check-hub-hooks, validate-content) — required now that the root package.json is "type":"module". Build from root: `pnpm install && pnpm build` -> docs/ (96 pages, 0 real broken links, hub hooks PASS 19/19, mdx + frontmatter 48/48). Kept scripts: check-mdx.mjs, check-frontmatter.mjs, check-hub-hooks.cjs, check-broken-links.cjs, validate-content.cjs, build-og-cards.py.
Replaces the "Quietype Indigo" palette with an engineering-notebook
identity across both dark and light themes:
tokens.css (public/assets/ source + docs/assets/ output):
- Dark: obsidian surfaces (#0B1020), amber primary accent (#F59E0B),
cyan secondary (#22D3EE), sky-blue links (#38BDF8)
- Light: slate-white surfaces (#F8FAFC), dark amber (#D97706), teal secondary
- New canonical token names (--surface, --text, --border, --link, --code-bg…)
- Old names (--card, --ink, --line, --chip, --good, --bad, --warn) kept as
migration aliases — all 96 pages resolve with zero markup edits
index.astro: 10 hardcoded hex values replaced with tokens or brand constants:
hero gradient → always-dark obsidian, topbar blur → var(--bg), badges/pills
→ warning/bg tokens, OG card gradient → amber+cyan, active chip → var(--bg)
lesson.css: figure bg #fffff8 → var(--surface); links a{} → var(--link)
with hover state using var(--link-hover)
validate-content.cjs: exclude 404.html from indexable/sitemap gate
BaseLayout.astro: additive robots prop (default index,follow; 404 uses noindex)
SiteFooter.astro: avatar src base-absolute (was bare-relative, would 404 on nested pages)
src/pages/404.astro: branded 404 page (noindex, obsidian/amber themed, base-absolute links)
URLs like /ai-agents/lessons/0001-ai-agents-from-first-principles.html are replaced by /ai-agents/lessons/ai-agents-from-first-principles.html. Physical MDX filenames are unchanged (ordering preserved); only the routed path, cross-references, figure dirs, and sitemap are updated. Also fixes check-broken-links.cjs to resolve root-relative /engineering-learning-hub/ paths against docs/ (the site root).
Raw relative hrefs like "ai-agents/lessons/..." resolved from the document root when the hub page URL lacked a trailing slash, producing /ai-agents/... paths that 404 in both dev and production. All three link sites (lchip, start button, reference pills) now emit base-absolute paths.
…ping content in <p> Multi-line <sup><a ...>\n N\n</a></sup> patterns caused MDX to wrap the footnote number in a <p> block, making it render as a standalone block element instead of an inline superscript. Collapsed all 197 occurrences across all content files to single-line form.
- Add .github/workflows/deploy.yml: builds Astro site with pnpm, runs validate-content + check-broken-links, deploys to GitHub Pages on push to main (PRs get build+validate only, no deploy). - Add docs/ to .gitignore and untrack the 300+ generated HTML files — the build output is now produced by CI, not committed to the repo. - Remove content-validation.yml and link-check.yml — both are now superseded by the build job in deploy.yml (validation runs post-build on every push/PR, against the freshly built docs/ rather than committed files). After merging, update GitHub Pages source from Settings → Pages → Source: "Deploy from a branch" to Source: "GitHub Actions"
Make the site read as a calm, technical engineering knowledge base rather than an amber-heavy dashboard. Amber now signals only genuine highlights; slate carries structure and cyan carries the technical/per-track identity. Hub cards (src/pages/index.astro): - .tile left border: amber (--tc) -> neutral (--line); only the curated START HERE card ([data-here]) keeps the amber edge. - .start button: was a solid amber block on every card; now a quiet amber ghost (outline) by default, fills on hover. The two cards that ARE the primary action -- START HERE and in-progress Continue -- stay filled. - .tag keyword chips + .cnt lesson-count pill: amber-tinted -> neutral slate (--bg-soft / --chip / --line). - a.lchip i lesson-number badge: amber fill -> neutral chip with cyan numeral. Topic chips (lesson.css + Reference/Interview/Roadmap layouts): - .ltags .lt: amber fallback -> neutral slate. Drops the 6-color rainbow nth-child cycle in ReferenceLayout (was the loudest non-amber offender). Per-track tokens (tokens.css): - All 15 --track-* unified from amber to the secondary cyan accent, so card icon tints read as one cohesive technical identity, not a rainbow. No layout/markup changes, no new deps. Lesson body links already used --link (cyan). Build green; validate-content + check-broken-links both pass.
check-mdx.mjs and check-frontmatter.mjs were sweep tools for the bulk MDX conversion -- they report all syntax/frontmatter errors in one pass instead of build's one-at-a-time. The migration is done and the site builds green, so `astro build` (which parses MDX and frontmatter with the same compilers) now fully covers what they checked. Neither was wired into CI or package.json; they only referenced each other in comments. Kept: build-og-cards.py (regenerates committed OG card PNGs) and check-hub-hooks.cjs (the only guard that analytics/ARIA DOM hooks survive a component refactor). No CI change. Actions minutes are free on this public repo regardless.
Run `prettier --write .` across the tracked source so the about-to-be-added pre-commit hook starts from a clean baseline (otherwise the first edit to each stale file would carry an unrelated reformat blast). Markup/frontmatter only — the dense Tufte CSS lives in JS template strings (set:html), which Prettier does not touch, and public/assets + src/content + figures stay .prettierignore'd. Build green; validate-content + broken-links pass.
Adds a fast, format-only pre-commit gate so committed source stays
consistently formatted without anyone remembering to run `pnpm format`.
- husky: installs the git hook reproducibly via the `prepare` script, so a
fresh `pnpm install` wires it up automatically (no manual setup per clone).
- lint-staged: runs `prettier --write` on ONLY the staged files of the
formattable types ({js,cjs,mjs,ts,astro,json,md,yml,yaml}) and re-stages
them. Sub-second; never blocks a commit over whitespace.
Scope is deliberate: the heavy gates (astro build, validate-content,
check-broken-links) stay in CI — they need a built docs/ and are too slow to
run on every commit. .prettierignore now also excludes the generated docs/
outDir and the machine-managed pnpm-lock.yaml.
`prepare` runs on every `pnpm install`, so husky was being set up in CI too, where the pre-commit Prettier hook is pointless — CI never commits, and formatting is a local concern (CI validates the built site, it does not format source). Route `prepare` through .husky/install.mjs, which exits early when the CI env var is set. GitHub Actions exports CI=true automatically, so deploy.yml needs no change: `pnpm install --frozen-lockfile` simply no-ops the hook setup in CI and installs hooks as normal on local machines.
Adds a commit-msg git hook that lints every local commit message against @commitlint/config-conventional. The repo already follows this convention (feat / fix / chore / refactor / style / docs, optional (scope), trailing ! for breaking changes) — this makes it non-optional so the history stays machine-readable (changelog/release tooling, easy scanning). - commitlint.config.js extends config-conventional with no custom rules; defaults fit (header <= 100, known type, non-empty subject). Scopes stay free-form so it guides without nagging. - .husky/commit-msg runs `commitlint --edit "$1"`. Local-only: CI never commits and husky is not installed in CI (install.mjs CI guard), so deploy.yml is untouched.
Squash merges use the PR title as the commit subject, so a non-conventional title would land an off-convention commit on the default branch even though local commits are linted. This adds a lightweight workflow that runs the repo's own commitlint config against the PR title. - Separate from deploy.yml on purpose: it must fire on the `edited` event (title fixes), and adding that to the build workflow would rebuild the whole site on every title/description edit. `synchronize` is included so the check re-stamps each head SHA (needed if it becomes a required status check). - The title is attacker-controlled, so it is passed via the PR_TITLE env var and never interpolated into the shell command (no injection surface). - Reuses commitlint.config.js — one source of truth for local commits AND PR titles. Self-skips nothing in CI: husky isn't installed (CI guard), and this job only runs `commitlint`, not the build.
…text Small follow-up pass on the Obsidian+Amber+Cyan palette. Most of the prompt's targets (neutral card borders with amber only on the START HERE card, cyan links, neutral tags, cyan hero tagline) were already in place from the prior refinement; this fixes only the three things still off: - Active filter chip (.fchip.is-active): dark ink (#111827) instead of white on the amber fill — calmer, not glowing, and fixes a real light-theme AA failure (white-on-amber was ~2.9:1). One value works in both themes, so the per-theme override is dropped; explicit box-shadow:none so it never reads as bulky. Adds a softer --accent-soft focus ring on the chips. - Normal Start button (.start ghost): was muddy/brown — amber 12% fill over the navy surface. Now brighter --accent-soft text with a lighter fill (9%) and border (26%) so it reads as a clean outlined CTA. Hover/featured/Continue fills are unchanged. - Dark-theme muted text: --text-muted #A7B0C0 -> #B6C0D1, --text-soft #7C8798 -> #8D98AB. Card descriptions/secondary text read easier while staying secondary; light theme untouched. No layout/content/markup changes. Build green; validate-content + broken-links pass.
The bottom sheet (openSheet) reads each lesson chip's literal href attribute
and looks it up in window.CARD_DETAIL to show the lesson's blurb. After lesson
hrefs gained the GitHub-Pages base prefix (TrackCard renders
`${base}${lesson.href}`), the DOM href became
"/engineering-learning-hub/<track>/lessons/<slug>.html" while CARD_DETAIL was
still keyed by the base-less "<track>/lessons/<slug>.html" — so every
DETAIL[href] lookup missed and no .cs-d description was appended.
Key cardDetail with the same `${base}${lesson.href}` the DOM uses, so the key
space matches the chip hrefs. Verified the built CARD_DETAIL key and the
a.lchip href are now identical. Single inline source (derived from tracks)
unchanged otherwise. Build + validate-content + broken-links green.
The card-detail sheet shows a per-lesson blurb only for lessons present in CARD_DETAIL (derived from lessons[].blurb). The 6 single-lesson AI-eng tracks (ai-evaluation, production-ai-architecture, ai-security, ai-infrastructure, domain-agent-design, advanced-ai-systems) never had a blurb — not a migration regression (the old prebuilt map had no lesson keys for them either), but it left each of those cards showing one lesson row with no description. Add a blurb to each, sourced verbatim from that lesson's own frontmatter `description` (the same kind of text every other blurb already uses — no new copy invented). Every lesson chip now resolves to a blurb: built CARD_DETAIL has 48 keys for 48 lesson chips, 0 missing. Build + both gates green.
public/assets/card-detail.js was the old pre-built CARD_DETAIL map (stale 0NNN--prefixed keys). Nothing loads it — index.astro derives window.CARD_DETAIL inline from lessons[].blurb — yet it still shipped to docs/assets/ as dead weight. Remove it. Fix the comments that still pointed at it: - track.ts (x2): blurb is emitted into CARD_DETAIL from lessons[].blurb inline, not "folded in from card-detail.js". - tracks.ts: drop the card-detail.js reference and the now-false claim that the 6 single-lesson tracks carry no blurb (they all do now). The one surviving mention (index.astro) correctly says there is NO card-detail.js asset, so it stays. Build + validate-content + broken-links green.
Follow-up to removing the orphan card-detail.js: the doc comments still said blurb was "folded in from card-detail.js" and that the 6 single-lesson tracks carry no blurb. Both are now false — blurb is authored in lessons[].blurb and emitted into window.CARD_DETAIL inline by index.astro, and every lesson now has one. (These two files were missed by the prior commit because the `git rm` pathspec aborted the combined `git add`.)
The 5-volume backend-mastery series was labelled "Book 1"…"Book 5" in roadmap
titles, lesson kickers/metalines, cross-references, diagram captions and the
EPUB/deep-dive cover art. That collided with the site's existing term "Track"
(a subject area — the hub has ~15), and the covers literally read
"A GUIDED LEARNING TRACK · BOOK 4". Rename the volume axis to "Part N" so the
two levels stay distinct and consistent: Track = subject, Part = volume,
Lesson = chapter within.
Scope (35 files): numbered "Book N"/"Books N" and all-caps "BOOK N" → Part/PART,
plus the navigational lowercase references (roadmap intros "this book →
this part", "next book → next part", "five-book series", "four books of theory").
Deliberately NOT touched: the calendar verb ("Book 30 min with Sam"), EPUB
filenames (book-N-….epub), facebook/rocksdb URLs, the REST hotel-booking
domain, runbook/bookmark/textbook, real books (the Google SRE Book, REST API
design books), idioms ("oldest bug in the book", "open-book exam"), librarian
world-analogies, and in-prose rhetorical metaphors ("holds the whole book")
where "Part" would read worse.
Supersedes the earlier buggy Book→Lesson find-replace (discarded — it collided
"Book 1, Lesson 8" into "Lesson 1, Lesson 8" and clobbered the calendar verb).
Build + validate-content + broken-links green.
Replace implicit array-position ordering with a deterministic, documented
hierarchy in one place (src/data/sortTracks.ts), so the grid and lesson lists
never drift and publish date is never the primary key.
Order:
- Tracks (sortTracksForHub): startHere first → manual `order` asc →
publishedAt desc → title asc (stable fallback).
- Lessons (sortLessons): `order` asc → publishedAt asc → title asc; an unset
`order` keeps the authored array position, which is already the learning
sequence — so lessons stay learning-first, never newest-first.
- sortByLatest (publishedAt desc → updatedAt desc → title) is provided for a
future date-first "Latest" section; no such section exists today.
Data model (src/schema/track.ts):
- Add `order` (optional int) to tracks and lessons — the manual sort key,
decoupled from array source position.
- Rename track `added` → `publishedAt` (first-published date). The DOM
attribute stays `data-added`, so the NEW-pill/analytics contract is unchanged
(TrackCard now emits data-added={track.publishedAt}).
- Add optional `updatedAt` (freshness) for the Latest sort.
- publishedAt is a TIEBREAK only and is never used to drive learning sequence.
tracks.ts: seed explicit order 1..15 (matching prior visual order) and set
Context Engineering startHere:true so both recommended entry points float first
(matches the hero copy that recommends both; previously only AI Agents was
flagged). Lessons keep authored order — no per-lesson churn.
Every comparator ends in a title compare, so ordering is fully deterministic
and stable regardless of engine sort. Build + validate-content + broken-links
green; verified render order (ai-agents, context-engineering, then order 3..15)
and learning-first lessons (★ fundamentals first in deep-dive tracks).
… cards)
A learning hub, not a blog: hub track cards stay learning-path-first (lesson
count, read time, difficulty, NEW pill) with NO published date — dates would be
visual noise there. Dates belong on the detail pages and date-first surfaces.
- Add optional publishedAt/updatedAt (YYYY-MM-DD) to lessonMetaSchema.
- LessonLayout renders them in the masthead ONLY when present ("if available"),
as a `.lesson-dates` line below the meta line, using semantic
<time datetime="…"> elements (good for SEO / article metadata / future RSS).
Date is formatted without Date() so there's no timezone shift — build-stable.
- Seed publishedAt on the 6 single-lesson AI tracks (accurate: their track was
published 2026-06-21). All other lessons stay dateless until a date is added.
Track cards unchanged — they carry data-added (the NEW-pill attribute) but
display no date. publishedAt remains a sort tiebreak (see sortTracks.ts).
NOT in this change (net-new, flagged for follow-up): a date-first Latest/recent
page and a changelog/RSS feed; extending the date line to the reference /
interview / roadmap article layouts. Build + validate-content + broken-links green.
…youts Extend the "if available" published/updated date line from lesson pages to the reference, interview, and roadmap detail pages — still NEVER on hub cards. - New src/components/PageDates.astro: renders the date line only when a date is present, with semantic <time datetime> (SEO/RSS/article metadata). Inline- styled with --muted so it reads correctly in both the lesson.css context and the three Tufte serif inline-style contexts (no per-layout .pubdates rule). - New src/lib/formatDate.ts: the YYYY-MM-DD → "Jun 21, 2026" formatter (no Date(), so no timezone shift), shared instead of inlined per layout. - Add optional publishedAt/updatedAt to reference/interview/roadmap schemas. - LessonLayout now uses PageDates too (drops its inline block + the one-off .lesson-dates rule in lesson.css). No page carries a reference/interview/roadmap date yet, so those stay bare until one is set. Build + validate-content + broken-links green.
Populate publishedAt: '2026-06-21' (the real launch date, matching the existing 6 AI tracks) on all 68 remaining lesson, reference, interview and roadmap pages so every detail page now shows an honest "Published …" line via PageDates. Honest, not backdated: the repo is public, so any publishedAt is checkable against git history — a real recent date is both accurate and more credible than a fabricated longer timeline. updatedAt is left unset (the field/render are wired; set it only when a page is actually revised, so it isn't redundant noise). Hub track cards remain dateless. Build + validate-content + broken-links green.
Set publishedAt on the existing corpus to a one-year spread (2025-07 → 2026-06) instead of a single launch date, so the hub reads as a year of work. Dates are assigned by learning order (track `order` 1..15): foundational/flagship tracks oldest, the deep-dive series newest; all pages in a track share the track's date. Applied to all 74 detail pages + the 15 track-level publishedAt in tracks.ts, so the NEW pill / sort tiebreak stay consistent with the page dates. Going forward, NEW posts take their real git-history date (this is a one-time seed of the back-catalogue, not a recurring fabrication). Grid order is unaffected (publishedAt is only a sort tiebreak after startHere + order); hub cards remain dateless. Build + validate-content + broken-links green.
Undo the 1-year backdated spread (a5dc6c0). All lesson/article pages and the track-level publishedAt go back to the actual publish date, 2026-06-21, so the displayed dates match reality (and the public git history). New posts will carry their own real date going forward. Build + validate-content + broken-links green.
A chronological "Latest" page: lists every lesson, reference, interview bank and roadmap newest-first (publishedAt desc → updatedAt desc → title), grouped by month. The hub grid stays learning-path-first; this is the date-first view, built from the content collections so it stays in sync automatically. - src/pages/latest.astro: getCollection over the 4 dated collections, normalized + sorted + grouped; indexable (BaseLayout head + analytics), scoped styles. - sitemap.xml: list /latest.html. - SiteFooter: add a "Latest" quick link for discoverability. With dates uniform today it shows one "June 2026" group (74 items, title- ordered); as new content lands with newer publishedAt it floats to the top. Build (98 pages) + validate-content + broken-links green.
A build-time RSS feed of all dated content (lessons, reference, interview, roadmap) newest-first — the feed-reader counterpart to /latest. - src/pages/rss.xml.ts: prerendered endpoint via @astrojs/rss; item links are base-prefixed and resolved against `site`; pubDate from publishedAt (UTC, build-deterministic); sorted publishedAt desc → title. - BaseLayout: <link rel="alternate" type="application/rss+xml"> autodiscovery on every page. SiteFooter: visible "RSS" quick link. - Adds the @astrojs/rss dependency. Also commits pnpm-lock.yaml, which had drifted: the @commitlint deps (added in a8ee263) updated package.json but the lockfile was never committed — so CI's `pnpm install --frozen-lockfile` would have failed. The lockfile now matches package.json (verified: frozen-install exits 0) for husky, lint-staged, commitlint and @astrojs/rss. Build (98 pages, /rss.xml = 74 items) + validate-content + broken-links green.
Add theme-independent typography tokens to tokens.css (the single source of truth, linked by all three page families): --font-sans / --font-mono system stacks, a --text-xs..4xl scale, and --leading-* / --tracking-* tokens. Zero external font dependencies. Apply, without redesigning any family: - global code,kbd,samp,pre -> var(--font-mono) - lesson.css: body -> var(--font-sans) + optimizeLegibility; code/pre -> mono; relax prose leading to 1.75 scoped to #main > p so compact card/callout/quiz text keeps its tighter metrics. - index.astro hub body -> var(--font-sans) + optimizeLegibility. Tufte serif pages (reference/interview/roadmap) keep their serif design; the sans body change is confined to the sans family. Headings already carried tight line-height + negative tracking and 17px root avoids tiny mobile text, so both were left compliant. Build green; both content + link gates pass.
Cut the PR template to What & why + a three-line CI checklist. Drop the redundant "Type of change" block (the PR-title commitlint gate already encodes the type) and the content-specific sub-checklist. Fix the stale gate paths: check-broken-links.js / validate-content.js -> .cjs.
Document and enforce the feature -> develop -> main flow. PRs into develop or main now run build + validate only (pull_request scoped to [main, develop]); the deploy job stays gated to push-on-main, so a PR to develop never deploys. Deploy fires only when a PR merges into main.
Default Consent Mode v2 analytics_storage to 'denied' so no analytics cookie (_ga) is set before consent — aligns the code with the documented default-denied convention (was granted-by-default). Add a small theme-aware banner: a bottom-right card on web, a full-width bottom sheet on mobile, with Accept analytics / Reject / ✕-dismiss. The choice persists in localStorage 'sd:consent' so it shows once and never nags again. Privacy page gains opt-in wording + an Enable/Disable analytics control as the re-entry point; the footer gains a Privacy link.
figure img/svg used background:var(--surface), which is dark in dark mode, so the dark ink of the paper-style diagrams was swallowed (titles, labels, arrows vanished). Affects all 363 diagrams — 89 PNGs + 274 figure-wrapped SVG components. Add a theme-independent --diagram-bg (#fff, = the current light-mode surface) and use it for figure backdrops in both themes; light mode is pixel-identical, dark mode now renders diagrams the way light mode already does. Also fix one inline <pre> (hardcoded cream bg) whose inherited text went light-on-cream in dark mode by hardcoding a dark ink color.
Set Context Engineering startHere: false so only AI Agents carries the curated START HERE highlight (amber edge + badge) on the hub. Context Engineering keeps its order:2 placement in the grid.
White / near-white text on var(--accent) failed contrast in light theme (--accent is the dark amber #D97706 there, ~3.1:1) and dark ink on it read muddy (~5.4:1); the filled Start/Continue buttons' dark-mode dimmed override put light text on a muted mid-tone (~3.4:1). Add a reusable --on-accent ink token and standardize every filled-amber surface to dark ink on the brighter --accent-soft (~8.3:1 light, ~10.6:1 dark): filter chip, .btn (was failing both themes), level-number circles (x35 lessons), START HERE badge, NEW pill, amber Start CTA, consent Accept button, reference flag, 404 button, dead iqtab rule. Theme-split the green Continue button: white on dark green in light (4.9:1), dark ink on bright green in dark (9.2:1). Brand logo glyph left as-is. All touched elements now pass WCAG AA in both themes.
Dark-mode topic chips fell back to var(--accent) (amber) while the light base used var(--text-muted) (neutral), so chips read amber in dark and gray in light. Point the dark-override fallback at --text-muted so chips are neutral in both themes — one shared rule that applies to every lesson and the hub. Also fix the cyan variant of the white-on-color pattern (white text on --track-rest, ~3.7:1 in light): the interview "design" tab and the reference .tag badge now use dark ink. And two per-lesson _*.css files that shadowed the lesson.css level-number-circle fix.
…esheets _0003 and _0005 redefined .lvl/.lvltop/.num/.nav as bare selectors, but lesson.css's #main .lvltop .num / #main .lvl etc. outrank them (higher specificity), so those copies never applied — dead code that also shadowed the single source of truth conceptually. Remove them so the level-block and nav styling (incl. the amber-contrast fix) comes only from lesson.css. Genuinely bespoke rules in these files (dark .rule band, 2-col .myth grid, custom details.iq/.mc markers, .cheat/.split/.pat/.toc, .lvltop h2, .tagline) are kept — they are not duplicates. Zero rendering change.
Amber accent TEXT (hubbar links, kicker, TOC rail, Q-numbers, ghost "Reveal" button, section accents) sat at only ~3.04:1 on the light page background — the brand --accent (#D97706) is too light to read as text and fails AA. Darken the LIGHT --accent to #A85508 (5.06:1) and update its muted tint; dark theme is untouched (8.82:1). One token, applies everywhere amber text appears. Also move the hub ghost-Start label from --accent-soft (even lighter, ~2:1) to --accent so it reads in light too. Filled amber surfaces are unaffected (they use --accent-soft + --on-accent dark ink). --warning keeps #D97706. Corrected the stale contrast figures in the tokens header comment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Typography pass + repo hygiene, in three commits:
tokens.css(
--font-sans/--font-mono,--text-*scale,--leading-*,--tracking-*);long-form prose leading on lesson bodies; mono for all code-like elements.
Tufte serif pages (reference/interview/roadmap) keep their serif design.
content sub-checklist; fix stale
.js->.cjsgate paths).pull_requesttrigger to[main, develop]; deploystays gated to push-on-main, so PRs into develop never deploy.
Checklist
node scripts/check-broken-links.cjspassesnode scripts/validate-content.cjspassesdocs/sitemap.xml(n/a — no new pages)