Commit Graph
237 Commits
Author SHA1 Message Date
andwati 9cd92d623d Add pnpm dev to run the Astro site and Strapi CMS together
Root "dev" script uses concurrently to run both apps with labeled,
color-coded output ([site]/[cms]) instead of needing two terminals.
The per-app dev:site/dev:cms scripts stay for when you only want one.
2026-08-31 08:06:54 +03:00
andwati bf29139cef Check off design system items in TODO 2026-08-31 08:04:11 +03:00
andwati 9392d45f53 Build the design system: typography, color tokens, real Base layout
References: pixperk.tech (dark, warm off-white text, serif-italic +
mono mixed typography, personal dashboard modules) and mcyoung.xyz
(meta line with reading time, subtle accent underline on titles).

- src/styles/tokens.css: dark-first color palette with a warm-paper
  light mode (data-theme override + prefers-color-scheme, toggle wins
  either direction), fluid clamp() type scale, spacing/radius scale.
- Fonts: Fraunces (display/headings, italic), Newsreader (body,
  long-form readability), JetBrains Mono (nav/meta/code) — all
  self-hosted via @fontsource, continuing the mono choice already
  used in the legacy OG-image pipeline.
- Base.astro rewritten: real header/nav with active-link state, a
  theme toggle (persisted to localStorage, no-flash inline script),
  footer with social links (ported from zola.toml), replacing the
  scaffold placeholder.
- Reading time: toml-content-loader.ts computes wordCount/
  readingMinutes from the raw body at load time, surfaced in the
  writings list and detail meta line.
- Bug fix: the marked heading renderer was using raw token.text for
  both the visible heading and the TOC, so a heading like
  "**Registers**" rendered literal asterisks instead of bold — now
  renders inline tokens properly and strips markdown for the
  plain-text TOC label/slug.

Verified in-browser (light + dark) across home, a migrated writing
with TOC/headings, bookshelf, and 404.
2026-08-31 08:03:50 +03:00
andwati 3d4b2fcd64 Normalize multi-word tags to kebab-case
"subresource integrity", "task scheduler", and "conventional commits"
had spaces, producing broken-looking /tags/<tag>/ URLs. Also fixes a
silent bug: site.config.ts's tagDescriptions already keyed
"subresource-integrity" with a hyphen, so the per-tag description
never actually matched the un-normalized tag.

Added normalizeTag() to migrate-posts-to-writings.mjs so re-running
the migration script doesn't reintroduce the spaces from the
untouched content/posts/ originals.
2026-08-31 07:47:54 +03:00
andwati 454e3459a1 Annotate remaining TODO items that need input vs. infra access 2026-08-30 19:19:27 +03:00
andwati 2fdd0052b5 Extend llms.txt to all content types; fix a gitignore bug
scripts/generate-llms.astro-site.mjs covers writings, portfolio,
bookshelf, and blogs (unblocked now that site.config.ts exists from
the feeds/meta work), wired into apps/site's build script.

