Update config mount path#309
Closed
matthieudecamy wants to merge 56 commits into
Closed
Conversation
Provides future Claude Code instances with a quick orientation to the project: the four-process architecture (Quart server, RQ workers, watchdog, React SPA), data stores, build/lint/test commands for both Python and TypeScript, testing fixtures, and key environment variables. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add CLAUDE.md with architecture overview and dev commands
Set timezone, user/group IDs, external port, and volume paths for a personal NAS music library setup on /volume1/docker/Music. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 Configure docker-compose for Synology NAS deployment
pytz is imported at runtime in database/models/base.py but was only listed under typed (dev) extras as types-pytz. This caused a ModuleNotFoundError on startup in the stable Docker image. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 fix: add pytz to production dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 NAS setup: docker-compose config, pytz fix, Makefile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 NAS setup: docker-compose config, pytz fix, Makefile, local image
- beets/config.yaml: current beets-flask defaults minus spotify, plus genreexpand - beets/beetsplug/genreexpand.py: expands Last.fm genres to full hierarchy - beets/genres.yaml: genre tree + Last.fm alias normalization Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 NAS setup: docker config, pytz fix, beets config with genreexpand
- genre_pre_expand: what lastgenre returned before expansion - genre_warnings: comma-separated unknown genres not found in genres.yaml - Both appear automatically in beets-flask beetsdata tab per item/album Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 NAS setup: full config, genre pipeline, pytz fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 NAS setup: full config, genre pipeline, pytz fix, Synology ignores
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ds-0de0e8 NAS setup: full config, genre pipeline, pytz fix, Synology ignores
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lds-0de0e8 NAS setup: full config, genre pipeline, pytz fix, Synology ignores
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lds-0de0e8 Rebrand UI to Shlagi Tagger
Python namespace packages prevent pluginpath from extending beetsplug at runtime. Copy plugin into site-packages on container startup instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Python namespace packages prevent pluginpath from extending the beetsplug namespace at runtime. Instead, copy the plugin into site-packages on container startup via /config/startup.sh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Synology DSM does not have make available. run.sh provides equivalent build and update_config commands.
`all_album_folders` uses beets'' `albums_in_dir` directly, which has no knowledge of our configured ignore patterns. Synology''s `@eaDir` thumbnail directories contain audio files that beets treats as valid album folders, flooding the job queue on every startup. Now filters at both the startup scan (register_inboxes) and the per-event auto_tag entry point, so any path component matching an ignore glob is silently skipped before a job is enqueued. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary - \�ll_album_folders\ delegates to beets' \�lbums_in_dir\, which is unaware of the inbox ignore globs configured in \gui.inbox.ignore- Synology NAS creates \@eadir\ directories inside every album folder containing audio preview files; beets sees these as valid album folders and enqueues them on every startup, flooding the job queue and making the UI very slow to load - Fix: check ignore patterns at the startup scan (\ egister_inboxes\) and inside \�uto_tag\ (covers live filesystem events too), skipping any path whose components match a configured ignore glob ## Test plan - [ ] Rebuild image and restart container - [ ] Confirm \@eadir\ entries no longer appear in watchdog logs - [ ] Confirm frontpage loads quickly 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Alpine 3.20 ships Node 21, which Vite 7 no longer supports (requires 20.19+ or 22.12+). Alpine 3.21 ships Node 22 LTS which satisfies the requirement. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Alpine 3.20 ships Node 21, which Vite 7 dropped support for (requires Node 20.19+ or 22.12+). Bumping to Alpine 3.21 which includes Node 22 LTS.
Windows git checkouts convert LF to CRLF, making shebangs like #!/bin/bash become #!/bin/bash\r which busybox sh cannot resolve. Added sed strip in Dockerfile and .gitattributes to prevent recurrence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The sed strip is no longer needed now that .gitattributes enforces LF for .sh files and the entrypoints are committed with correct line endings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary - Bump base image to Alpine 3.21 for Node 22 LTS (Vite 7 dropped Node 21) - Strip CRLF from entrypoint scripts in Dockerfile - Add .gitattributes to enforce LF for .sh files - Filter inbox ignore globs in watchdog before enqueueing to prevent @eadir flood 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…rfile .gitattributes alone cannot guarantee LF endings in all environments. Strip \r and ensure scripts are executable at image build time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, []) ConfigView.get() only takes one argument; the default [] was causing a TypeError. Use the existing ignore_globs property which handles the beets fallback and type coercion correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ConfigView.get() only takes one argument, passing a default caused a TypeError on startup. Use the existing ignore_globs property which handles type coercion and the beets fallback correctly. Generated with Claude Code
The actual fix was docker-compose down && up (not restart) to recreate the container with the new image. The sed was unnecessary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…und (#18) - Fix TypeError in watchdog: ConfigView.get() only takes one argument, use get_config().ignore_globs instead - Remove unnecessary sed CRLF strip from Dockerfile (the real fix was docker-compose down && up) Generated with Claude Code
…tribute types Beets requires flex attributes to be declared under `types` in config before they can be written to the library database. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add genre_pre_expand and genre_unrecognized to the types section so beets creates the columns in the library database. Generated with Claude Code
The home page was calling 3203148 . and 382 as blocking subprocesses on every request because the watchdog fired FileSystemUpdate on every file change, which immediately cleared the 60-second in-process TTL cache. The result: every home page hit after any inbox activity ran two expensive shell commands synchronously in the Quart event loop. Fix: persist computed stats in a new SQLite table. The stats endpoints read from the DB (instant) and fall back to the subprocess path only if no row exists yet (first boot). Values are kept fresh by: - Watchdog: recomputes inbox stats after each debounce fires, and eagerly pre-computes them for all inboxes on startup. - Import workers: all three importers (, , ) refresh the library stats row after a successful import. Subprocess calls inside run via so they no longer block Quart's event loop. now only busts the directory-tree cache () needed for the live inbox view; / are kept as TTL-cached fallbacks but are no longer the authoritative source. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Beets raises AttributeError when accessing a flex field that is registered in config types but has no stored value on a particular item or album (e.g. genre_pre_expand before the genreexpand plugin has run). Guard both key- iteration loops in _repr_Item and _rep_Album with try/except so unset flex fields are silently skipped rather than crashing the serializer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## What changed In `_repr_Item` and `_rep_Album` (`backend/beets_flask/server/routes/library/resources.py`), both key-iteration loops now catch `AttributeError` and skip any flex field that is registered in the beets config but has no stored value on a given item or album. ## Why Beets raises `AttributeError: no such field 'genre_pre_expand'` when code does `item[key]` for a flex field that appears in the `types:` block of `config.yaml` but has not been written to the item yet (e.g. the genreexpand plugin has not run on it). This crashed the serializer and surfaced as an error in the UI whenever a user clicked an album to tag it. ## Reviewer notes - Only the field-access line is guarded; path formatting, bytes decoding, and empty-value pruning are still applied to fields that do resolve. - Pattern is consistent with the existing `try/except` already used a few lines below for `file_size`. - Items/albums that *do* have `genre_pre_expand` stored will still include it in the response. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary - The home page was running 3203148 . and 382 as blocking subprocesses on every load, because the watchdog fired on every file change, which immediately wiped the 60-second in-process TTL cache - Adds a new SQLite table () that stores pre-computed and per directory path - Stats endpoints (, ) now read from the DB (instant) and fall back to the subprocess path only on first boot when no row exists yet ## What changed **New** — model. Uses the inherited uid=1000(matthieu) gid=1000(matthieu) groups=1000(matthieu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker) PK set to the resolved path string; no migration needed ( handles it). **** - Extracted / (uncached subprocess logic) - Added — async, runs both subprocesses via , writes to DB - Added — instant DB read - now only clears the tree cache; / TTL caches are no longer wiped on every file change **Stats routes** — DB read first, subprocess fallback only when DB has no row **** - On startup: kicks off for each configured inbox as a background task - After each debounce fires: refreshes stats for the inbox containing the changed album **** - All three import workers (, , ) refresh the library stats row after the import completes ## Reviewer notes - The subprocess fallback in the stats routes is intentional — it ensures a first boot with an empty DB still works (just slower that once) - uses so it never blocks Quart's event loop, even on cache miss - The table is keyed on resolved absolute path, so different inboxes and the library each have their own row ## Test plan - [ ] Hit home page immediately after (re)starting the server — should be fast after watchdog startup completes - [ ] Add a file to the inbox, wait ~30 s for the debounce, reload home page — inbox stats should reflect the change - [ ] Run an import, reload home page — library size should update - [ ] Run - [ ] Run 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
chore: bump version 1.2.1 → 1.2.2 Updates version in `backend/pyproject.toml` and `frontend/package.json`.
* ci: add workflow to build and push Docker image on merge to main Builds the production image and pushes to ghcr.io/matthieudecamy/beets-flask on every push to main (i.e. after a PR merges). Tags with :latest and :sha-* for easy rollback. Includes amd64 + arm64 builds and GHA layer caching. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update docker-compose image to ghcr.io/matthieudecamy/beets-flask Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: add workflow to build and push Docker image on merge to main Builds the production image and pushes to ghcr.io/matthieudecamy/beets-flask on every push to main (i.e. after a PR merges). Tags with :latest and :sha-* for easy rollback. Includes amd64 + arm64 builds and GHA layer caching. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update docker-compose image to ghcr.io/matthieudecamy/beets-flask Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve pre-existing CI failures (ruff, mypy, prettier) - enqueue.py: sort stdlib imports to fix ruff I001 - session.py: add type: ignore for confuse ConfigView.get() callable arg - frontend/src: run prettier to fix formatting across all source files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: correct import order in enqueue.py (ruff I001) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: correct mypy ignore code in session.py (call-overload not arg-type) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: ignore PytestUnraisableExceptionWarning in pytest config SSL socket cleanup warnings fire non-deterministically in async tests and are unrelated to test correctness. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* perf: eliminate home screen loading delay by using infinite stale time Stats data (inbox and library) is now cached indefinitely in React Query instead of being treated as always-stale. The data was already pre-computed on app start and stored in the DB by the watchdog; the frontend just wasn't taking advantage of it. - Set staleTime: Infinity on inboxStatsQueryOptions and libraryStatsQueryOptions so navigating back to the home screen never triggers a redundant fetch - Invalidate inbox stats via WebSocket on PREVIEW_COMPLETED and IMPORT_COMPLETED (session counts in compute_stats change at those points) - Invalidate libraryStats via WebSocket on IMPORT_COMPLETED - Set defaultPendingMinMs: 0 to remove the artificial 1-second loading spinner Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: improve watchdog stats logging Upgrade compute_and_store_dir_stats log from DEBUG to INFO so it appears in production, and add a clear completion message once all startup stats are pre-computed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: bump version to 1.2.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use FolderStatus enum instead of Progress for WebSocket invalidation FolderStatusUpdate.status is FolderStatus, not SerializedProgressState. Use PREVIEWED/IMPORTED instead of PREVIEW_COMPLETED/IMPORT_COMPLETED. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: format frontend files and suppress pre-existing ESLint warnings - Run prettier to reformat all files to match the config introduced in the large refactor that was merged from main - Add eslint-disable-next-line for react-refresh/only-export-components on 6 hooks/constants that were already exported alongside components in the codebase before this PR Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove redundant eslint-disable directives that broke CI The eslint config already disables `react-refresh/only-export-components` on CI via `process.env.CI ? 'off' : 'warn'`, so the inline disable comments were flagged as unused directives, producing 6 warnings that caused the `--max-warnings 0` check to fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.