Skip to content

Latest commit

 

History

History
28 lines (23 loc) · 6.4 KB

File metadata and controls

28 lines (23 loc) · 6.4 KB

Learned User Preferences

  • Follow SvelteKit and Svelte idioms; reuse existing content, components, and patterns rather than porting React-style structure.
  • When extending the site, migrate and normalize content first, then build UI to match the existing look.
  • Keep the work page intro prominent; list past tech roles in a minimal, de-emphasized way with no company logos.
  • Avoid implicit any; type route +page.ts loaders with PageLoad from ./$types.
  • Keep thoughts as local markdown on disk; syndicate to the ATmosphere with standard.site, do not render posts from the PDS.
  • Use @lucide/svelte for UI icons; brand/site logos (BrandIcon, MsLogo) are inline SVG components (not <img>) so currentColor works and size is set via class; Lucide excludes brand icons.

Learned Workspace Facts

  • 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; optional hero for cover/OG); syndicate to standard.site via @ewanc26/svelte-standard-site (pnpm publish-publication, publish-thought, publish-thoughts, write-well-known; markpub.at content; hero uploads as coverImage blob via scripts/lib/cover-image.mjs). Verification <link rel="site.standard.document"> on individual thought pages via postbuild inject (scripts/inject-standard-site-links.mjs; clears stale .br/.gz so sirv serves updated HTML; not <svelte:head>—prerender crawler rejects at://); pnpm-workspace.yaml pins @ewanc26/* overrides.
  • /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.
  • 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).
  • 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 (ephemeral DOCKER_CONFIG for GHCR login, quiet docker compose pull, optional DEPLOY_DOCKER_CPU_QUOTA on dockerd, then up --no-build, then docker image prune -a -f; no on-host build); web service cpus: 0.8; kokoro-js and @ewanc26/svelte-standard-site are devDependencies only (client TTS/publish scripts, not runtime node_modules); 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.
  • 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; WebGPU when navigator.gpu on desktop, WASM q8 on mobile (WebGPU OOMs on phones); mobile: sync unlockReaderAudio() on tap, one-sentence generation lookahead, drop played buffers; first play: beginReaderAudioSession() in audio-context.ts (silent keep-alive loop) so AudioContext stays running during async model load; speech text via normalizeReaderWhitespace() in word-highlight.ts collapses editor soft line wraps before sentence split (real breaks stay as DOM block boundaries); word highlighting via CSS ::highlight(reader-word) in static/reader-highlight.css (outside Tailwind pipeline).
  • Static assets live under static/static/ so URLs stay /static/images/...; site favicons in static/static/favicons/ (linked from src/app.html), derived from MsLogo with transparent PNG backgrounds.
  • 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.
  • Node is locked to 24.x (engines in package.json, .nvmrc, .node-version).
  • Open Graph and Twitter meta come from SocialMeta in the root layout and getPageSocialMeta in $lib/utils/social-meta.ts; thought pages use hero then images[0]; default image is site.socialBanner (/static/images/michael-schultz-social.jpg).
  • pnpm check runs svelte-check then tsc --noEmit so untyped route loaders are caught in CI and locally.

Cursor Cloud specific instructions

  • Node 24 is required (engine-strict=true in .npmrc); pnpm install will refuse to run on older Node. The VM has an /exec-daemon/node (Node 22) that shadows nvm on PATH, so run commands in a login shell (bash -lc '...') where ~/.bashrc puts nvm's Node 24 first. Verify with node --version before running pnpm.
  • Standard commands are in README.md/package.json: pnpm dev (Vite dev server on http://localhost:5173, routes use trailingSlash: 'always' so bare paths 308-redirect to the trailing-slash URL), pnpm check, pnpm build (also runs Pagefind indexing over build/), pnpm start (serves the built app on port 3000).
  • No secrets are needed to run/lint/build/test locally. Without LASTFM_API_KEY the /listening page renders normally but shows "No recent plays yet"; pnpm status (Bluesky) is a publishing script and not needed to run the site.
  • There is no automated test suite; pnpm check (svelte-check + tsc) is the type/lint gate and the .husky/pre-commit hook runs it on commit.