A backtick inside a comment in a css`` literal ends the literal. It has
cost four sessions across three plans, it is written down in CLAUDE.md,
the skill and NOTES.md, and it was read twice in the session it then
cost a cycle in. Knowledge that has been ignored three times is not a
knowledge problem.
The expense is the report, not the mistake: the literal ends early, the
rest of the CSS parses as JavaScript, and tsc says 'Class static side
incorrectly extends base class static side' pointing at a line of prose
-- or, in a shared module, every test in the suite fails to import and
the output reads like a broken test runner. make dev-headless mean-
while keeps serving the last good bundle.
Detection is exact rather than heuristic: if a backtick in a comment
closed the literal early, the text the parser took as the literal
contains an unterminated /*. Nothing else produces that. Verified both
ways -- clean on the tree, and red on a deliberately broken comment.
CLAUDE.md has claimed since the file was written that commitlint gates
the commit format in CI and that semantic-release runs off it. There was
no commitlint config, no workflow running one, and nothing invoking
.releaserc.yml — so the first thing every contributor and every agent
reads about this repo was false in two places.
scripts/commit-check.sh is the smaller honest answer: the grammar is one
regex, and commitlint would mean a Node dependency tree at the root of a
Go repo to run it. It is a commit-msg hook locally and a CI step over
every commit in a push, and its type list is .releaserc.yml's so the
check and the release rules cannot drift.
The semantic-release half is recorded as configured-but-not-wired rather
than implied to run.
Plan 007 phase 4 is verified by measurement, not by assertion, and
there was no way to produce a number: the fixture library is a few
dozen tracks and cannot show any of the findings.
- `cmd/gentestdata -bulk N` (`make bulkdata`) writes a ~50 000-track
library in 11 s / 466 MB by encoding six clips once and copying
them, while still tagging every file through `backend/tagwriter` —
a library the app cannot read back measures nothing.
- `make sandbox-seed-bulk` seeds from it through the same script and
the same discipline as any other seed: by running the app and
waiting for the real scan.
- `e2e/perf/measure.mjs` (`make perf LABEL=x`, `make perf-compare`)
takes fourteen measurements against a running app and writes them
to a gitignored `.dev/perf/<label>.json`. It wraps every bound Go
method, so "did that refetch the library" is a fact rather than an
inference, and records `longtask` entries, which is where a 25 MB
JSON parse on the main thread shows up and nowhere else.
It is not a spec and does not run in CI.
A coding agent could develop this repo's Go packages and could not
develop the application: every path to running YellowJacket ended in a
blocking GTK window, so 265 bound methods, 46 events, 33 component
directories and 13 stores had exactly one form of verification
available — `tsc --noEmit`.
The unlock is that `wails dev`'s dev server on :34115 serves the real
frontend with the real generated bindings against the same Go backend a
desktop window attaches to, so a plain Chromium under Xvfb gets a fully
functional app. Four test tiers now exist, cheapest first:
- `make ui-test` — 313 Vitest tests in a real browser in ~2 s, no app,
no backend, no display. Works because `frontend/wailsjs/` is a pure
passthrough to `window.go`/`window.runtime`, so faking just those two
globals runs the real bindings and the real store code.
- `make test` — services in-process, asserting on the payload the
frontend would receive, via a new `events.Emit` wrapper.
- `make dev-headless` + `playwright-cli` — the real app, driven
interactively, with an event bridge on `window.__yjEvents` and a
dev-only control surface at `/__test/`.
- `make e2e` — 19 of those flows frozen as Playwright specs.
`events.Emit(ctx, …)` replaces all 35 direct `runtime.EventsEmit` call
sites: wails' `getEvents` `log.Fatalf`s on any context without its
runtime, so those paths could not run under test and a background
worker could take the app down. Four packages had each hand-rolled the
same guard; nine more guarded on `ctx != nil`, which does not help.
`TestNoDirectRuntimeEmits` fails the build on a new one.
Fixtures are generated, not committed (`make testdata`), and seeds are
built by *running the app* — never by hand-writing config and DB rows,
which would be a second description of a valid YJ_HOME.
`.gitea/workflows/ci.yml` is the first workflow here that tests
anything; the other three only package, so `gitea_ci` reported only
packaging jobs and misled anyone asking whether a push was healthy.
Both jobs were prototyped to green in a bare ubuntu:24.04 container
before the YAML was written, which immediately caught `make lint`
linting three configurations that nothing builds: all three passes
omitted `webkit2_41`, so wails resolved webkit2gtk-4.0 — which Arch
still ships and Ubuntu 24.04 dropped.
Operational instructions live in `.pi/skills/yellowjacket-dev/`,
measured discoveries in `.planning/NOTES.md`, and architecture in
`CLAUDE.md` — split by tense, not by topic, because a topical split
gives every new fact two plausible homes. `make skill-check` fails a
commit if the skill cites a make target that does not exist.
Ships the fresh-start schema cleanup: rebuilt explore catalog index
pipeline (dump import, artifact fetch/build, incremental listen-count
refresh), a new download subsystem (Lidarr/Prowlarr/qBittorrent/SABnzbd/
slskd/yt-dlp providers, staging, reconciliation, wanted list), and the
supporting schema/query/store changes across backend and frontend.
Also includes two smaller follow-ups: bump the central index's
rebuild-after cadence from 90 to 180 days, and remove the Explore
"library only" online/offline toggle entirely (frontend-only, no
backend counterpart) rather than carry unused UI/state.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2Agd9af5hE7qzti2ackiS
Add a central job registry that library scans and search index builds
report into, so background work is visible instead of buried in the
settings page.
- backend/jobs: registry with per-job ring-buffer logs, capability-driven
controls, and one coalesced JobsChanged snapshot at 4Hz
- pause survives restart via a job_state table; a paused scan is adopted
back on launch and skipped by the soft scan
- top-bar indicator, popover, details drawer and a Jobs page replacing
the config page's scan UI; per-library start/stop retained
- scan timing breakdown moves into the job log, Full rescan to the Jobs
page; delete the orphaned library-manager component
Also add cmd/indexbuild and cmd/indexexport so the explore index can be
built once centrally rather than by every install, which today streams
~205GB from the ListenBrainz spark dump on first run. indexbuild picks
build/refresh/rebuild from index state; the Gitea workflow runs it on
push, weekly, or manually and publishes only when content changed.
fresh-install no longer defaults YJ_HOME under /tmp: it is tmpfs on most
distros, and the import needs ~6GB of real disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wire up Go's standard profiling toolkit so it's automatically available
in dev builds and completely absent from production. The profiling
package uses build tags (dev/!dev) to eliminate all pprof, trace, and
timing code from release binaries with zero new dependencies.
- backend/profiling: pprof HTTP server on :6060, /debug/trace endpoint,
block/mutex profiling, and TimeOp helper for structured operation timing
- scripts/profile.sh: interactive menu-driven script that auto-selects
free ports (8080-8089) so multiple profiles can be open simultaneously
- Instrumented key operations: app init, database init, player load/restore,
queue set/restore
- Makefile targets: profile, profile-cpu, profile-heap, profile-trace
- .gitignore: exclude trace-*.out and *.pprof artifacts