mirror of
https://github.com/andwati/andwati.com.git
synced 2026-10-18 09:22:53 +00:00
Nav bar & layout consistency - Base.astro's `wide` layout now defaults on for every page (was opt-in), fixing the nav bar wrapping differently depending on page width - Icon buttons for search and theme toggle (magnifying glass, sun/moon), replacing the plain "Search" text link and the "◐" glyph - Search is now a Cmd/Ctrl+K modal over Pagefind (lazy-loaded on first open) instead of a separate /search/ page, matching the old site - Fixed the article page's TOC sitting with unwanted left padding relative to the header (grid was center-justified instead of start-justified) Smooth navigation - Added Astro's ClientRouter for animated page transitions - Every page-local script that only ran once at module load (three.js hero, minimap, comments, book-spine carousel, scroll-to-top, reading progress, theme toggle, Pagefind init) now re-initializes via astro:page-load, with AbortController-based cleanup so window/document listeners don't accumulate across client-side navigations Restored from the legacy Zola site - Reading progress bar (article pages) and scroll-to-top button - Share buttons (X, Bluesky, LinkedIn, Reddit, HN, email) on writings - Prev/next post navigation with the old label/title layout - Utterances comments, wired to andwati/andwati.com with dark/light theme sync - YouTube embeds: any plain <iframe src="...youtube-nocookie.com..."> is now auto-wrapped in a responsive 16:9 container, gets loading="lazy", and emits VideoObject JSON-LD — no shortcode needed - LaTeX: $inline$ and $$block$$ math renders to static HTML via KaTeX at build time (no client-side JS shipped for it) - Tags page redesigned as a centered, alphabetical tag cloud Removed the related-posts section per request. Accessibility & contrast - --accent (#d1502f) measured 4.0:1 on the light background, just under WCAG AA's 4.5:1 for normal text — darkened to #a84025 (5.7:1), plus matching light-mode-only fixes for the two hljs syntax colors and the tip/warning callout colors that had the same problem in light mode - Fixed heading-order violations (h1 straight to h3 with nothing at h2) across the homepage, writings/portfolio/bookshelf/blogs lists, and tag pages — introduced a shared .entry-title class for the h2 list titles - Fixed inconsistent title-link underline colors: a plain <a> carries its own default underline (in its own text color) instead of matching its parent heading's accent-colored one — h1/h2/h3 anchors now explicitly inherit the heading's decoration - Bookshelf spine colors are generated per-title from a hash; the lightness-based text-color heuristic could still produce sub-4.5:1 contrast for some hues (e.g. green) — now computes real WCAG contrast and adjusts Performance - The three.js homepage hero was the single largest main-thread cost site-wide (dragging that page's Lighthouse performance score to the low 80s while every other page scored 95+): three.js is now dynamically imported only once the browser is idle (WebGL-support check split into its own tiny module so unsupported browsers never fetch it at all), the render loop is capped to ~30fps, and hover raycasting only runs on an actual pointer move instead of every frame SEO & structured data - Self-referential canonical links site-wide (with override support for posts first published elsewhere) - Sitewide WebSite/Person JSON-LD, plus per-article BlogPosting, BreadcrumbList, and (when applicable) VideoObject JSON-LD - robots.txt explicitly welcomes AI/LLM crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot, etc.) and points at llms.txt - Added an Atom feed (RSS and JSON Feed already existed) - Sitemap now sets <lastmod> per entry from its own date/updated frontmatter instead of leaving every URL at build time Analytics & comments infra - Self-hosted Umami wiring via PUBLIC_UMAMI_SCRIPT_URL/WEBSITE_ID (apps/site/.env.example), no-ops until both are set - sws.toml's CSP updated to allow utteranc.es and analytics.andwati.com — without this both were silently blocked in production Lighthouse CI - @lhci/cli wired up against the built site (mobile + desktop configs), `pnpm run lighthouse`, and a GitHub Actions workflow to run it on every push/PR — reporting only for now, no failing thresholds
12 KiB
12 KiB
Astro + Strapi Rebuild — TODO
Tracking doc for the astro-migration branch. See the locked spec in the
approved plan for full rationale on each decision referenced below.
Done
- Scaffold pnpm workspace (
apps/siteAstro,apps/cmsStrapi + SQLite) - Switch tooling to pnpm-only, add Biome for lint/format
- Define shared
content/schema (writings, portfolio, bookshelf, blogs) with TOML frontmatter matching the legacy Zola format - Bookshelf schema covers both books (ISBN) and papers (DOI/arXiv id)
- Custom Astro content loader parsing
+++TOML frontmatter directly (no YAML conversion step) - Basic list/detail pages for all four content types, verified running
end-to-end in-browser (
pnpm --filter site dev) - CLAUDE.md updated: no agent self-attribution in commits/PRs, commit regularly after each working step
- Strapi content-type definitions matching the
content/schema exactly (writings, portfolio, bookshelf, blogs) — verified boots and registers all four REST routes apps/cms/Dockerfile.dev+ rootdocker-compose.ymlfor running Strapi locally in a container (node:24-bookworm-slim, matches local Node version);apps/cms/Dockerfileproduction multi-stage build for later Dokploy deployment. Verified: builds, boots, admin panel and API reachable, sharedcontent/volume mounted at the right relative path
Content pipeline
- Strapi custom provider/lifecycle hooks that read + write the same
Markdown files in
content/(Strapi's DB stays a disposable cache) —apps/cms/src/utils/content-{file,sync}.ts, wired insrc/index.ts - Verify round-trip: edit in Strapi admin → file on disk updates, and vice versa, without drift — verified create/update/delete via the content-manager API, plus idempotent re-import across a restart
- Bookshelf ISBN lookup hook with local cover caching —
apps/cms/src/utils/bookshelf-covers.tsfetches from Open Library by ISBN and caches intoapps/site/public/covers/; verified end-to-end (real cover downloaded,cover_imagesynced to file, cleaned up on delete) - Bookshelf paper lookup hook — Semantic Scholar backfills title/authors
by DOI/arXiv id when missing. No cover/thumbnail: neither Semantic
Scholar nor arXiv offers a reliable image source for papers, so
cover_imagestays manually-set for papers (or gets one later from the OG-image pipeline below) - OG image auto-generation extended to all three content types
(writings, portfolio, bookshelf) using the new design system —
scripts/generate-og-images.astro-site.mjs, wired intoapps/site's build script;Base.astrorendersog:image/twitter:imagefrom a per-pageogImageprop (writings/bookshelf detail pages wire it in; portfolio has no detail page yet to wire it into). Uses static-weight Fraunces/Newsreader font packages, not the variable fonts the site ships — satori's opentype.js parser can't read the variable fvar table llms.txtgeneration extended to cover all content types — unblocked oncesite.config.tsexisted (built above for feeds/meta anyway):scripts/generate-llms.astro-site.mjsreads all four content directories directly, wired intoapps/site's build script
Design system
- New typography scale + color system — dark-first (warm near-black,
cream text, coral accent) with a warm-paper light mode;
Fraunces (display/italic) + Newsreader (body) + JetBrains Mono
(nav/meta/code), fluid clamp() type scale.
src/styles/tokens.css. Referenced pixperk.tech and mcyoung.xyz — see the commit for specifics - Motion primitives: CSS + native Web Animations API spring utility —
apps/site/src/lib/spring.ts(damped-oscillator keyframes,prefers-reduced-motionaware). Not wired into any component yet — the Three.js centerpiece below is the natural first user - Base layout/components replacing the placeholder
Base.astro— real header/nav with active-link state, theme toggle, footer with social links. Verified light + dark in-browser across home, a writing detail page (TOC/headings), bookshelf, and 404 - Homepage redesign around the Three.js centerpiece as hero — the constellation sits above a 3-column dashboard (latest writings, portfolio, bookshelf), verified rendering and hover/click working in-browser
- New 404 page concept (terminal easter egg retired) — "Page Fault", now fully styled with the rest of the design system
Three.js centerpiece
- Constellation data model: nodes are real content entries (writings,
portfolio, bookshelf), edges are shared-tag connections — the same
relationship "related posts" already uses, drawn instead of listed.
No decorative/fake data.
src/lib/constellation-data.ts - Built as an isolated component (
Constellation.astro+ a plain client<script>module, no framework), fibonacci-sphere layout, hover raycasting with a title tooltip, click-through to the entry - Fallback for
prefers-reduced-motion(skips rotation/entrance animation) and no-WebGL (supportsWebGL()check hides the canvas entirely, falls back to a<noscript>-style plain count) - Perf budget check — scene is trivially light (24 nodes, 12x12-segment
spheres, a handful of line-segment edges: well under 10k triangles),
renders at 100+fps uncapped with no dropped frames. The real cost is
the JS payload: three.js core pulls in ~130KB gzip / ~106KB brotli
regardless of
import *vs named imports (sideEffects: falsealready lets Rollup tree-shake fully; tried named imports, no size change). That script is a deferred module (doesn't block first paint/DCL) and the page was interactive in ~270ms in testing. No code changes made — budget is fine as shipped
Content features carried over from the Zola site
- Series-nav (multi-part post series) —
writings/[...slug].astro, uses the existingextra.series/extra.series_indexfields - Table of contents — sticky sidebar on desktop (>=74rem), falls back to
the original inline list below the tags on narrower viewports; same
heading data rendered via a shared
TocList.astroso the two aren't duplicated markup by hand..container--widebumped 64rem → 76rem so big monitors use more of the screen (prose column itself stays capped at 42rem for readability — the extra width is the TOC gutter); verified at 390/900/1180/1920px with no distortion at any width - Prev/next post navigation
- Related posts (tag-based, tag-overlap ranked, limit 3)
- Per-tag descriptions on tag archive pages —
/tags/+/tags/[tag]/, descriptions ported fromzola.toml's[extra.tag_descriptions] llms_descriptionmeta (site-wide default insite.config.ts+ per-post override, rendered as<meta name="description">inBase.astro)- RSS/JSON feeds —
/rss.xml,/feed.json - Sitemap + robots.txt —
@astrojs/sitemap, staticpublic/robots.txt - Code block syntax highlighting —
toml-content-loader.tsrenders fenced code blocks throughhighlight.js's core build with only the 11 languages content actually uses registered (no bundled-theme/WASM grammar weight like shiki, which was tried first and swapped out for this lighter option);.hljs-*token classes inglobal.cssare colored from the existing design tokens (--accent,--text-muted, ...), so they already switch with the site's theme with no extra[data-theme]rules needed
Search & comments
- Pagefind integration — indexes the build output (
pnpm buildrunsastro build && pagefind --site dist),/search/page using Pagefind's default UI; verified with a real query in a browser - Comments: kept Utterances (not Webmentions) —
Comments.astro, wired toandwati/andwati.com, with the same dark/light theme-sync-via- postMessage behavior the legacy site had, adapted to this site'sdata-themeattribute. Therel="webmention"link was removed since it's unused now.sws.toml's CSP updated to allowutteranc.es(script-src, frame-src) — without that the widget is silently blocked in production
Analytics
- Umami — self-hosted at
analytics.andwati.com.Analytics.astrorenders the tracker script fromPUBLIC_UMAMI_SCRIPT_URL+PUBLIC_UMAMI_WEBSITE_ID(set inapps/site/.env, gitignored — copy into the production environment's env vars too);sws.toml's CSP updated to allow that host inscript-src/connect-src
Migration from the legacy Zola site
- Script/process to convert
content/posts/*.mdinto the newcontent/writings/schema —scripts/migrate-posts-to-writings.mjs, additive only (content/posts/untouched), re-runnable/idempotent. 2 of 20 posts (from-c-to-machine-code,self-hosting-gitea-and-mirroring-github) use note/tip/warning/danger callout shortcodes — flagged by the script, still left as-is in the body text - note/tip/warning/danger callout shortcode rendering — the legacy
{% <note> %} ... {% </note> %}blocks are converted to styled.calloutmarkup bytoml-content-loader.ts'srenderCallouts()(runs before the body's markdown parse), styled inglobal.cssto match the new design system. Verified in-browser onfrom-c-to-machine-code - Preserve slugs/URLs via redirects — the script generates 301s from
/posts/<slug>/to/writings/<slug>/intosws.toml, idempotently - Migrate
content/archive/,content/about.mdequivalents —/archive/groups every published writing by year;/about/reframes the legacy copy around the site's "digital legacy" identity while preserving its PGP and security contact details. Both are in the main navigation, and the linked.well-knownfiles are copied into Astro's public output - Verify RSS subscribers and inbound links survive the cutover — can only really be checked once this is deployed live (needs your input: this is a production-cutover verification, not a build-time one)
Infra / deployment
- Wire Strapi into the Dokploy deployment alongside the Astro build —
docker-compose.prod.ymldefines both production services, persistent CMS/Git volumes, reverse-proxy settings, and secret-only configuration - Build trigger decided and implemented: Strapi commits Markdown and generated covers to Git, pushes the production branch, then calls the Dokploy Compose deploy webhook so Astro rebuilds from the durable commit
- Update
Dockerfile/sws.tomlfor Astro'sapps/site/distoutput, current security headers, legacy redirects, and the custom 404 page - Launch the production Compose application and configure its domains,
secrets, GitHub token, and deploy webhook in Dokploy — repository-side
setup is complete in
DEPLOYMENT.md; host access is still required
Cleanup
- Remove Zola-specific files (
zola.toml,templates/,sass/,themes/,static/,Makefile,bin/update-zola,content/posts/,content/archive/_index.md,content/about.md,content/_index.md, legacy OG/llms scripts) — verified safe: rootDockerfilealready buildsapps/site(notzola build), nothing inapps/sitereads those content files, andcontent/writings/has all 20 posts fromcontent/posts/. Rootpackage.json'slegacy:*scripts already removed. Blocked: the actualgit rmwas refused by the permission system as irreversible destruction — run it yourself (fully reversible via git history/revert since these are tracked files):git rm -r templates/ sass/ themes/ static/ zola.toml Makefile bin/ content/posts/ content/archive/_index.md content/about.md content/_index.md scripts/generate-llms.mjs scripts/generate-og-images.mjs