Commit Graph
12 Commits
Author SHA1 Message Date
a57d0e3f85 fix(v3): replace broken window.prompt() in Playlists with in-app uiPrompt modal (#614)
window.prompt() is a silent no-op in the Electron desktop shell, so the
Playlists "New Playlist" and "Rename" buttons and the library's bulk
"add selected songs to a playlist" action did nothing. Route all three
through the existing window.uiPrompt() modal (resolves to the string, or
null on cancel; the handlers were already async). window.confirm() works
in Electron and is left as-is.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:35:30 +02:00
3b2d83d406 feat(folder_library): Folder Library core plugin (#610)
Adds the bundled Folder Library plugin (browse the DLC library by its on-disk
folder tree, in-app folder CRUD, drag-and-drop + dialog song moves, sort/filter,
live search), wired into the classic v2 toolbar and the v3 Songs page.

Includes the screen.js IIFE dedup (unified surface factory) and review fixes:
path-traversal guard on /song/move, folder-delete data-loss fix, plural
/api/plugins/<id> namespace, loose-folder song recognition, error-text escaping,
v3 setLibView null-guard, and tests.

Co-authored-by: Kyle <kyle.j.t@live.co.uk>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:03:54 +02:00
8b4c9b0050 Fix list/tree view: select mode, parts visibility, song actions (#585)
* Fix list/tree view: select mode, parts visibility, song actions

Bring the v3 list/tree view to parity with the grid card:
- Select mode now renders a per-row checkbox + selected-ring, preserves
  expanded artist groups across re-render, and a capture-phase guard
  makes a row/chip click select the song instead of starting playback.
- Always-on favourite / save-for-later / overflow-menu cluster on each
  row, same actions as the grid card.

Rebuild static/tailwind.min.css so the new utilities are compiled in -
notably .sm:flex behind the arrangement chips' "hidden sm:flex" wrapper.
Without it the chips (and #582's badges) render display:none on the
Docker build, which serves the committed CSS; the desktop build looked
fine only because it rebuilds Tailwind from source at bundle time.

Signed-off-by: Sin <deathlysin@outlook.com>

* fix(v3): regenerate tailwind.min.css from source + add tree select tests + CHANGELOG

The committed tailwind.min.css was over-built: 135,578 bytes / 1,428
selectors, with 294 selectors (accent-amber-400, bg-cyan-500,
animate-spin, after:bg-gray-400, …) used in zero core source files —
bloat from a local build scanning outside the repo's content globs. It
would fail CI's rebuild-and-diff and violates the byte-stable rule in
scripts/build-tailwind.sh.

Regenerate via `scripts/build-tailwind.sh` (pinned tailwindcss@3.4.19):
111,491 bytes / 1,134 selectors, byte-identical to a clean rebuild,
still containing the .sm\:flex fix plus every new tree class
(ring-fb-primary, accent-fb-primary, pointer-events-none, …). Docker
chips now render and CI stays green.

Add tests/browser/v3-tree-select.spec.ts:
- select mode keeps expanded artist groups open across the tree
  re-render (fails without loadTree's openArtists capture/restore)
- clicking a row in select mode selects instead of playing

Record the fix under CHANGELOG [Unreleased] -> Fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: Sin <deathlysin@outlook.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:49:02 +02:00
Sin d91995fec3 Revert "Fix list/tree view: select mode, parts visibility, song actions"
This reverts commit ac3c89493d.
2026-06-23 21:12:55 +01:00
Sin ac3c89493d Fix list/tree view: select mode, parts visibility, song actions
- Preserve expanded artist groups across re-renders (was collapsing
  all groups whenever select mode toggled)
- Add select checkbox + ring highlight to tree rows, matching grid
- Add capture-phase select guard on tree clicks so rows/chips toggle
  selection instead of falling through to play
- Always show favorite/save-for-later/overflow-menu buttons on tree
  rows instead of hover-only (matches grid card behaviour)
- Always show arrangement chips on tree rows (no longer hidden below
  the sm breakpoint)

Signed-off-by: Sin <deathlysin@outlook.com>
2026-06-23 21:02:27 +01:00
db4a30085b feat(v3 library): clickable arrangement badges in tree view (#582)
The v3 library tree rows showed no arrangement badges, unlike the grid/card
view. Render the same clickable chips in tree rows so both views match, and
clicking a specific arrangement opens THAT arrangement in the highway.

Extract the grid's chip markup into a shared arrChipsHtml(song) (one
<button data-arr="<index>"> per arrangement, capped at 4) and use it in both
songCard and the tree row. No new wiring needed: wireCards() already binds
[data-arr] → playCard(song, index) → playSong(filename, index) for any
[data-fn] scope, and the arrangement index is preserved through
/api/library/artists. Chips are hidden on the narrowest viewports
(hidden sm:flex) so they don't crowd the dense single-line tree row.

Closes #581

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:05:41 +02:00
af2949677a rename: slopsmith → feedBack, byron → got-feedBack (#537)
* Update GitHub repo references from feedback* to feedBack*

* rename: slopsmith -> feedBack, byron -> got-feedBack

Renames across the entire codebase:
- slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack
- byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack
- /home/byron/ -> /opt/got-feedBack/
- byron@ougsoft.com -> hi@got-feedBack.org
- github.com/byrongamatos/ -> github.com/got-feedback/
- com.byron. -> com.got-feedback.
- SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback
- Protocol/storage strings migrated with read-old/write-new pattern
- window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias

Refs: #rename-slopsmith

* rename: complete regen against current main + fix backward-compat alias

Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of
current main (3 commits had landed since the branch: #572/#554/#574),
resolving the four content conflicts in favour of main's newer content
(autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge).

Completion fixes on top of the mechanical rename:
- Re-apply rename to post-branch content the original rename never saw:
  window.slopsmith(.Tour) consumers in lessons.js / notifications.js /
  onboarding-tour.js, and the matching JS + python tests (autoplay_exit,
  progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env
  vars now match server.py (which reads FEEDBACK_SYNC_STARTUP /
  FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real
  path again.
- Restore the window.slopsmith backward-compat alias dropped during conflict
  resolution, and move the bus aliases to AFTER the _feedBackExisting merge
  block so they reference the fully-assembled object (also fixes the
  loop_api.test.js API-surface regex, which the original PR latently broke).
- Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR)
  and gitignore it.
- Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped
  source labels.

Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669
passed / 1683 collected with 0 import errors; zero residual slopsmith/byron
except the two intentional window.slopsmith aliases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* rename: implement advertised backward-compat + prune dead community plugins

Address gaps where PR #537's "Backward compatibility" section was advertised
but not implemented, and clean up the community plugin list.

Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured):
- New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py
  (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and
  tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI /
  SLOPSMITH_PLUGINS_DIR / etc. deployments keep working.
- Fix the rename collapsing plugins/__init__.py and minigames/routes.py from
  `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant
  `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost).

Storage (app.js update-channel):
- Read feedBack-update-channel, fall back to legacy slopsmith-update-channel,
  and clear the legacy key on write — so a user's update-channel preference
  survives the rename instead of resetting to "stable".

Community plugin list (README): the rename rewrote third-party repo URLs we
don't own. Probed every one; their owners never renamed, so:
- Restore the 13 live community plugins to their real slopsmith-* names.
- Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex
  tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle).
- Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory).

Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors,
rename-sensitive + startup suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:03:01 +02:00
a8ad02739a fix(v3): refresh library accuracy badge right after a song is scored (#574)
The v3 library loaded the best-accuracy map (/api/stats/best) once into
state.accuracy at render time and only refreshed it on a full re-render.
The play->return flow takes the screen-entry fast-path that restores the
cached grid DOM without re-fetching, so a just-earned score stayed
invisible on the card until the next app restart re-ran render().

stats-recorder now emits a `stats:recorded` event (filename/arrangement)
once the scored POST /api/stats resolves on the server -- the correct
moment, since song:stop fires before the POST completes. songs.js
listens: if the library is the active screen it re-fetches
/api/stats/best and patches the affected card/row badge in place;
otherwise it marks the filename dirty and onV3SongsScreenEnter applies
it on return. A failed fetch keeps the entry dirty so a later trigger
retries instead of silently dropping the update.

Badge markup is factored into a shared accuracyBadge(filename, variant)
(grid pill + tree-row percentage, both tagged .fb-acc-badge) so the
in-place repaintAccuracy can find and replace them without a full list
re-render, preserving scroll and pagination. The old empty song:stop
"refresh lazily next render" placeholder is replaced.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:55:57 +02:00
32127bc70b feat: save as .feedpak; discover and load both .feedpak and .sloppak (#553)
* feat: save songs as .feedpak; discover and load both .feedpak and .sloppak

The open song format was renamed sloppak -> feedpak (public spec lives in
the feedback-feedpak-spec repo), but the server still wrote and recognized
only `.sloppak`. The two are byte-identical on disk.

Read both suffixes everywhere songs are discovered, uploaded, and loaded;
writing the new `.feedpak` suffix is handled in the editor plugin repo. Keep
the internal `format` tag `sloppak` so existing feature gates (stems, drums,
keys) are untouched, matching the "internal rename not landed yet" stance.

- lib/sloppak.py: add FEEDPAK_EXT / SLOPPAK_EXT / SONG_EXTS; is_sloppak()
  now matches either suffix (covers all 7 callers).
- server.py: union scan glob over SONG_EXTS; widen loose-folder exclusion,
  settings DLC count, upload gate (_ALLOWED_SONG_EXTS) and zip-magic check;
  refresh user-facing messages to .feedpak.
- static: library format filter relabeled Sloppak -> Feedpak (value stays
  sloppak, matches both); badge text SLOPPAK -> FEEDPAK in v2 + v3;
  filename-suffix detection and upload drag-drop filter accept both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: topkoa <topkoa@gmail.com>

* test: cover .feedpak/.sloppak dual-suffix support

Add tests/test_feedpak_extension.py pinning the four paths PR #553
widened so a refactor can't drop .sloppak back-compat or stop
accepting .feedpak:

- is_sloppak / SONG_EXTS suffix detection (file + dir form, case-insensitive)
- _background_scan discovery glob unions over both suffixes
- POST /api/songs/upload accepts both, rejects wrong suffix + non-zip
- save_settings DLC count includes both suffixes

19 tests, all passing; reuses the existing scan_module / TestClient /
isolate_logging fixtures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: topkoa <topkoa@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
2026-06-22 20:47:56 +02:00
530995dd02 fix(v3): scope song search to the library + keep it above the toolbar on scroll (#560)
The topbar search (#v3-search) rendered on every screen and, on the library
screen, was hidden behind the filter toolbar while scrolling (both were
sticky top-0 z-20 in the #v3-main scroller).

- shell.js: wrap the search in #v3-search-wrap (hidden by default) and toggle
  it in syncActive() so it only shows on #v3-songs; bump the topbar to z-30 so
  it always sits above the toolbar.
- songs.js: drop the toolbar's top-0 and pin it beneath the topbar by measuring
  the topbar height (positionToolbar). A ResizeObserver on #v3-topbar keeps the
  offset correct as the topbar height changes (viewport width, search show/hide)
  and fixes the initial position regardless of render()/syncActive() ordering.

Fixes #559

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:26:52 +02:00
21997f4b5c Fix slow library cover loading: serve sloppak art without unpacking + revalidated caching (#534)
* sloppak: read cover without unpacking + serialize/cap zip unpacks

Album art for a zip-form sloppak was served by resolve_source_dir(), which
unpacks the ENTIRE archive (stems included, ~30 MB) to disk just to read
cover.jpg. On the library grid that meant a full extraction per card on scroll.

- read_cover_bytes(): opens only the cover member from the zip (or reads the
  file for dir-form), with zip-slip guarding. ~4 ms vs a full unpack.
- resolve_source_dir(): per-file lock + bounded global semaphore so concurrent
  callers don't rmtree + re-extract the same dest at once (a race), and a burst
  can't saturate disk/CPU. 8 concurrent calls now dedupe to 1 unpack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* server: serve sloppak art via read_cover_bytes + cache album-art responses

- get_song_art() sloppak branch now reads the cover directly (no full unpack),
  off-thread via asyncio.to_thread.
- All art responses carry Cache-Control: public, max-age=86400. URLs are already
  cache-busted with ?v=<mtime>, so the browser stops re-fetching every cover on
  scroll-back; day bound self-heals any URL missing ?v.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* v3 library: lazy-load + async-decode card cover images

The grid (24 cards/page) and artist-row thumbnails emitted plain <img> with no
loading hint, so a whole page of covers fetched + decoded at once on each
scroll batch. Add loading="lazy" decoding="async" to defer off-screen fetches
and keep image decode off the main thread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* address Codex review: zip cover normalization + correct art revalidation

Findings from the preflight Codex passes:

- sloppak.read_cover_bytes (zip form) read the raw manifest cover string via
  zf.read(), so a non-canonical name like './cover.jpg' or 'art/../cover.jpg'
  404'd. Normalize via safe_join → relative member; reject escape and the
  degenerate root-collapse case ('.', 'subdir/..') like _unpack_zip does.

- Album-art caching is correctness-first: Cache-Control: no-cache plus a strong
  validator, with real conditional handling (Starlette FileResponse emits an
  ETag but doesn't evaluate If-None-Match). All three art paths route through
  _art_conditional/_file_art_response → bodyless 304 on a matching validator.
  A long immutable max-age was rejected because the frontend ?v=<mtime> buster
  is only second-resolution and would pin a same-second rewrite.

- The sloppak cover is validated by CONTENT (sha1 of the bytes), not a stat:
  a dir-form sloppak edited in place changes the cover file's mtime but not the
  directory's, so a dir-stat ETag could emit a stale 304. Content hashing is
  correct for both dir- and zip-form. get_song_art gained an optional request
  (internal get_art caller passes none — safe).

Adds tests/test_sloppak_cover_art.py pinning read_cover_bytes (canonical,
non-canonical, degenerate/escape, dir/zip, webp) and the endpoint's 304 contract
incl. the dir-form in-place-edit no-stale-304 regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 00:22:43 +02:00
byrongamatos 6c110398b4 Clean release snapshot 2026-06-16 18:47:13 +02:00