Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f3414fa
misc cleanups
CS-5 Sep 1, 2026
7dda942
overhaul: one toolchain, TypeScript tools, leaner CI and docs
claude Sep 1, 2026
a828414
overhaul: run the vendored anti-slop rules under ESLint
claude Sep 1, 2026
1967e5d
Fix settings and node version
CS-5 Sep 1, 2026
e073f32
Start cleaning up text
CS-5 Sep 1, 2026
e0f92c8
overhaul: drop check:tokens, settle on TypeScript 6, finish the oxlin…
claude Sep 1, 2026
4feda18
overhaul: vendor the font files and drop the fontsource packages
claude Sep 2, 2026
fc1904b
overhaul: render social cards with Satori and resvg, no Python
claude Sep 2, 2026
7121d6c
overhaul: prepare source images with sharp-cli instead of a script
claude Sep 2, 2026
410372b
Cleanups
CS-5 Sep 2, 2026
ae0d8b0
rm `content-references` check
CS-5 Sep 2, 2026
cb6aaf5
design: grain the ground, lift the pools, raise the ghost numerals
CS-5 Sep 3, 2026
4e640f6
design: one identity mark per card
CS-5 Sep 3, 2026
2c58a39
design: an underline means a link, drawn by hand
CS-5 Sep 3, 2026
036e611
design: eight tweaks from the second review
CS-5 Sep 3, 2026
2aadd10
design: the link underline is one gentle hand-drawn curve
CS-5 Sep 3, 2026
05d2671
design: one underline curve per line, stretched to the link
CS-5 Sep 3, 2026
370eb81
ci: put the LCP waterfall behind a Lighthouse failure in the job log
claude Sep 7, 2026
1a5a9ad
perf: measure Lighthouse over HTTP/2, subset the fonts, add a 672px l…
claude Sep 7, 2026
97bb6cc
ci: tell knip that serve.ts calls the system openssl
claude Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 13 additions & 74 deletions .claude/hooks/format-lint.sh
Original file line number Diff line number Diff line change
@@ -1,89 +1,28 @@
#!/usr/bin/env bash
# Formats and lints one edited file with whichever toolchain owns its extension
# (docs/tooling.md). Lint failures exit 2 so the findings go back to the agent.
# Formats and lints the file Claude Code just edited. Lint failures exit 2 so the
# findings go back to the agent; a missing toolchain exits 0 so a fresh clone is not
# blocked before `pnpm install`.
set -uo pipefail

repo_root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}"
[[ -n "$repo_root" ]] || exit 0

payload="$(cat)"
file="$(printf '%s' "$payload" | jq -r '.tool_response.filePath // .tool_input.file_path // empty')"
file="$(jq -r '.tool_response.filePath // .tool_input.file_path // empty')"
[[ -n "$file" ]] || exit 0
[[ -f "$file" ]] || exit 0

