Skip to content

feat(typography): type-scale tokens, simpler PR template, main-only deploy#33

Merged
dineshbyte merged 52 commits into
developfrom
poc/astro-migration
Jun 22, 2026
Merged

feat(typography): type-scale tokens, simpler PR template, main-only deploy#33
dineshbyte merged 52 commits into
developfrom
poc/astro-migration

Conversation

@dineshbyte

Copy link
Copy Markdown
Owner

What & why

Typography pass + repo hygiene, in three commits:

  • feat(typography): theme-independent type tokens in 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.
  • docs(github): simplify the PR template (drop the redundant type block and
    content sub-checklist; fix stale .js -> .cjs gate paths).
  • ci(deploy): scope the pull_request trigger to [main, develop]; deploy
    stays gated to push-on-main, so PRs into develop never deploy.

Checklist

  • node scripts/check-broken-links.cjs passes
  • node scripts/validate-content.cjs passes
  • New/renamed pages added to docs/sitemap.xml (n/a — no new pages)

… 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
…-LD), Sources, Rehearse, Steps; Quiz id optional
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
…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.
@dineshbyte dineshbyte merged commit 8a8487f into develop Jun 22, 2026
3 checks passed
@dineshbyte dineshbyte deleted the poc/astro-migration branch June 22, 2026 20:09
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