Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
1f59d6a
feat: add Discogs music scraper plugin (#87)
fabiodalez-dev Mar 30, 2026
32f574a
feat: music-aware labels, Discogs settings, CodeRabbit fixes
fabiodalez-dev Mar 30, 2026
498c2bc
feat: add Discogs to bundled plugins + 7 E2E tests
fabiodalez-dev Mar 30, 2026
ea3a5d0
test: add Discogs import E2E test (barcode → scraping → save)
fabiodalez-dev Mar 30, 2026
7016608
fix: prevent barcode from being stuffed into isbn13 for music media
fabiodalez-dev Mar 30, 2026
9400e5e
fix: handle SweetAlert confirmation in Discogs import test
fabiodalez-dev Mar 30, 2026
873e73f
feat: human-readable format names + HTML tracklist
fabiodalez-dev Mar 30, 2026
f1786b0
feat: add tipo_media column for media type filtering (#87)
fabiodalez-dev Mar 30, 2026
e3c383b
fix: Discogs enrichment + CodeRabbit review fixes
fabiodalez-dev Mar 30, 2026
ed8694c
fix: address CodeRabbit review findings (round 3)
fabiodalez-dev Mar 30, 2026
2fb459d
fix: address CodeRabbit review (round 4)
fabiodalez-dev Mar 30, 2026
063e8ff
test: add 5 advanced Discogs E2E tests
fabiodalez-dev Mar 30, 2026
5724f16
fix: admin detail uses formatTracklist for music records
fabiodalez-dev Mar 30, 2026
9ec7d98
feat: extend music plugin with MusicBrainz + Deezer sources
fabiodalez-dev Mar 30, 2026
475e8b5
fix: use unique barcode in plugin test (avoid EAN UNIQUE conflict)
fabiodalez-dev Mar 30, 2026
5adf824
fix: CodeRabbit round 5 + Schema.org media-specific shapes
fabiodalez-dev Mar 30, 2026
c7a419c
Fix Discogs review follow-ups and multisource tests
fabiodalez-dev Mar 30, 2026
b5a247d
Address follow-up CodeRabbit review fixes
fabiodalez-dev Mar 31, 2026
63df3aa
fix: 4 logic bugs from code review
fabiodalez-dev Mar 31, 2026
ff03d06
chore: bump version to 0.5.4 (matches migration filename)
fabiodalez-dev Mar 31, 2026
610c29c
fix: CodeRabbit round 6 — logic hardening
fabiodalez-dev Mar 31, 2026
a12f4d4
fix: CodeRabbit round 7 — 3 quick fixes
fabiodalez-dev Mar 31, 2026
6dfd7e5
test: add 10 reusable PR #100 feature tests + fix PHPStan level 5
fabiodalez-dev Mar 31, 2026
328a9cb
fix: bulk cover download message — 'senza ISBN/barcode' instead of 'l…
fabiodalez-dev Mar 31, 2026
5c0d069
fix: genres from Discogs now saved as parole_chiave
fabiodalez-dev Mar 31, 2026
1d46bf9
fix: CodeRabbit round 8 — 8 fixes
fabiodalez-dev Mar 31, 2026
3a3469f
fix: address 10-agent review findings + add .coderabbit.yaml
fabiodalez-dev Apr 1, 2026
fc399cb
fix: address CodeRabbit review #11 on PR #100
fabiodalez-dev Apr 1, 2026
875a710
fix: PR #100 10-agent review follow-up + barcode-ISBN guard hardening
fabiodalez-dev Apr 15, 2026
4ff5d3b
test: pre-clean existing barcode in discogs-import beforeAll
fabiodalez-dev Apr 15, 2026
5ef3db3
chore: ignore local-release artifacts and local release script
fabiodalez-dev Apr 15, 2026
c9bd82c
fix: add missing placeholder for metadata in autoRegisterBundledPlugins
fabiodalez-dev Apr 15, 2026
fb1e881
fix: correct bind_param type order in autoRegisterBundledPlugins
fabiodalez-dev Apr 15, 2026
434b46f
test: activate optional discogs plugin via SQL in test 1
fabiodalez-dev Apr 15, 2026
76daf8d
test: add installed-state guards to prevent cross-spec pollution
fabiodalez-dev Apr 15, 2026
402a4a1
test: use RUN_ID in book title + author name in smoke-install
fabiodalez-dev Apr 15, 2026
5f1d302
test: search by RUN_ID in Edit book verification
fabiodalez-dev Apr 15, 2026
e4c1181
fix: address 8-agent review findings (critical + important + security)
fabiodalez-dev Apr 15, 2026
bdd4184
fix: use SecureLogger::info for success events (CodeRabbit feedback)
fabiodalez-dev Apr 15, 2026
9f39049
fix: stop excluding public/installer/assets from release ZIP
fabiodalez-dev Apr 16, 2026
ab011be
test: fix assertion string after log message rewording
fabiodalez-dev Apr 16, 2026
df82f9e
fix: address all CodeRabbit PR #100 findings (19 files)
fabiodalez-dev Apr 16, 2026
e1a58ed
feat: bulk ISBN enrichment service with cron + admin UI (#99)
fabiodalez-dev Apr 16, 2026
47de8b9
fix: use copertina_url (not copertina) in bulk-enrich test SQL
fabiodalez-dev Apr 16, 2026
7f07916
fix: CSRF + copertina_url in bulk-enrich tests
fabiodalez-dev Apr 16, 2026
2bdba5a
fix: set_time_limit(300) in batch start + unique ISBNs for no-overwri…
fabiodalez-dev Apr 16, 2026
71af10c
fix: use json.results path in all bulk-enrich tests
fabiodalez-dev Apr 16, 2026
26aa486
fix: add skipped counter to batch summary + use results path in test 14
fabiodalez-dev Apr 16, 2026
e5ed75e
fix: use unique ISBN for tipo_media preservation test 15
fabiodalez-dev Apr 16, 2026
742e2c4
fix: unique ISBN for EAN preservation test 17
fabiodalez-dev Apr 17, 2026
8810660
fix: address CodeRabbit round 2 — prepare guard, sort fix, XSS hardening
fabiodalez-dev Apr 17, 2026
7f6c6a1
fix: replace public/installer/assets symlink with real directory
fabiodalez-dev Apr 17, 2026
b15d7c1
Revert "fix: replace public/installer/assets symlink with real direct…
fabiodalez-dev Apr 17, 2026
ffa43c5
Reapply "fix: replace public/installer/assets symlink with real direc…
fabiodalez-dev Apr 17, 2026
77fba6b
fix: address 16 CodeRabbit Major findings (PR #100)
fabiodalez-dev Apr 17, 2026
23db85d
chore: bump version to 0.5.5, add deezer + musicbrainz bundled plugins
fabiodalez-dev Apr 17, 2026
9b69fcc
fix: address CodeRabbit round N (20 findings) — last round before squash
fabiodalez-dev Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 154 additions & 1 deletion .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,155 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
# Pinakes — CodeRabbit Configuration
# PHP/Slim 4 library management system with MySQL

language: "it-IT"

tone_instructions: |
Sii conciso e diretto. Concentrati su bug reali, vulnerabilità di sicurezza
e violazioni delle regole del progetto. Evita suggerimenti stilistici minori.

early_access: true

reviews:
max_files: 200
profile: "assertive"
request_changes_workflow: false
high_level_summary: true
poem: false
review_status: true

# ── File Filters ──────────────────────────────────────────────────
path_filters:
- "!vendor/**"
- "!node_modules/**"
- "!public/assets/tinymce/**"
- "!public/assets/fontawesome/**"
- "!public/assets/choices/**"
- "!public/assets/flatpickr/**"
- "!public/assets/sweetalert2/**"
- "!*.min.js"
- "!*.min.css"
- "!*.map"
Comment on lines +29 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

CodeRabbit path_filters glob syntax recursive ** vs * matching

💡 Result:

In CodeRabbit's path_filters, glob patterns follow the minimatch library syntax. The '' pattern enables recursive directory matching (zero or more directories/subdirectories), while '' matches zero or more characters within a single path segment (non-recursive, does not cross directory boundaries). Key differences and examples from CodeRabbit docs: - '!dist/': Excludes the 'dist' directory and all files/subdirectories recursively within it. - '!/dist/': Excludes any 'dist' directory (at any level) and its recursive contents. - '!/node_modules/': Excludes any 'node_modules' directory anywhere and recursively. - '!/.min.js': Excludes all minified JS files anywhere in the repo (recursive due to leading '/', combined with file pattern). - 'src/': Includes 'src' directory and all subdirectories/files recursively. In contrast: - '*' alone would match any file or directory name in the current level only (e.g., equivalent to '' with noglobstar option enabled). - Use '/' to separate path segments; patterns are evaluated relative to the repository root. This syntax keeps reviews focused by excluding build artifacts, dependencies, etc. Patterns with '!' prefix exclude; without include/override exclusions. Defaults include many recursive excludes like '!/dist/'. For full details, see CodeRabbit's reference to minimatch documentation.

Citations:


🏁 Script executed:

# Check the .coderabbit.yaml file at the specified lines
cat -n .coderabbit.yaml | sed -n '25,35p'

Repository: fabiodalez-dev/Pinakes

Length of output: 415


🏁 Script executed:

# Search for minified JS, CSS, and map files in the repository to see if they'd be affected
fd -e min.js -e min.css -e map --type f | head -20

Repository: fabiodalez-dev/Pinakes

Length of output: 1069


🏁 Script executed:

# Also check the full path_filters section to understand context
cat -n .coderabbit.yaml | sed -n '20,35p'

Repository: fabiodalez-dev/Pinakes

Length of output: 615


Use recursive glob patterns for minified and map file exclusions.

The patterns !*.min.js, !*.min.css, and !*.map match only files at the repository root. Minified assets and source maps exist in nested paths (e.g., public/assets/js/sortable.min.js, public/assets/css/sortable.min.css, public/assets/star-rating/dist/star-rating.min.js), so these exclusions fail to filter them. While specific paths like !public/assets/tinymce/** are excluded, other build artifacts in subdirectories will still be reviewed.

🛠️ Proposed fix
-    - "!*.min.js"
-    - "!*.min.css"
-    - "!*.map"
+    - "!**/*.min.js"
+    - "!**/*.min.css"
+    - "!**/*.map"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- "!*.min.js"
- "!*.min.css"
- "!*.map"
- "!vendor/**"
- "!node_modules/**"
- "!public/assets/tinymce/**"
- "!public/assets/fontawesome/**"
- "!public/assets/choices/**"
- "!public/assets/flatpickr/**"
- "!public/assets/sweetalert2/**"
- "!**/*.min.js"
- "!**/*.min.css"
- "!**/*.map"
- "!pinakes-*.zip"
- "!pinakes-*.sha256"
- "!test-results/**"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.coderabbit.yaml around lines 29 - 31, The exclusion globs "!*.min.js",
"!*.min.css", and "!*.map" only match files at repo root; update them to
recursive patterns so minified assets and source maps in subdirectories are
excluded—replace those lines with "!**/*.min.js", "!**/*.min.css", and
"!**/*.map" (keeping existing specific exclusions like
"!public/assets/tinymce/**" untouched).

- "!pinakes-*.zip"
- "!pinakes-*.sha256"
- "!test-results/**"

# ── Path-Specific Review Instructions ──────────────────────────────
path_instructions:
# Controllers — input validation, auth, soft-delete
- path: "app/Controllers/**"
instructions: |
- CRITICO: ogni query sulla tabella `libri` DEVE avere `AND deleted_at IS NULL`
- Verifica che `getParsedBody()` non sia usato per JSON — serve `json_decode((string)$request->getBody())`
- Input utente: validare e sanitizzare PRIMA dell'uso
- Sessione: `$_SESSION['user']['id']` (NON `$_SESSION['user_id']`)
- Eccezioni: catturare `\Throwable` non `\Exception` (strict_types TypeError extends \Error)
- Logging: `SecureLogger::error()` non `error_log()` per contesti sensibili
- Route: mai hardcodare percorsi URL, usare `route_path('key')` o `RouteTranslator::route('key')`
- Export CSV: tipo_media deve essere incluso, usare stringa vuota come fallback (non 'libro')

# Models / Repository — query safety
- path: "app/Models/**"
instructions: |
- CRITICO: ogni SELECT/UPDATE/DELETE sulla tabella `libri` DEVE avere `AND deleted_at IS NULL`
- Soft-delete: nullificare isbn10, isbn13, ean quando si fa soft-delete (prevent unique constraint violations)
- Transaction safety: mai annidare `begin_transaction()` in mysqli (causa commit implicito)
- Pattern: verificare `@@autocommit` per rilevare transazioni in corso
- hasColumn() guard per colonne aggiunte in migrazioni recenti (backward compat)
- tipo_media: usare `array_key_exists` guard, non sovrascrivere il valore se non esplicitamente fornito

# Views — escaping, XSS prevention
- path: "app/Views/**"
instructions: |
- CRITICO: `htmlspecialchars(url(...), ENT_QUOTES, 'UTF-8')` in TUTTI gli attributi HTML (href, action, src)
- `route_path()` richiede lo stesso escaping negli attributi HTML
- PHP->JS: usare `json_encode(..., JSON_HEX_TAG)` per qualsiasi dato PHP inserito in JavaScript
- TinyMCE: SEMPRE includere `model: 'dom'` e `license_key: 'gpl'` in ogni `tinymce.init({})`
- Mai usare `HtmlHelper::e()` nelle view — usare `htmlspecialchars(..., ENT_QUOTES, 'UTF-8')`
- Schema.org: ogni tipo_media deve avere il proprio branch con proprietà specifiche (non mescolare Book con CreativeWork)
- DataTable: ogni valore da API deve passare per `escapeHtml()` prima del rendering

# Support classes — helpers, utilities
- path: "app/Support/**"
instructions: |
- MediaLabels: `isMusic()` deve essere autoritativo su tipo_media quando impostato
- `inferTipoMedia()`: attenzione ai false positive su token corti ('cd' matcha 'CD-ROM', 'lp' matcha parole con 'lp')
- `formatTracklist()`: deve rilevare HTML pre-formattato (`<ol>`) e restituirlo as-is
- PluginManager: usare `\Throwable` non `\Exception`, `BundledPlugins::LIST` centralizzato
- Route translation: mai hardcodare percorsi, usare `RouteTranslator::route('key')`

# Plugins — API safety, rate limiting
- path: "storage/plugins/**"
instructions: |
- SICUREZZA: ogni chiamata curl DEVE avere CURLOPT_PROTOCOLS (HTTP/HTTPS only), CURLOPT_MAXREDIRS, CURLOPT_CONNECTTIMEOUT, CURLOPT_SSL_VERIFYPEER
- SSRF: validare/castare ID esterni (es. releaseId a int) prima di usarli in URL
- Rate limiting: deve essere elapsed-based (microtime) e static (persistere tra istanze)
- Ogni `curl_exec()` deve avere `curl_error()` check con logging
- Hook registration: transazione + rethrow on failure
- Non enrichire dati di libri con cover musicali (gate su resolveTipoMedia)

# Migrations — versioning, idempotency
- path: "installer/database/migrations/**"
instructions: |
- CRITICO: il nome del file di migrazione DEVE avere versione <= version.json (altrimenti viene silenziosamente saltata)
- L'updater usa `version_compare($migrationVersion, $toVersion, '<=')` — versioni superiori sono IGNORATE
- Ogni migrazione DEVE essere completamente idempotente (IF NOT EXISTS, IF @col_exists = 0, etc.)
- LIKE patterns: evitare `%cd%` e `%lp%` che matchano false positive ('CD-ROM', parole con 'lp') — usare REGEXP word boundaries
- Se servono più migrazioni per una release: unirle in UN file con la versione della release

# Translations — completeness
- path: "locale/**"
instructions: |
- Ogni chiave presente in it_IT.json DEVE essere presente anche in en_US.json e de_DE.json
- Le chiavi di traduzione devono corrispondere esattamente (case-sensitive)
- I placeholder (%s, %d) devono essere preservati in tutte le lingue
- Nuove chiavi aggiunte nel codice PHP/JS devono essere aggiunte in TUTTE le lingue

# Tests — E2E patterns
- path: "tests/**"
instructions: |
- I test E2E richiedono `/tmp/run-e2e.sh` per credenziali DB/admin
- `--workers=1` obbligatorio per esecuzione seriale
- SweetAlert: dopo form submit, verificare e cliccare `.swal2-confirm`
- Choices.js: usare `fill` + `waitForTimeout` + click suggestion
- Flatpickr: interagire via JS evaluate, non click diretto
- Pulizia dati test: FK-safe order (prima tabelle figlie, poi padri)

# Release scripts
- path: "scripts/**"
instructions: |
- MAI creare ZIP manualmente — SEMPRE usare `create-release.sh`
- Lo script verifica 9 file critici nel ZIP prima del rilascio
- `git archive` usa file COMMITTATI, non la working directory
- Verificare che `public/assets/tinymce/models/dom/model.min.js` sia nel ZIP

# ── Auto Review Settings ───────────────────────────────────────────
auto_review:
enabled: true
drafts: false

# ── Tools ──────────────────────────────────────────────────────────
tools:
phpstan:
enabled: true
shellcheck:
enabled: true
semgrep:
enabled: true
gitleaks:
enabled: true
yamllint:
enabled: true

# ── Chat ──────────────────────────────────────────────────────────────
chat:
auto_reply: true

# ── Knowledge Base ────────────────────────────────────────────────────
knowledge_base:
opt_out: false
learnings:
scope: "local"
issues:
scope: "auto"
pull_requests:
scope: "auto"
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* text=auto

# Exclude from release archives (git archive)
public/installer/assets export-ignore
# NOTE: public/installer/assets/ MUST be in the ZIP — contains installer.js
# and style.css. Excluding it causes step 2 (test connection) to silently fail.
tests/ export-ignore
test/ export-ignore
.github/ export-ignore
Expand Down
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ storage/plugins/goodlib/*
!storage/plugins/goodlib/*.md
!storage/plugins/goodlib/views/
!storage/plugins/goodlib/views/*.php
!storage/plugins/discogs/
storage/plugins/discogs/*
!storage/plugins/discogs/*.php
!storage/plugins/discogs/*.json
!storage/plugins/discogs/*.md
!storage/plugins/discogs/views/
!storage/plugins/discogs/views/*.php
!storage/plugins/deezer/
storage/plugins/deezer/*
!storage/plugins/deezer/*.php
!storage/plugins/deezer/*.json
!storage/plugins/deezer/*.md
!storage/plugins/deezer/views/
!storage/plugins/deezer/views/*.php
!storage/plugins/musicbrainz/
storage/plugins/musicbrainz/*
!storage/plugins/musicbrainz/*.php
!storage/plugins/musicbrainz/*.json
!storage/plugins/musicbrainz/*.md
!storage/plugins/musicbrainz/views/
!storage/plugins/musicbrainz/views/*.php

# Premium plugin - never track (private/commercial)
storage/plugins/scraping-pro/
Expand Down Expand Up @@ -204,6 +225,7 @@ desktop.ini
# Test Artifacts
# ========================================
.playwright-mcp/
test-results/

# ========================================
# Development Documentation (not for distribution)
Expand Down Expand Up @@ -402,6 +424,14 @@ hackernews.md
docs/reference/bug-gemini.md
docs/reference/analisi-sicurezza.md
scripts/generate_dewey_json.py
scripts/create-release-local.sh
scripts/reinstall-test.sh
pinakes-v*-local.zip
pinakes-v*-local.zip.sha256

# Reinstall / upgrade regression runbook (local-only, not committed)
reinstall_test.md
tests/manual-upgrade-real.spec.js
docs/reference/start-server.md
docs/reference/security-audit-report.md
docs/reference/routes-to-add.md
Expand All @@ -423,6 +453,7 @@ internal/
updater.md
updater_new.md
scraping-pro-*.zip
scraping-pro-*.zip.sha256
fix-autoloader.php
test-upgrade/
.playwright-cli/
72 changes: 66 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

Pinakes is a self-hosted, full-featured ILS for schools, municipalities, and private collections. It focuses on automation, extensibility, and a usable public catalog without requiring a web team.

[![Version](https://img.shields.io/badge/version-0.5.3-0ea5e9?style=for-the-badge)](version.json)
[![Version](https://img.shields.io/badge/version-0.5.5-0ea5e9?style=for-the-badge)](version.json)
[![Installer Ready](https://img.shields.io/badge/one--click_install-ready-22c55e?style=for-the-badge&logo=azurepipelines&logoColor=white)](installer)
[![License](https://img.shields.io/badge/License-GPL--3.0-orange?style=for-the-badge)](LICENSE)

Expand All @@ -24,19 +24,79 @@ Pinakes is a self-hosted, full-featured ILS for schools, municipalities, and pri

---

## What's New in v0.5.3
## What's New in v0.5.5

### 🔍 Cross-Version Consistency Fixes (v0.4.9.9–v0.5.2)
### 📥 Bulk ISBN Enrichment (#87 follow-up)

- **New Admin page `/admin/libri/bulk-enrich`** — automatic enrichment of books with missing covers/descriptions using their ISBN/EAN
- **Manual batch** — process 20 books per click through all active scraping plugins (Open Library, Google Books, Discogs, MusicBrainz, Deezer, scraping-pro if installed). Rate-limited to 1 request per 2 minutes to protect upstream APIs
- **Cron-driven** — configurable background enrichment via `scripts/bulk-enrich-cron.php` with atomic `flock(LOCK_EX|LOCK_NB)` locking
- **No-overwrite guarantee** — only fills NULL or empty fields, never touches populated data
- **Empty-string safe** — `NULLIF(TRIM(col), '')` on `isbn13/isbn10/ean` so legacy rows with blank identifiers don't shadow populated ones

### 🔌 New bundled scraping plugins

- **Discogs** — music metadata (CD, vinyl, cassette) via UPC/EAN barcode or text search. Registers 4 hooks (`scrape.isbn.validate`, `scrape.sources`, `scrape.fetch.custom`, `scrape.data.modify`)
- **MusicBrainz** — fallback music metadata source
- **Deezer** — cover art + track listings for audio media
- **GoodLib** — custom-domain book metadata scraper

### 🎯 Upgrade/Install robustness

- **Fixed** `public/installer/assets` symlink → real directory (manual upgrade used to crash with `copy(): The second argument cannot be a directory` on installs where the dir had been materialized)
- **Release ZIP guard** — `create-release.sh` now scans ZIP metadata via `zipinfo` and aborts if any symlink entry would ship (prevents regressions like the one above)
- **Reinstall regression test** — full end-to-end suite (`scripts/reinstall-test.sh` + `tests/manual-upgrade-real.spec.js`) that exercises the real admin UI upgrade flow (upload ZIP → click "Avvia" → `Updater::performUpdateFromFile`) instead of bypassing via rsync. Runs the full Playwright suite on both a fresh install and an upgraded install

### 🧹 CodeRabbit Major fixes (16 items)

- **`BulkEnrichController::start`** — no longer leaks raw exception messages to clients; logs via `SecureLogger` and returns a generic 500
- **`BulkEnrichController::toggle`** — `filter_var(FILTER_VALIDATE_BOOL)` so `"false"/"0"/"off"` correctly disable the feature
- **`BulkEnrichmentService::setEnabled`** — returns bool; controller propagates DB failures instead of swallowing them
- **`BulkEnrichmentService::enrichBook`** — checks the `UPDATE` execute() result before marking the book as enriched (prevents false-positive success logs on DB failure)
- **`ScrapeController::normalizeIsbnFields`** — distinguishes validated ISBN requests (via `IsbnFormatter::isValid`) from plugin-accepted barcode requests, so legitimate book lookups no longer skip ISBN backfill when the scraper omits `format`/`tipo_media`
- **Accessible switch** — `aria-label` + `aria-labelledby` on `#toggle-enrichment`
- Full list in `updater.md` Version History.

### 🌐 i18n

- **168 new translations** added to `en_US.json` + `de_DE.json` — all strings introduced in this branch are now fully localised. `it_IT.json` stays minimal (fallback-to-key)

### Migrations

No new migrations. All DB changes ship in existing `migrate_0.5.4.sql`. Running v0.5.5 on a v0.5.4 install is a code-only upgrade.

---

## Previous Releases

<details>
<summary><strong>v0.5.4</strong> - Discogs Plugin + Media Type + Plugin Manager Hardening</summary>

### 🎵 Discogs music scraper plugin (#87)

- **New `tipo_media` ENUM** (`libro/disco/audiolibro/dvd/altro`) on `libri` with composite index `(deleted_at, tipo_media)`
- **Heuristic backfill** from `formato` using anchored LIKE patterns (avoids `%cd%` matching CD-ROM, `%lp%` matching "help")
- **Discogs + MusicBrainz + CoverArtArchive + Deezer** chain with 4 hooks (incl. `scrape.isbn.validate` for UPC-12/13)
- **Barcode → ISBN guard** in `ScrapeController::normalizeIsbnFields` — skips normalization when no format/tipo_media signal to avoid the EAN-in-`isbn13` regression
- **PluginManager** migrated from `error_log` → `SecureLogger` (31 call sites)

### Post-release hotfixes (rolled into v0.5.4)

- `autoRegisterBundledPlugins` INSERT had 14 columns / 13 values after CodeRabbit round 11 — fresh installs crashed with "Column count doesn't match value count" (fixed in `c9bd82c`)
- Same method's `bind_param('ssssssssissss')` had positions 8+9 swapped — `path='discogs'` was cast to int `0`, orphan-detection then deleted the rows (fixed in `fb1e881`)

</details>

<details>
<summary><strong>v0.5.3</strong> - Cross-Version Consistency Fixes (v0.4.9.9–v0.5.2)</summary>

- **`descrizione_plain` propagated** — Catalog FULLTEXT search and admin grid now use `COALESCE(NULLIF(descrizione_plain, ''), descrizione)` for LIKE conditions, completing the HTML-free search feature from v0.4.9.9
- **ISSN in Schema.org & API** — `issn` property now emitted in Book JSON-LD and returned by the public API (`/api/books`)
- **CollaneController atomicity** — `rename()` aborts on `prepare()` failure instead of committing partial state
- **LibraryThing import aligned** — `descrizione_plain` (with `html_entity_decode` + spacing), ISSN normalization, `AuthorNormalizer` on traduttore, soft-delete guards on all UPDATE queries, and `descrizione_plain` column conditional (safe on pre-0.4.9.9 databases)
- **Secondary Author Roles** — LT import now routes translators to `traduttore` field based on `Secondary Author Roles`

---

## Previous Releases
</details>

<details>
<summary><strong>v0.5.2</strong> - Name Normalization (#93)</summary>
Expand Down
Loading
Loading