Also fixes .gitignore: the bare `public` pattern (meant to ignore the
Zola build's root-level output directory) was matching at any depth,
so apps/site/public/ and apps/cms/public/ were being silently
excluded from every commit despite `git add` reporting them as
staged for directory adds (the ignore-file warning only surfaces for
an explicitly named path, not a recursive directory add — easy to
miss). Scoped to `/public` so only the root output dir is ignored.
Picks up robots.txt, the Astro scaffold's favicon, llms.txt, and
Strapi's own public/robots.txt, none of which were actually tracked
until now.
2026-08-30 19:18:43 +03:00
andwati 529a0efbfe Add a 404 page concept and a spring-physics motion primitive
404: "Page Fault" — a SIGSEGV/memory-fault framing distinct from the
retired terminal easter egg, still fitting the CS-flavored identity.
Plain/unstyled for now like the rest of the placeholder layout;
restyle once the design system lands.

src/lib/spring.ts: a small CSS+Web Animations API spring utility
(damped harmonic oscillator -> keyframe progress values), per the
locked "no animation library" decision. Not wired into any component
yet — this is the foundational primitive the design system's motion
work will build on, kept free of any visual/component opinions.
2026-08-30 19:12:44 +03:00
andwati 1b7c4039e3 Migrate legacy Zola posts into content/writings/ (additive)
scripts/migrate-posts-to-writings.mjs converts all 20 posts under
content/posts/ into the new writings schema — normalizes dates to
bare TOML local-dates, converts the one unambiguous Zola shortcode in
use (youtube embeds) to a plain <iframe>, and strips the now-meaningless
{% raw %}/{% endraw %} escape tags. content/posts/ itself is untouched;
this only adds content/writings/*.

Also generates 301 redirects from the old /posts/<slug>/ URLs to the
new /writings/<slug>/ ones into sws.toml, inside marker comments so
re-running the script regenerates that block idempotently instead of
duplicating it.

Two posts (from-c-to-machine-code, self-hosting-gitea-and-mirroring-github)
use note/tip/warning/danger callout shortcodes that have no Astro
equivalent yet — flagged by the script rather than guessed at, since
that needs a real component once the design system exists.

Verified: astro check clean, full site builds (80 pages, all real
migrated posts render), redirect generation confirmed idempotent
across repeated runs.
2026-08-30 19:10:53 +03:00
andwati 4fa9715ebf Check off carried-over content features and Pagefind in TODO 2026-08-30 19:04:53 +03:00
andwati 02a7a6197f Add feeds, sitemap, search, tags, and per-post nav/TOC/series/related
- src/site.config.ts: minimal site metadata (title, description,
  base URL, nav, tag descriptions) ported from the legacy zola.toml
  as a placeholder pending real design/copy.
- RSS (@astrojs/rss) and JSON Feed endpoints, sitemap
  (@astrojs/sitemap), robots.txt.
- Base.astro: meta description (site-wide default or per-page
  override via llms_description/description), RSS/JSON feed
  <link rel=alternate>, and a webmention endpoint link (plumbing
  only — needs the domain registered on webmention.io to do
  anything).
- toml-content-loader.ts: marked renderer now assigns heading ids
  and collects them into rendered.metadata.headings, so
  render(entry) exposes headings the same way a built-in Markdown
  loader would.
- Writing detail page: table of contents, prev/next navigation,
  series-nav (uses the existing extra.series/series_index fields),
  and tag-overlap related posts.
- Tags index + per-tag pages, tag links wired in from writing cards.
- Pagefind: indexes the build output as part of `pnpm build`
  (astro build && pagefind --site dist), plus a /search/ page using
  Pagefind's default UI.

Verified: astro check clean, full build succeeds (feeds/sitemap/
pagefind all generate correctly), and manually exercised /search/
(real query, highlighted results) and /tags/ in a browser against
the built site.
2026-08-30 19:04:25 +03:00
andwati 7b857d4be7 Note why OG-image and llms.txt extension are blocked, not skipped 2026-08-30 18:53:24 +03:00
andwati 62db772dad Add bookshelf ISBN/paper enrichment, fix status field name collision
apps/cms/src/utils/bookshelf-covers.ts fetches book covers from Open
Library by ISBN (real image, cached into apps/site/public/covers/)
and backfills missing title/authors for papers from Semantic Scholar
by DOI/arXiv id. Wired into the sync middleware in content-sync.ts:
runs once per entry (cover_image already set means "done"), and only
on live create/update through Strapi, not during the boot-time
import (so a restart doesn't hammer external APIs).

Also renames the bookshelf `status` field to `read_status`
everywhere (schema, content/README.md, seed content, Astro zod
schema, bookshelf page, sync mapping). Strapi's content-manager API
treats a top-level `status` key as its own reserved draft/publish
parameter even with draftAndPublish disabled, so an attribute
literally named `status` silently rejected every write — caught this
while testing the enrichment hook.

Verified end-to-end: created a book by ISBN with no cover_image,
confirmed a real cover image was downloaded and cached, cover_image
was set on the entry and written to the file, and the cover/file were
both removed on delete.
2026-08-30 18:52:39 +03:00
andwati 85efb824ae Wire Strapi to content/ via a two-way file sync provider
Adds slug (uid) fields to all four content types so Strapi documents
can be matched to files deterministically, plus:

- apps/cms/src/utils/content-file.ts: read/write/delete the +++ TOML
  frontmatter files, using smol-toml's TomlDate so dates round-trip as
  bare local dates (matching the legacy Zola format), not quoted
  datetime strings.
- apps/cms/src/utils/content-sync.ts: per-type frontmatter <-> Strapi
  field mappings, a boot-time importer that upserts every content/
  file into Strapi by slug, and a Document Service middleware that
  writes any create/update/delete made through Strapi straight back
  to the same files.

Strapi's SQLite DB is now genuinely a disposable cache: content/
stays buildable and readable with Strapi entirely absent, and
restarting Strapi rebuilds its DB from content/ with no duplicates
(matched by slug).

Verified end-to-end: fresh boot imports all seed files once; a second
boot re-imports idempotently (0 created, all matched by slug); create/
update/delete through the content-manager API all correctly write to
and remove the corresponding file on disk.
2026-08-30 18:45:44 +03:00
andwati 27efeb8b26 Add Docker setup for running Strapi locally
apps/cms/Dockerfile.dev + root docker-compose.yml give a one-command
way to run the CMS in a container for testing (docker compose up
cms), with the shared content/ directory mounted so it matches the
host dev setup. apps/cms/Dockerfile is a production multi-stage build
for later Dokploy deployment. Uses node:24-bookworm-slim to match the
local Node version and get glibc prebuilt binaries for better-sqlite3
without needing a native build toolchain in the final image.

Verified: both images build, the dev container boots Strapi
successfully, admin panel and API routes are reachable, and the
mounted content/ volume resolves at the same relative path the future
sync provider will expect.
2026-08-30 18:36:31 +03:00
andwati 9d6d78b31f Define Strapi content types matching the content/ schema
Adds writing, portfolio-entry, bookshelf-entry, and curated-blog
collection types with attributes mirroring the TOML frontmatter
fields in content/README.md exactly, so the future sync provider has
a direct field mapping. draftAndPublish is disabled on all four since
our own `draft` boolean (synced to the file) is the source of publish
state, not Strapi's built-in workflow. Verified Strapi boots and
registers all four REST routes (api/writings, api/portfolio-entries,
api/bookshelf-entries, api/curated-blogs).
2026-08-30 18:23:42 +03:00
andwati f24701b1e7 Add TODO.md tracking remaining Astro/Strapi rebuild work 2026-08-30 18:16:55 +03:00
andwati 16c8dff5d5 Wire Astro content collections to the shared content/ directory
Adds a custom Astro loader that reads the Zola-style TOML frontmatter
(+++ ... +++) directly, so content/ stays the single source of truth
with no YAML conversion step. Defines writings/portfolio/bookshelf/
blogs collections and basic list/detail pages for each, rendering the
seed entries end-to-end (verified in-browser via `pnpm --filter site
dev`). This is the first runnable MVP: home, writings list + post
detail, portfolio, bookshelf (books and papers), and curated blogs
all render from real content/ files.
2026-08-30 18:14:34 +03:00
andwati cae62d4a66 Apply Biome formatting across the workspace
Fixes the formatting deltas from the initial Astro/Strapi scaffold
and brings the legacy generate-og-images/generate-llms/validate-post
scripts in line with Biome's style. No behavioral changes.
2026-08-30 17:59:30 +03:00
andwati 8be678d488 Define shared content schema for writings, portfolio, bookshelf, blogs
Adds content/README.md documenting the TOML frontmatter contract each
content type must follow, plus one seed entry per type to lock the
schema in a runnable form. Bookshelf entries use a kind field ("book"
or "paper") so academic papers (DOI/arXiv id) are first-class
alongside ISBN-driven books, not bolted on later.
2026-08-30 17:58:10 +03:00
andwati 80e1d55ea1 Scaffold Astro/Strapi monorepo for the site rebuild
Set up a pnpm workspace with apps/site (Astro) and apps/cms (Strapi,
SQLite-backed) as the foundation for the new digital-legacy site.
Switches package management to pnpm and adds Biome for linting/
formatting, replacing npm/no-linter setup. Existing Zola site stays
in place for now and will be migrated/removed progressively.
2026-08-30 17:56:36 +03:00
andwatiandGitHub 7a2853a44b Merge pull request #3 from andwati/migrate-security-headers-to-sws
Serve security headers via static-web-server, drop Cloudflare deploy path
2026-08-19 13:20:58 +03:00
andwati f87b818201 Serve security headers via static-web-server, drop Cloudflare deploy path
Production (Dokploy/Docker) was sending no security headers at all -
static/_headers and wrangler.toml were Cloudflare Pages/Workers
config that static-web-server never reads. Added sws.toml with the
same CSP/security headers and the two redirects from _redirects,
wired into the Dockerfile. Removed wrangler.toml, static/_headers,
static/_redirects, the wrangler devDependency/deploy script, and
related .gitignore/.dockerignore entries since Cloudflare deploy is
no longer used.

Also fixed a console error in theme.js where the Utterances
postMessage call could fire before the comments iframe navigated to
utteranc.es.
2026-08-19 12:31:44 +03:00
andwati a49aa02886 Auto-generate per-post OG images
Renders a 1200x630 social card per post (satori + resvg-js, using the
site's own self-hosted fonts) instead of reusing one generic og-image.png
for every post. Wired into npm run build via a new generate:og step;
templates/page.html falls back to the per-slug image unless extra.image
is set, and non-post pages keep the generic fallback.
2026-08-19 10:56:40 +03:00
andwati 6b8b926730 added article "Self-Hosting Gitea and Automating GitHub Mirrors" 2026-08-19 10:37:55 +03:00
andwatiandGitHub 18a6b5b4d9 Merge pull request #2 from andwati/zola-0.23-migration
Migrate templates to Zola 0.23 / Tera v2
2026-08-07 10:59:16 +03:00
andwati bf4ef2aa29 Add Dockerfile for self-hosted deployment, bump build.sh to Zola 0.23.1
Adds a multi-stage Dockerfile (Node for llms.txt generation, the
official Zola image to build, static-web-server to serve) for
deploying via Dokploy/Docker instead of Cloudflare Pages. This is an
additional path, not a replacement - wrangler.toml and the Cloudflare
build.sh remain in place as an option.

Also bumps build.sh's pinned ZOLA_VERSION from 0.22.1 to 0.23.1: the
prior commit migrated every template to Tera v2 syntax (components,
spread operator, native slicing), which 0.22.1 can't parse - confirmed
by a live Cloudflare build failure on this branch. Keeping build.sh
and the Dockerfile on the same Zola version avoids maintaining two
template dialects across the two deploy paths.

Verified: `docker build` succeeds, the container serves the homepage,
a post, the youtube embed, and a proper 404; `build.sh` also runs
clean standalone (simulating the Cloudflare build) and produces a
fully populated public/.
2026-08-07 10:54:38 +03:00
andwati e992baf2d2 Migrate templates to Zola 0.23 / Tera v2
bin/update-zola always installs the latest release, which as of
v0.23.0 rewrote the template engine to Tera v2. That broke the whole
site: removed filters (slice, concat, escape), test calls now require
named args, shortcodes are gone in favor of Tera components, and
content itself is now Tera-templated before markdown runs.

- slice(end=n) -> native [:n] slicing; concat(with=x) -> spread [...arr, x]
- escape -> escape_html; starting_with/containing -> named `pat` arg
- get_taxonomy_url `name` -> `term` (old name is deprecated)
- %+ date format no longer recognized; replaced with explicit RFC3339 pattern
- shortcodes (note/tip/warning/danger/youtube) rewritten as components;
  content call sites updated to component invocation syntax
- {% include %} no longer inherits for-loop-local variables (only ambient
  page/section context) - post-card.html converted to a component that
  takes page/index explicitly instead of relying on implicit `page`/`loop`
- two content code blocks with literal Jinja-style {{ }} syntax (Netlify
  CMS config, a Flask template snippet) wrapped in {% raw %} so Tera
  doesn't try to render them, since content is now templated too
- fixed a real content corruption bug along the way: the youtube
  shortcode's multi-line <iframe> tag had a conditional that could
  collapse to a whitespace-only line, which prematurely closed the
  HTML block under CommonMark rules and got the rest of the tag
  syntax-highlighted as code instead of rendered

Verified with a clean `zola build` and `zola serve` on v0.23.1, plus a
spot-check of the rendered HTML for both callouts and the video embed.
`zola check` only reports pre-existing external link rot, unrelated to
this change.
2026-08-07 10:46:09 +03:00
andwati e0a7feed3e fix _redirects: drop absolute-URL www redirect breaking deploy
Cloudflare Workers static assets rejects the www->apex redirect rule
with "Only relative URLs are allowed" (Pages allows absolute source
URLs for cross-host redirects, but this project deploys via wrangler's
static assets, not Pages, which doesn't). The www/apex canonicalization
needs to be handled via Cloudflare's dashboard Redirect Rules instead.
2026-07-17 16:50:12 +03:00
andwati 9917059437 add missing alt text, remove broken image links
15 screenshots in the ScriptCTF writeup had empty alt text. Also removed
two <img> references in the semver post pointing at files that never
existed in the repo (leftover from the old Netlify CMS media setup).
2026-07-17 15:59:46 +03:00
andwati 3e1c643283 fix zola.toml config table scoping bug and JSON-LD safety
[[extra.menu]] silently swallowed ~20 later extra.* keys (posts_page_title,
search_placeholder, show_rss_link, notfound_*, etc.) into the last menu
entry/tag_descriptions table, since TOML absorbs bare keys into the last
open table until a new header appears. show_rss_link was the only visibly
broken one (RSS link missing from homepage); the rest were masked by
matching template defaults.

Also replaced Tera's default() filter with explicit if/else where values
feed into JSON-LD, since default()'s fallback gets escaped in a way `safe`
can't undo. Added author/dateModified to BlogPosting list entries.
2026-07-17 15:59:27 +03:00
andwati 3b7cca3cc7 remove cc banner. added llms.txt 2026-07-17 15:01:48 +03:00
andwati f84a09bda7 fix lighthouse issues 2026-07-17 14:49:19 +03:00
andwati 4fb5fae2ba fix xml 2026-07-17 14:37:04 +03:00
andwati f5df752429 fix youtube embedd 2026-07-17 14:12:16 +03:00
andwati d48835a270 fix json ld 2026-07-17 13:59:48 +03:00
andwati 4d7e55bf90 fix json ld errors 2026-07-17 11:11:44 +03:00
andwati 36d916e930 update author information from "mockingspectre" to "andwati" across multiple files 2026-07-16 16:41:56 +03:00
andwati bb5ddeffc0 fix build script 2026-07-16 16:22:57 +03:00
andwati b4c52ee98d added pgp keys 2026-07-16 16:09:20 +03:00
andwati b2a61483dc remove Dockerfile and .dockerignore; add security.txt, _headers, and _redirects for deployment 2026-07-16 15:48:56 +03:00
andwati 1230de9387 remove workflow 2026-07-16 14:42:19 +03:00
andwati d8b898ca4a fix node version 2026-07-16 14:37:57 +03:00
andwati f14ec8942e workflow changes 2026-07-16 14:34:52 +03:00
andwati 741086acfd cloudflare build 2026-07-16 14:17:47 +03:00
andwati 512a54ba2d yolo 2026-07-16 12:28:42 +03:00
andwati e1f7c33692 updated 2026-07-16 12:28:42 +03:00
andwati a51671d7e4 redesign site as "case file" theme
- new paper/ink palette with single red accent, replacing catppuccin
- self-hosted IBM Plex Serif (display) + Source Serif 4 (body), drop Outfit
- rebuilt homepage masthead, numbered post index, navbar with active states
- switch syntax highlighting + utterances to gruvbox, synced via postMessage
- remove terminal/404 easter egg chrome, clean up remaining inline styles
- fix stray syntax.css link causing a 404
2026-07-16 12:28:34 +03:00
andwati 57462869ec switch ga tag 2026-07-02 23:53:03 +03:00
andwati 7b9eea35dd created dockerfile 2026-07-02 23:30:10 +03:00
andwati b27529c6a5 added observability configs 2026-07-02 22:37:21 +03:00