feat(libraries): scope a library scan to one registered folder

POST /api/libraries/:id/scan accepts an optional {"folder_path": "..."}:
the scan then covers exactly that one registered folder's subtree instead
of the whole library. The path is validated against the library's
registered folders — anything else is a 400, never trusted. An omitted
body keeps today's full-library behavior, and coalescing (409 with the
active job) is unchanged.

The LibraryPanel folder rows get a per-folder Scan button: pure htmx
attributes posting to the same scan endpoint, with hxValsFolderScan
emitting plain eval-free JSON for hx-vals alongside the existing
hxValsFolderDelete precedent. Progress surfaces through the existing
active-scan polling; no new client code.

Safety: the scanner's archive-reconciliation guard (previous commit)
skips partially-covered libraries, so a one-folder scan cannot mark the
library's other folders' items missing.
This commit is contained in:
John O'Keefe committed 2026-10-27 13:22:41 -04:00
1 parent 6f37be59fc
commit 0f1d0abbb0
6 files changed
+286 -197

No files matched your search

+12
View File
@@ -383,6 +383,18 @@ func hxValsFolderDelete(folderPath string) string {
return "js:" + string(b)
}
// hxValsFolderScan builds the hx-vals for the LibraryPanel per-folder
// Scan button: it scopes the library scan endpoint to ONE registered
// folder (ScanLibraryRequest.FolderPath). Plain JSON, no js: prefix —
// the eval-free form under the hash CSP.
func hxValsFolderScan(folderPath string) string {
b, err := json.Marshal(map[string]string{"folder_path": folderPath})
if err != nil {
return "{}"
}
return string(b)
}
// fmtSecondsHMS renders seconds as H:MM:SS (or M:SS under an hour) —
// the audio progress display shape shared with the web player.
func fmtSecondsHMS(seconds float64) string {