# Absolute, and inside this repo.
case "$file" in
/*) ;;
*) file="$repo_root/$file" ;;
esac
case "$file" in
"$repo_root"/*) ;;
*) exit 0 ;;
esac

rel="${file#"$repo_root"/}"
case "$rel" in
legacy/* | dist/* | .astro/* | node_modules/* | plan/* | tools/lint/anti-slop/* | pnpm-lock.yaml)
exit 0
;;
esac
[[ "$file" = /* ]] || file="$repo_root/$file"
[[ -f "$file" && "$file" = "$repo_root"/* ]] || exit 0

cd "$repo_root" || exit 0
bin="$repo_root/node_modules/.bin"
[[ -x "$bin/prettier" ]] || exit 0

# Use the installed binaries directly so the hook works without pnpm on PATH.
# Status 127 means the tool itself is missing, which callers must not report as
# lint findings: on a fresh clone, before `pnpm install`, every edit would
# otherwise be rejected with an empty message.
readonly TOOL_MISSING=127

run() {
local tool="$1"; shift
local bin="$repo_root/node_modules/.bin/$tool"
if [[ -x "$bin" ]]; then
"$bin" "$@" 2>&1
return
fi
command -v pnpm >/dev/null || return "$TOOL_MISSING"
[[ -d "$repo_root/node_modules" ]] || return "$TOOL_MISSING"
pnpm exec "$tool" "$@" 2>&1
}
"$bin/prettier" --write --ignore-unknown --log-level warn "$file"

# Runs a linter and exits 2 with its findings. A missing toolchain exits 0 so the
# edit is allowed through un-linted rather than blocked with nothing to act on.
lint() {
local findings status
findings="$(run "$@")"
status=$?
case "$status" in
0) return 0 ;;
"$TOOL_MISSING") exit 0 ;;
*)
case "${file##*.}" in
ts | mts | js | mjs | astro)
findings="$("$bin/eslint" --max-warnings 0 "$file" 2>&1)" || {
printf '%s\n' "$findings" >&2
exit 2
;;
esac
}

case "${file##*.}" in
ts | tsx | js | jsx | mjs | cjs | json | jsonc | json5)
# Without --ignore-path, oxfmt also reads .prettierignore, which excludes
# every extension oxfmt owns.
run oxfmt --ignore-path .gitignore "$file" >/dev/null
lint oxlint --type-aware "$file"
;;
# oxfmt's directory scan formats these too, so `pnpm check` fails on an unformatted one —
# but oxlint has no rules for them, so they are formatted and not linted. Passing one to
# oxlint is not a no-op: it reports "No files found to lint" and exits non-zero.
css | yaml | yml | toml)
run oxfmt --ignore-path .gitignore "$file" >/dev/null
;;
astro)
run prettier --write "$file" >/dev/null
lint eslint --max-warnings 0 "$file"
;;
md)
run prettier --write "$file" >/dev/null
}
;;
esac

exit 0
2 changes: 1 addition & 1 deletion .claude/skills/shadcn-astro/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Port a shadcn/ui component to an Astro primitive in src/components/

# Porting shadcn components to Astro primitives

This site ships **zero client framework runtime** (D3). shadcn is React + Radix, so a port is a
This site ships **zero client framework runtime**. shadcn is React + Radix, so a port is a
re-implementation, not a copy — but the _design API_ comes across almost verbatim, and should.

## When to use this
Expand Down
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# For what neither oxfmt nor Prettier covers (yaml, toml, shell, gitignore-likes).
# For what Prettier does not format (toml, shell, gitignore-likes).
root = true

[*]
Expand Down
24 changes: 24 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Setup
description: Install the mise-pinned toolchain and the pnpm dependencies, with both cached.
runs:
using: composite
steps:
- uses: jdx/mise-action@v4
with:
version: 2026.8.14
cache: true

# mise-action caches tool binaries, not pnpm's content-addressable store.
- name: Locate pnpm store
shell: bash
run: echo "PNPM_STORE=$(pnpm store path --silent)" >>"$GITHUB_ENV"

- uses: actions/cache@v6
with:
path: ${{ env.PNPM_STORE }}
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: pnpm-store-${{ runner.os }}-

- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfile --prefer-offline
22 changes: 22 additions & 0 deletions .github/eslint-matcher.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"problemMatcher": [
{
"owner": "eslint-stylish",
"pattern": [
{
"regexp": "^([^\\s].*)$",
"file": 1
},
{
"regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$",
"line": 1,
"column": 2,
"severity": 3,
"message": 4,
"code": 5,
"loop": true
}
]
}
]
}
99 changes: 65 additions & 34 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: CI

on:
pull_request:
push:
branches: [main, staging]

permissions:
contents: read
Expand All @@ -12,54 +14,83 @@ concurrency:

jobs:
check:
name: Check + Build
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup

- name: Install mise tools
uses: jdx/mise-action@v4
with:
version: 2026.8.14
install: true
cache: true

# mise-action's cache covers mise-managed tool binaries (node, pnpm), not
# pnpm's content-addressable store, so without this every run re-downloads
# the whole tree.
- name: Locate pnpm store
run: echo "PNPM_STORE=$(pnpm store path --silent)" >>"$GITHUB_ENV"

- name: Cache pnpm store
uses: actions/cache@v6
with:
path: ${{ env.PNPM_STORE }}
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: pnpm-store-${{ runner.os }}-

- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline
# Every step runs even after an earlier one fails, so one PR run reports every kind of
# problem. The ESLint matcher turns findings into inline annotations on the PR diff.
- name: Typecheck
run: pnpm typecheck
- name: Lint
if: ${{ !cancelled() }}
run: |
echo "::add-matcher::.github/eslint-matcher.json"
pnpm lint
- name: Format
if: ${{ !cancelled() }}
run: pnpm fmt:check
- name: Unused code and dependencies
if: ${{ !cancelled() }}
run: pnpm knip

- name: Check (typecheck + lint + format + knip)
run: pnpm check
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup

- name: Build
run: pnpm build

# Needs dist/, so it cannot live in `pnpm check`: uniqueness of titles and descriptions,
# and whether an og:image resolves, are only answerable across the whole built site.
- name: Verify page metadata
run: pnpm run check:meta
# Title and description uniqueness, and whether every og:image resolves, are only
# answerable across the whole built site.
- name: Page metadata
run: pnpm check:meta

# --root-dir is required for the root-relative hrefs Astro emits (/_astro/...);
# without it lychee cannot resolve them in local files and errors on every page.
# llms.txt is in scope because its links are hand-written paths in src/data/events.ts,
# which nothing else would catch drifting from the routes they name.
- name: Link check
# --root-dir resolves the root-relative hrefs Astro emits. llms.txt is in scope because its
# links are hand-written paths that nothing else checks against the routes they name.
- name: Links
uses: lycheeverse/lychee-action@v2
with:
args: >-
--offline --include-fragments --no-progress
--root-dir ${{ github.workspace }}/dist
'dist/**/*.html' 'dist/llms.txt'
fail: true

- uses: actions/upload-artifact@v5
with:
name: dist
path: dist/
retention-days: 1

lighthouse:
name: Lighthouse
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup
- uses: actions/download-artifact@v6
with:
name: dist
path: dist/

- name: Lighthouse CI
run: pnpm dlx @lhci/cli@0.15.1 autorun --config=tools/ci/lighthouserc.json

# Into the log as well as the summary, so the waterfall behind a failed LCP assertion is
# readable from the API without downloading the report artifact.
- name: Summarize
if: ${{ !cancelled() }}
run: node tools/ci/lighthouse-summary.ts | tee -a "$GITHUB_STEP_SUMMARY"

- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: lighthouse-reports
path: .lighthouseci/reports/
53 changes: 0 additions & 53 deletions .github/workflows/lighthouse.yml

This file was deleted.

24 changes: 0 additions & 24 deletions .oxfmtrc.json

This file was deleted.

Loading
Loading