|
5 | 5 | - Keep the work page intro prominent; list past tech roles in a minimal, de-emphasized way with no company logos. |
6 | 6 | - Avoid implicit `any`; type route `+page.ts` loaders with `PageLoad` from `./$types`. |
7 | 7 | - Keep thoughts as local markdown on disk; syndicate to the ATmosphere with standard.site, do not render posts from the PDS. |
| 8 | +- Use `@lucide/svelte` for UI icons; keep GitHub and Bluesky brand logos in `BrandIcon` (Lucide excludes brand icons). |
8 | 9 |
|
9 | 10 | ## Learned Workspace Facts |
10 | 11 |
|
11 | | -- SvelteKit site at the repo root: `@sveltejs/adapter-node` (hybrid — most routes prerendered, `/listening` is SSR), `$lib/content/*` plus `$lib/config/*` for posts, work, games, and site config. Thoughts are local markdown (`src/content/thoughts/*.md`, `markdown-it`, `js-yaml` v3 frontmatter); syndicate to standard.site via `@ewanc26/svelte-standard-site` (`pnpm publish-publication`, `publish-thought`, `publish-thoughts`, `write-well-known`; markpub.at content). Verification `<link>` tags on individual thought pages only; `pnpm-workspace.yaml` pins `@ewanc26/*` overrides. |
| 12 | +- SvelteKit site at the repo root: `@sveltejs/adapter-node` (hybrid — most routes prerendered; Node server required for `/listening`, `/api/status`, `/api/cron/sync-listening`, and `hooks.server.ts` SQLite/cron — not removable without architecture change), `$lib/content/*` plus `$lib/config/*` for posts, work, games, and site config. Thoughts are local markdown (`src/content/thoughts/*.md`, `markdown-it`, `js-yaml` v3 frontmatter); syndicate to standard.site via `@ewanc26/svelte-standard-site` (`pnpm publish-publication`, `publish-thought`, `publish-thoughts`, `write-well-known`; markpub.at content). Verification `<link>` tags on individual thought pages only; `pnpm-workspace.yaml` pins `@ewanc26/*` overrides. |
12 | 13 | - `/listening` reads recent Last.fm scrobbles from SQLite (`better-sqlite3`); sync runs via in-process `node-cron` (~5×/day at server startup) and stale refresh on page load; `GET /api/cron/sync-listening` (Bearer `CRON_SECRET`) is optional for external/manual HTTP sync only. |
13 | 14 | - Homepage status line reads `com.michaelschultz.status/self` from Bluesky via `/api/status` (60s server cache); publish with `pnpm status` using `BLUESKY_APP_PASSWORD` locally (not needed at runtime). |
14 | | -- Docker deployment: multi-stage `Dockerfile` + `docker-compose.yml` on external network `pangolin` for reverse-proxy routing; shared pnpm store cache mount with separate `prod-deps` and `build` stages (runtime copies prod `node_modules` from `prod-deps`, `build/` from `build`); `pnpm-workspace.yaml` sets `trustLockfile: true`; production image is built in GitHub Actions and pushed to `ghcr.io/michaelwschultz/michaelschultz.com`; VPS runs `deploy/deploy.sh` (`docker compose pull` + `up --no-build`); SQLite at `DATABASE_PATH` (default `/app/data/listening.db`); `deploy/docker-entrypoint.sh` chowns the `listening-data` volume. |
15 | | -- Production deploy is automated via `.github/workflows/deploy.yml` (GH Actions build/push to GHCR, then SSH pull + `deploy/write-env.sh` + `deploy/deploy.sh`); app and infrastructure settings live in GitHub Environment secrets, not in the repo. |
| 15 | +- Docker deployment: multi-stage `Dockerfile` + `docker-compose.yml` on external network `pangolin` for reverse-proxy routing; shared pnpm store cache mount with separate `prod-deps` and `build` stages (runtime copies prod `node_modules` from `prod-deps`, `build/` from `build`); `pnpm-workspace.yaml` sets `trustLockfile: true`; production image is built in GitHub Actions (`.github/workflows/deploy.yml`) and pushed to `ghcr.io/michaelwschultz/michaelschultz.com`; VPS runs `deploy/deploy.sh` (`docker compose pull` + `up --no-build`, no on-host build); do not pass `--config.child-concurrency=2` to `pnpm install` in the Dockerfile (pnpm 11 lifecycle-hook bug); SQLite at `DATABASE_PATH` (default `/app/data/listening.db`); `deploy/docker-entrypoint.sh` chowns the `listening-data` volume; app and infrastructure settings live in GitHub Environment secrets, not in the repo. |
| 16 | +- Thought read-aloud: client-side Kokoro TTS (`kokoro-js`) on `/thoughts/[slug]/` only; engine in `$lib/page-reader/*` (ported from standard-reader); `thoughts/[slug]/+layout.svelte` lazy-loads the engine and `PageReaderBar`; attributes standard-reader.app; word highlighting via CSS `::highlight(reader-word)` in `static/reader-highlight.css` (outside Tailwind pipeline). |
16 | 17 | - Static assets live under `static/static/` so URLs stay `/static/images/...`. |
17 | | -- Search is Pagefind: `pnpm build` runs `vite build` then `scripts/postbuild.mjs` to index `build/`; UI uses the `pagefind-modal` web component; dev search needs a production build (index not available in `vite dev`). |
18 | | -- `export const trailingSlash = 'always'` in `src/routes/+layout.ts` emits directory-style HTML (`work/index.html`) for correct Pagefind paths. |
19 | | -- Layout sets `data-pagefind-meta` with clean route URLs (e.g. `url:/work`); `content-check` routes use `data-pagefind-ignore` so they are not indexed. |
| 18 | +- Search is Pagefind: `pnpm build` runs `vite build` then `scripts/postbuild.mjs` to index `build/`; UI uses the `pagefind-modal` web component; dev search needs a production build (index not available in `vite dev`); `export const trailingSlash = 'always'` in `src/routes/+layout.ts` emits directory-style HTML for correct paths; layout sets `data-pagefind-meta` with clean route URLs (e.g. `url:/work`); `content-check` routes use `data-pagefind-ignore` so they are not indexed. |
20 | 19 | - Node is locked to 24.x (`engines` in `package.json`, `.nvmrc`, `.node-version`). |
21 | 20 | - Open Graph and Twitter meta come from `SocialMeta` in the root layout and `getPageSocialMeta` in `$lib/utils/social-meta.ts`; default image is `site.socialBanner` (`/static/images/michael-schultz-social.jpg`). |
22 | 21 | - `pnpm check` runs `svelte-check` then `tsc --noEmit` so untyped route loaders are caught in CI and locally. |
|
0 commit comments