feedBack/plugins
Byron Gamatos 1702afa379
Some checks are pending
ship-ci / ci (push) Waiting to run
feat(career): extract the whole setlist before the gig starts (no more waiting between songs) (#971)
* feat(career): extract the whole setlist before the gig starts

A feedpak is a zip, and the first play of one pays for its extraction into
sloppak_cache. Inside a set that cost landed BETWEEN songs: the player finished
a number and then sat there waiting for the next one to unpack, mid-gig.

A setlist is a known list up front, so unpack it all while the poster is still on
screen. New POST /gigs/prepare walks the set through resolve_source_dir; the
poster's Play button shows "Preparing set…" while it runs.

Best-effort by design, at every level:
  - a corrupt pak in the set does not sink the prepare (it is reported in
    `failed`; the play itself surfaces the error exactly as it does outside a
    gig — slow beats blocked)
  - a host without the library resolvers degrades to a no-op rather than 500
  - a failed request just falls through to the old lazy extraction

Ordering matters and is pinned: the set is unpacked BEFORE the stage is borrowed
(venue/viz overwritten) and before the queue starts, so a proposal cancelled
while unpacking leaves nothing half-applied to unwind.

Tests unpack REAL zips rather than mocking the extractor: every song of the set
lands on disk before the first note, a re-prepare does not duplicate the unpack,
one bad pak still leaves the good one prepared, and no-library / empty-setlist
degrade cleanly. 18/18.

NB the other half of the gig report — the per-song results popup interrupting
the set (and worse, claimAutoExit'ing so the queue would not advance until it was
dismissed) — is fixed in the note_detect plugin repo, which is not part of this
checkout.

* fix(career): bound the prepare request; validate the setlist (PR #971 review)

Both CodeRabbit findings were right.

1. A HUNG PREPARE COULD BLOCK THE GIG FOREVER.

   `await fetch(...)` only rejects on a network ERROR. A server that accepts the
   connection and then never answers hangs indefinitely — and the gig would never
   start. That makes this optimisation the exact thing the PR promises it can
   never be: the reason you cannot play.

   The request is now bounded by an AbortController (PREPARE_TIMEOUT_MS, generous
   because unpacking a setlist is real work — but a CEILING, not a wait). Past it
   we start the gig and let the first play extract lazily, as it always did. The
   Play button is restored in a `finally`, so a timeout cannot strand the poster
   on "Preparing set…" with Play disabled — which would have been the same bug
   wearing a different hat.

2. THE `songs` BODY WAS UNVALIDATED.

   A str is iterable: "abc" would have prepared three one-character "songs". And
   the endpoint unpacks zips, so an arbitrary caller could ask for unbounded work.
   Now list-only, string entries, blanks dropped, capped at MAX_GIG_SONGS.

Tests: the fetch is abortable and the button is re-enabled on EVERY path
including the abort; non-list bodies, non-string/blank entries, and an
oversized setlist. 50 career tests, JS 5/5, eslint clean.

* fix(career): path-traversal guard on prepare; a cap test that actually tests the cap

CodeRabbit again, and the first one is a real hole I put there.

1. PATH TRAVERSAL. sloppak.resolve_source_dir() does a bare `dlc_root / filename`
   with NO containment guard — so `../../x` walks straight out of the library, and
   my new endpoint handed it attacker-supplied filenames. Every filename now goes
   through _resolve_dlc_path first, the same check every other filename-bound
   handler applies. Pinned: `..`, backslash traversal, an absolute POSIX path and
   a Windows drive path are all refused, and nothing outside the library is
   unpacked.

2. THE CAP TEST WAS VACUOUS. It asserted `prepared == 0` against a fixture with no
   library — where the endpoint exits before extraction — so it passed whether or
   not MAX_GIG_SONGS existed. It now runs against a real library and asserts the
   endpoint CONSIDERED at most MAX_GIG_SONGS of the 82 it was handed. Verified to
   fail when the cap is removed.

   Same class of mistake as the notedetect gigBlock: a test that passes for the
   wrong reason. Worth saying out loud since it is twice in one day.

3. E702 — semicolon-joined statements in the new tests, split.

51 career tests; full suite green.
2026-07-15 00:36:20 +02:00
..
achievements feat(achievements): wall sync drain worker + review fixes (epic PR3) (#592) 2026-06-24 17:01:48 +02:00
app_tour_library rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
app_tour_settings rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capability_inspector rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
career feat(career): extract the whole setlist before the gig starts (no more waiting between songs) (#971) 2026-07-15 00:36:20 +02:00
drum_highway_3d fix(highways): validate panel index before indexing the camera map 2026-07-09 16:33:57 -04:00
folder_library perf(folder_library): render only the songs on screen — 1.3M DOM nodes -> ~30 (#965) (#967) 2026-07-14 21:37:22 +02:00
highway_3d fix(highway): a SUPERSEDED renderer init is not a FAILED one (#970) 2026-07-15 00:36:16 +02:00
input_setup fix(input_setup): stop collapsing audio driver-type variants in the wizard (#627) 2026-06-28 20:25:49 +02:00
keys_highway_3d fix(keys_highway_3d): stop auto-connect clobbering the global MIDI device (#825) 2026-07-09 22:40:33 +02:00
minigames fix(minigames): drain legacy slopsmith pending queue + alias SDK (#578) 2026-06-23 16:30:00 +02:00
tuner fix(tuner): anchor injectPlayerButton to a direct-child button (feedBack#800) 2026-07-07 09:54:06 +02:00
__init__.py fix(plugins): make a module plugin actually re-evaluate on reload (#879) (#897) 2026-07-12 00:19:59 +02:00