This repository contains a React 19 single-page portfolio/resume app built with Vite.
- Node.js 24.x LTS (project pin:
24.21.0in.nvmrc) - npm 12.x (project pin:
12.0.2inpackage.json) - Git
- Optional: ImageMagick, for stripping image metadata when the media metadata check fails
- Optional: metaflac, for stripping FLAC metadata when the media metadata check fails
npm run dev(ornpm start): start the local Vite dev server athttp://localhost:5173npm run build: create a production build indist/and copydist/index.htmltodist/404.htmlnpm run preview: preview the production build locallynpm run test:jam: validate jam filtering, catalog metadata, and chart authoring without starting a servernpm run check:media-metadata: check tracked and untracked media files for image and FLAC metadatanpm run check:image-metadata: compatibility alias fornpm run check:media-metadatanpm run setup:hooks: configure this local clone to use the repo's Git hooks from.githooks/npm run generate:resume-pdf: use Playwright + headless Chromium to render/print-resume?mode=pdfintodist/Kellen-Stuart-Resume.pdf(requiresdist/to exist)
npm run predeploy: run the production build and generatedist/Kellen-Stuart-Resume.pdfnpm run deploy: publishdist/usinggh-pages(runspredeployautomatically)
Use the Node.js version in .nvmrc and npm version in package.json.
Dependency installation is performed by the operator, as required by AGENTS.md.
npm ci
npm run setup:hooks
npm run check:media-metadataAfter changing dependency versions in package.json, run npm install to update
package-lock.json and the installed packages, then validate with npm run build
and npm run check:media-metadata. Commit both package files together. After a
Playwright upgrade, run npx playwright install chromium to install its matching
browser before testing resume PDF generation.
- Static assets are served from
public/. - Routing is handled client-side with
react-router. - The build includes a
404.htmlcopy for static hosting fallback behavior. - If Playwright browser binaries are missing in your environment, run
npx playwright install chromium. - On Linux runners/workstations missing shared libraries, run
npx playwright install --with-deps chromium.
For the step-by-step process of adding a chart, including chord/lyric alignment,
important voicings, rhythm notation, and validation, see
Authoring jam charts. Song-specific
content belongs in src/data/charts/; jamSongs.js holds catalog metadata
and associates it with those chart modules.
Edit songs in src/data/jamSongs.js. People, genres, and Yousician availability
have their own fields; practice and descriptive labels remain in tags.
playedWith: [musicians.kaylee, musicians.casey],
genres: ["punk"],
hasYousician: true,
tags: [jamTags.needsWork, jamTags.soloLearned],playedWithrecords people you've played the song with. Use references such asmusicians.kayleeinstead of quoted IDs. To add someone, define their stable ID inmusiciansand their display name injamMusicians. This is jam history, not a required lineup or a claim that everyone currently remembers the song.genresaccepts multiple IDs fromjamGenres, such as"punk","country","classic rock", and"psychedelic". Leave it empty when unspecified.hasYousiciandisplays Available when true and Not Available when false. The display reflects the catalog flag. Existing Yousician tags were carried over astrue; availability has not been independently checked.tagsusesjamTagsfor practice and descriptions such as Acoustic or Open Jam. Needs Work and Stage Ready can coexist; they have not been reduced to a single readiness value. Solo progress remains separate.- Catalog songs default to empty people/genre/tag arrays and
hasYousician: falsewhen these fields are omitted. Tuning, musical key, capo, and chart data keep their existing fields.
On /jam, click names under Played with to find songs you've played with
every selected person. Each additional person narrows the list; click a selected
name again to remove it. Selected buttons show a checkmark. There is no matching
mode to choose. Genre, Guitar Tuning, Yousician, and chart
filters match any selected option within their group. Different groups narrow
each other. Practice and other tags require every selected tag.
The main filter groups appear in this order: Played with, Guitar Tuning, Genre, and Chart. Stage Ready is a standalone toggle immediately below them. More filters contains Practice, Yousician, and Other Tags, in that order; Stage Ready is not repeated inside Practice and does not count toward the More filters selection count. Selected filters remain visible and removable even when that section is collapsed. Song cards show guitar tuning, key, and Stage Ready first. More song details shows compact clickable lists for Played with, Genres, Practice Notes, and Other Tags, plus additional song facts. Yousician appears last as plain Available / Not Available text. Chart availability remains in the card footer and main Chart filters. Search includes names, genres, practice tags, and song details. Musical key is informational only: it appears as a plain label on song cards and is excluded from filters and the search metadata. Guitar tuning uses the same plain-label style directly below the key; filter it using the Guitar Tuning controls in the main filter section.
The print view at /jam/print groups songs alphabetically by artist, then by
song title. Song lines are indented without numbering and show
Title - 🎸 Guitar tuning - ♫ Musical key, with 🤘 for Ready to rock (Stage Ready). Missing
values display Tuning TBD or Key TBD. The sheet uses two columns, with a single
column on narrow screens.
Run npm run test:jam and npm run build after changing filter logic. For a
manual smoke test, select two people and verify each song lists both, then
deselect one and confirm the list broadens. Select two genres or
tunings, try the Yousician filter, and clear the selections. Check the expandable
controls on a phone-sized viewport and confirm the Rooster chart and print list
still open.
The repo includes a pre-push hook at .githooks/pre-push that checks media metadata before anything is pushed.
Fresh clones need one local Git setting because core.hooksPath is not committed with the repo:
npm run setup:hooksThe hook itself only requires Git and Node. ImageMagick is optional but recommended because it is the easiest way to clean an image that fails the check. metaflac is optional but recommended for cleaning FLAC files.
Typical cleanup commands:
magick input.png -strip output.png
magick input.webp -strip output.webp
magick input.jpg -auto-orient -strip output.jpg
metaflac --dont-use-padding --remove-all input.flacOn ImageMagick 6, use convert instead of magick.
Blog posts support both hero photos and inline photos.
- Put image files in
public/blog/<post-slug>/, for examplepublic/blog/my-post/trail.webp. - Add an optional
heroImageobject insrc/data/blogPosts.js:
heroImage: {
src: "/blog/my-post/trail.webp",
alt: "Backpacking trail at sunrise",
caption: "Day 2, near treeline.",
}- To insert inline photos inside a post component, import and use
BlogImage:
import BlogImage from "../components/blog/BlogImage";
<BlogImage
src="/blog/my-post/camp.webp"
alt="Camp setup near an alpine lake"
caption="Final campsite before summit day."
/>;Posts can track publish date, last-updated date, and an optional update log.
- Add
lastUpdatedandupdatesinsrc/data/blogPosts.js:
{
slug: "my-post",
title: "My Post",
date: "2026-03-01",
lastUpdated: "2026-06-15",
updates: [
{ date: "2026-04-03", note: "Added new photos." },
{ date: "2026-06-15", note: "Added audio recordings and notes." },
],
}- The blog template will automatically render:
Published <date>Last updated <date>(when different from publish date)- an
Updatessection whenupdateshas entries
To embed recordings in a post, use BlogAudio:
import BlogAudio from "../components/blog/BlogAudio";
<BlogAudio
src="/blog/my-post/riff-demo-1.mp3"
title="Riff Demo 1"
caption="LA30BL, Sabbra Cadabra at 18V, C# standard."
/>;Store audio files under public/blog/<post-slug>/ so they are available as static assets.
To make the Download PDF button return a real PDF locally:
- Install Playwright Chromium:
npx playwright install chromium - Build + generate the PDF artifact:
npm run predeploy - Serve from
dist/:npm run preview - Test
Download PDFin the preview app
npm run dev is not sufficient for this test because it does not serve the generated dist/Kellen-Stuart-Resume.pdf artifact.