Imported from WebJamApps/JaMmusic (
AGENTS.md). Install upstream withnpx skills add WebJamApps/JaMmusic. Copyright stays with the author.
AGENTS.md — JaMmusic
Guidance for AI coding agents (Claude Code, agy/Antigravity, etc.) working in
this repo. (Global rules live in ~/.agents/AGENTS.md; this file adds JaMmusic
specifics.)
Cross-AI hard rules
The cross-AI hard rules that bind every agent on every surface are NOT duplicated here. They live
in exactly one file: docs/cross-ai-rules.md in the web-jam-tools repository, which normally
sits alongside this repository — ../web-jam-tools/docs/cross-ai-rules.md, and on Josh's laptop
/home/joshua/WebJamApps/web-jam-tools/docs/cross-ai-rules.md.
Read that file before acting. If you cannot find it, STOP and say so — do not proceed without the rules and do not reconstruct them from memory or from this file.
What this is
The React + TypeScript + Vite front-end for Web Jam LLC (the band site,
web-jam.com / webjamsalem). State via React context providers
(src/providers/) and Redux (src/redux/). The built front-end is
embedded into the backends at build time (web-jam-back → webjamsalem,
WebJamSocketCluster) and redeployed via a fan-out dispatch when main updates —
JaMmusic itself does not deploy to Heroku directly.
Commands (get all green before declaring done)
- Everything:
npm test— runs lint → typecheck (tsc --noEmit) → jscpd (copy-paste detection) → unit tests with coverage. This is the single check to confirm you're green — CI runs exactly this. ALWAYS run the FULLnpm testbefore pushing; running the sub-scripts individually (test:unit,test:lint) skips typecheck/jscpd and lets a break reach CI. - Lint:
npm run test:lint(stylelint onsrc/styles/**/*.scss+ eslint). Auto-fix withnpm run test:lint-fix. - Unit tests:
npm run test:unit(vitest, run mode, with coverage,TZ=UTC). - Type-check only:
npm run typecheck(tsc --noEmit). - Dev server:
npm run dev(HTTPS Vite). Build:npm run build. - e2e (
npm run test:e2e, Playwright) needs a browser install + running app; you don't need to run it — unit tests + lint are the gate.
Conventions
- Snapshot updates: use
npm run test:unit-u(pinsTZ=UTC) — never a barevitest -u. Re-runnpm testafterwards to confirm green (a bare update has bitten CI before). - Imports: prefer the
src/path alias over deep../../../relative chains. - Coverage gate (CI-BLOCKING):
npm testfails and the PR cannot merge if coverage drops below 90/90/80/80 (statements/lines/functions/branches). Any new component/function/branch you add MUST ship with tests in the SAME PR — check the coverage output at the end ofnpm testand get it green BEFORE opening the PR, not after CI flags it. - Tests live in
test/, mirroringsrc/; shared mocks in__mocks__/. Add or update specs alongside the file you change.
Layout
- App code in
src/:App/,components/,containers/,lib/,providers/,redux/,styles/, plusMain.tsxentry. - SCSS in
src/styles/(stylelint-checked). Static assets inpublic/. - Config:
vite.config.ts,tsconfig.json/tsconfig.prod.json,eslint.config.mjs,playwright.config.ts.
Don't touch
dist/,coverage/,node_modules/,public/build artifacts.- Do not add, upgrade, or remove dependencies — ask first.
- Do not edit CI config or anything under
.github/unless the task is about it. - Bump the semver
versioninpackage.jsononce per PR on the feature branch (not once per push). When rebasing a feature branch ontodev, always verify thatpackage.json's version is strictly incremented relative to the updateddevmerge-base.
Pull requests
Never merge to dev or main — Josh is the mandatory human reviewer. Open PRs
with the shared script (~/WebJamApps/web-jam-tools/scripts/create-draft-pr.sh),
never gh pr create directly. It always opens a draft PR based on dev
from a <lane>/<issue#>-<slug> branch.
PR body conventions (violations may be machine-rejected)
- Summary: markdown bullet points, one change per bullet — never a run-on paragraph.
- Test evidence: paste the REAL runner output verbatim (the lines showing pass/fail and test counts), inside a ``` fence — never a description like "all tests passed". If the output has scrolled out of view, re-run the test command and paste what it prints.
- Test plan: exact commands and concrete manual verification steps exercising the actual new behavior (start command, route/page, what to click, expected visible result) — a green test suite execution command (
npm test) alone is not a plan. - Attribution:
--authornames the model actually doing the work. Antigravity/agy sessions are ALWAYSagy — Gemini 3.5 Flash (Medium)or(High)— never write any other Gemini model name (models misremember their own identity; use this exact string). - Version bump ⇒ snapshot update: the AppTemplate footer renders the package.json version into a snapshot, so after bumping the version run
npm run test:unit-u(never barevitest -u— the script sets TZ=UTC) and commit the updated snapshot in the same PR.
Troubleshooting & Guardrails
- Vite Production Builds: Local environment variables (e.g.,
NODE_ENV=developmentin.env) can bleed intonpm run buildand compile a development-mode bundle containing React development helpers. This causes a critical browser runtime crash with the error:TypeError: (0, X.jsxDEV) is not a function. To compile a pure, clean production bundle, always prefix the build command:NODE_ENV=production npm run build. - Playwright selectors for Material-UI Typography: Material-UI's
<Typography>component compiles to<p>tags (or other tags like<h1>or<h6>based on variants) by default, never<span>tags. Avoid utilizing tag-locked selectors likespan:has-text("...")in E2E/Playwright tests, as they will timeout. Instead, use tag-agnostic text selectors like:text("...")orp:has-text("..."). - Running Playwright E2E Tests Locally: By default,
playwright.config.tstargetshttps://www.web-jam.com. Runningnpm run test:e2edirectly will test against the live production site and ignore local code modifications. To run E2E tests against your local changes:- Build a clean production bundle:
NODE_ENV=production npm run build - Start the local preview server:
npm run preview(typically runs onhttp://localhost:4173) - Run E2E tests pointing to the preview server:
BASE_URL=http://localhost:4173 npm run test:e2e
- Build a clean production bundle:
- Draft PR Script Requirements: The workspace
create-draft-pr.shscript strictly requires the--author,--summary,--test-plan, and--test-evidenceflags. Leaving any of these empty or as a default placeholder will cause the script to abort and refuse to open the draft PR. - Vitest Unit Tests and Environment Variables: Local unit tests inspecting
checkIsAdminthat parseprocess.env.userRolesmust supply a fallbackuserRolesJSON string (e.g.,process.env.userRoles || JSON.stringify({ roles: ['admin'] })) to be self-contained and pass on clean checkouts where.envis absent. - Distinguishing Socket Server Failures vs Empty Data: In components consuming socket-fetched collections (
gigs,pics), set state tonullon connection failure/timeout (CONNECTION_TIMEOUT_MS = 8000) so components render inline error banners (.gigs-error-message,.pics-error-message) rather than rendering an identical empty UI state as a 0-item dataset ([]). - Worktree node_modules Symlink: In temporary git worktrees,
node_modulesis not symlinked by default. Symlink the main repo'snode_modules(ln -s /home/joshua/WebJamApps/JaMmusic/node_modules node_modules) so test tools (tsc,stylelint,vitest) are available. Unlink or exclude the symlink before committing. - TypeScript Number Comparison in Form States: In
@mui/materialdialog forms, numeric fields (such asgigIntervalinside form state) are typed asnumber. Comparing a numeric state variable against string empty (form.gigInterval !== '') will cause a compilation errorTS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.Ensure you checktypeof form.field === 'number'or keep form states properly type-separated. - Testing Library Jest-DOM Import: In Vitest unit tests using DOM element matchers such as
toHaveAttribute,toBeInTheDocument, ortoHaveTextContent, always includeimport '@testing-library/jest-dom';at the top of the spec file to extend Vitest'sexpectmatchers. - Date Formatting & Timezone Mismatch in Vitest Snapshots: React date inputs and localized date string components render local time strings (e.g.
GMT-0500 Eastern Standard Time) when snapshots are updated locally. On CircleCI Linux runners operating inUTC(GMT+0000), timezone mismatches break unit test assertions. Always run snapshot updates withnpm run test:unit-u(orTZ=UTC npx vitest run -u) so snapshot outputs align with CI. - BackendUrl and Production Builds: In production builds,
BackendUrldefaults to an empty string indicating same-origin requests (${BackendUrl}/songresolves to/song).vite.config.tscarries a build-time guard that refuses production builds wheneverBackendUrlresolves tolocalhostunlessALLOW_LOCALHOST_BACKEND=trueis explicitly set (e.g., for local Playwright E2E suites). - E2E & Playwright CI Verification: When an issue specifies continuous verification or execution in CI for Playwright/E2E tests, ensure
.circleci/config.ymlinstalls browser dependencies (npx playwright install --with-deps chromium) and executesnpm run test:e2e, and ensureplaywright.config.tsdefines awebServerwithcommand: 'npm run build && npm run preview -- --port <port>'(e.g.npm run build && npm run preview -- --port 7878) with a local defaultbaseURLso production JSX runtime is generated withoutjsxDEVmismatch errors and E2E tests run self-contained in CI without manual external server startup. In addition, when rebasing a feature branch ontodev, ensure the semver version bump inpackage.jsonis preserved/updated relative to the newdevbase. - Venue Form Optional Enums & Derived-With-Override Attributes (
familyNearby): InEditVenueDialog, optional enum fields (templateOverride,audienceAttention,venueType) must never be sent as empty strings ("") in request payloads, as backend Mongoose schema validation rejects empty strings against allowed enum values; omit or delete empty enum values before sending.familyNearbyis a two-mode field with server-managedfamilyNearbyOverride: when the client sends no explicitfamilyNearbykey, the backend derives proximity from the venue address viaweb-jam-back'sisFamilyNearby()(geo-distance) withfamilyNearbyOverride: false; when a human explicitly toggles the checkbox, the client sends an explicit boolean (true/false), which the backend stores verbatim and setsfamilyNearbyOverride: trueto protect the manual choice from address recomputes. The backend also supportsfamilyNearby: nullto clear the override and restore address derivation. The front-end therefore exposes a 3-state UI ('Auto-derived' vs 'Manual override') with an explicit 'Recompute from address' affordance when overridden, sendingfamilyNearby: nullon save to clear the override and restore derive-by-default, and omitting the key when untouched so existing override status or automatic address recomputes stay in force. - Queue Header Badges vs Widened Collections & Strict Mock Typing: When widening an in-memory collection (e.g. merging sent pitches into outreach records for the Awaiting Reply panel), ensure dedicated queue badges (such as REPLY REVIEW QUEUE) maintain their own state tracking distinct queue items rather than reading the widened set. In unit test mocks, never use raw 'as any'; use strict casts ('as unknown as typeof ...') and explicit interfaces ('IpendingReply'). Additionally, ensure downstream filter side effects (such as venues with sent records being excluded from neverPitchedVenues) are explicitly protected by unit tests.
Branch & memory hygiene
- One branch per task: never create or push any branch other than the one created for the current task.
- Once your PR is merged or closed, its branch is DEAD — never commit to it or
push it again. Follow-up work (including afterthoughts like docs or lessons
learned) starts on a NEW branch off the latest
dev, with its own PR. - Save lessons BEFORE the merge, not after: anything you learned during the task
worth keeping (build quirks, selector gotchas, testing patterns — e.g. the
output of a
/learn-style memory pass) gets committed to this file's Troubleshooting/Memory sections on the SAME task branch while the PR is still open, so it ships inside the PR. A post-merge push to the old branch strands the lesson and forces manual cleanup.
Snyk and security audits
- If a task involves resolving Snyk security failures in a PR or build, and you cannot access the Snyk reports locally (e.g., due to local authorization or API limits), always ask the user to provide the exact Snyk failures and vulnerability IDs first. Do not attempt to guess or audit blindly, as this can lead to going down the wrong path.
- For tasks specifically targeting the resolution of PR checkers, GitLab/GitHub actions, or Snyk security checks, you should proceed with committing and pushing the changes (once local tests and linters are verified green) so the remote pipelines can actually execute and validate the fixes, rather than leaving them as uncommitted working tree edits.