Repository navigation
Matching: Match automatically where people look, Add it for books not found, ASINs the database doesn't know; invite and Bridge job polish - #390
Merged
Conversation
A match run's Not found rows offered only Match by hand and Open book, and the match dialog's empty state only "try other words": nothing said the book still plays, or how to get it into the community database. Both now say "This book is not in the community database yet. It still plays as normal.", with an Add it link to meta.audiosilo.app's Add a book page and a pointer to the Help complete the community database setting. The page takes one query parameter, q (audiosilo-meta site/src/lib/search-cta.ts): an Audible ASIN opens that record, other text is searched. The link prefills an ASIN typed into the dialog, else the book's own, else the words searched, else title and author. Part of #370.
A book with an ASIN of its own (a metadata.json, an admin's edit, the Bridge's backfill) counts as matched, so Match automatically skips it, yet /meta has nothing for it when the community database lacks that ASIN. The only trace was the opt-in unknown-ASIN report. Health gains an "ASIN not in the community database" category (unknown_asin): a book whose own ASIN the report's last lookup found unknown (unknown_asin_reports), with no ISBN, no recorded community match and no community-given ASIN, so a hand match settles it. Its rows offer Review match and Add it (prefilled with the ASIN). The report is the only source, so /admin/issues lists the kind only while the report runs; with it off the card is absent rather than an all-clear 0. No migration and no player wire change. Health's summary sits in the list cache, so unknown_asin_reports is no longer free user state: a row added or removed, or its answer changed, moves the content generation; renewing checked_at or sent_at alone still does not. matchedExpr is unchanged (facets, coverage and match runs still read an ASIN as matched). Part of #370.
Match automatically was reachable only from Health > Not matched, and a new user moving from Audiobookshelf couldn't find it. The Overview and Library > Books (while browsing) now show "N books not matched" with a Match automatically link that opens Health > Not matched, where the card says what a run sends before it starts. The count is the Health summary's own (ignored books left out), and the prompt is hidden at zero, which includes community metadata being off. Part of #370.
The invite dialog warned "Their browser will warn about the certificate" only for https at an IP address, so a self-signed server reached by a name such as nas.local gave no warning. The warning now follows the TLS mode the server runs: any https link while it is self-signed, none under autocert or with TLS off (a proxy's certificate). While the mode is unknown (settings loading, or a TLS mode change waiting for a restart) https at an IP address still stands in for it. Part of #386.
On a phone the invite page said "Install AudioSilo, then open this invite link again and tap Open in app", which reads oddly to someone who typed the code in rather than followed a link. A typed code now reads "Get the app, then come back to this page" / "Install AudioSilo, then come back to this page and tap Open in app", and the reopen hint asks them to enter the code on this page again; a link keeps the link wording. Every language block has the three keys. A new test holds the connect page to what exists: each key its script or markup asks for is in the dictionary, and each element id the script looks up is in the page. Part of #386.
Pressing Share again while the system share sheet was still open made the browser refuse the second share (InvalidStateError), and the console fell back to copying the message underneath the open sheet. shareOrCopy now ignores a press while its share is in flight; once the sheet closes the next press opens it again. Part of #386.
Batch B stopped showing a finished job's last step in Settings > Bridge > Jobs, which was right for a done job but dropped the one clue to where a failed or cancelled job stopped. Those now show "Last step: <step>" with its count (the server keeps the progress on both); a done job still shows none, and open jobs are unchanged. Part of #386.
The LibriVox fetch checked ids with its own metaID pattern while contribute and upcoming use audiosilo-meta's model.ValidSlug. One rule now: a librivox.fetch's work_id and recording_id must be slugs (lower-case kebab, at most 100 bytes), which every community id is. Stricter than before, and checked only when a job is queued, so a job already queued is never refused later. Part of #386.
FoldLibrivoxFetch and LibrivoxFetchMatch unpacked the result's Adopted *bool by hand. LibrivoxFetchResult.Folder now reads it as one of three named states (FolderUnsaid for an older Bridge, FolderPlaced, FolderAdopted), and both callers compare against those. No change in behaviour; the fold's existing tests cover each state. Part of #386.
The not-matched prompt's test navigated with an author filter object the router's types reject, which only the typecheck caught. It searches instead, as the page's other tests do. Part of #370.
Audible's and LibriVox's job wording each read a failure code their own way (the result's code, else a code the error starts with). bridge-model's failureCode is now the one rule both use. useTrackedJob, and the jobPollMs and jobGone it relies on, move from the LibriVox feature into the Bridge feature (use-bridge.ts, bridge-model.ts): following any job by id is the Bridge's, and the bridge feature no longer needs anything from librivox to do it. No change in behaviour; their tests move with them. Part of #386.
A blank line left where the sharing flag moved up. Part of #386.
The invite card worked out whether the server runs self-signed from the saved settings and restart_pending inline, so the one branch where the address heuristic comes back (a TLS mode change waiting for a restart) had no test. runsSelfSigned holds the rule beside inviteReach, with that case covered. Part of #386.
addBookUrl checked a typed ASIN that parseMatchQuery had already checked, as a second field beside the book's own; the match dialog now passes the one it prefers. NotInDatabase's books flag was always the absence of href, so the wording follows href and a list with a link (or one book without) can't be asked for. META_SITE is read only in its own file and is no longer exported. Part of #370.
TestIssueUnknownASIN read only the first page of the list by hand where listAll follows the cursor, and TestConnectPageNamesWhatExists carried its own file reader and a second parser of the dictionary's language blocks. dictKeys is now the one reader of a block's keys, for both i18n tests. Part of #370 and #386.
addBookUrl runs while the match dialog renders, and encodeURIComponent throws a URIError on a lone surrogate (a pasted search cut inside an emoji), which took the dialog down. Each part of the words is now cleaned of such a half before it is trimmed, so a query that was only the half falls through to the title. Tests cover the characters that could split the one q parameter (&, #, +, %, =), non-ASCII words, a line break and whitespace-only input. Part of #370.
The link in a sentence repeated ExternalTextLink's classes, icon, target and rel. It now renders that component at the sentence's own font size, so the new-tab link style has one home; only the row's button is drawn here. Part of #370.
The store's rule for unknown_asin_reports was tested with plain UPDATEs only. The report writes through an upsert on a WITHOUT ROWID table, and Health's unknown_asin count is cached under the generation: if the pre-update hook saw the upsert's DO UPDATE differently on either engine, the count would go stale or every daily recheck would empty the content caches. The test drives SaveASINChecks, MarkASINsSent and DropUnknownASINs: the generation moves on a first lookup, a changed answer and a forgotten row, and stays for the rest. Part of #370.
The new kind had a denied test for its list but not for ignoring one. A member's and an anonymous caller's ignore are now refused (403, 401) and the book stays counted. Part of #370.
The per-cache audit of the content generation still said /admin/books reads no user state and Health reads only progress, and handleIssues' comment called progress the one user state it reads. Both cached answers now read which ASINs the report found unknown, so an audit of a cache against that list would have missed the table. The Health issues entry gains the unknown_asin kind. Part of #370.
Keying the warning on the self-signed TLS mode alone dropped it for a server with TLS off behind a proxy that has its own self-signed certificate, reached at https://192.168.1.20: the mode is known and not self-signed, so the person invited met the browser's warning unannounced. The card now warns when the running mode is self-signed (a name like nas.local too) or the link is https at an IP address, whatever the mode; a name on a server that isn't self-signed stays quiet. Part of #386.
The certificate warning now shows for a self-signed server at a name too, but its text still said the link was https at an IP address. inviteReach now gives the reason (selfsigned or address) and the card picks the sentence for it: a self-signed server says so, and https at an IP address says such an address rarely has a certificate browsers trust. All six locales. Part of #386.
In a Not found row the Add it button sat beside the book page's own new-tab link with the same icon, and below md the icons are all that shows, so the two read as one. Add it now leads with a book-plus icon. Part of #370.
This was referenced Nov 14, 2026
KodeStar
added a commit
that referenced
this pull request
Nov 14, 2026
…t in setup, the nearest Audible marketplace (#389) A server installed with Docker (or any headless start) creates `admin` with a random password and prints it once. Recreate the container and it is gone, the console had no way to choose your own, and the sign-in page pointed at a setup link the log never had. This makes first sign-in recoverable and lets the admin pick their own account. Closes #369, closes #192. ## What changes **Lost the first-run password: `reset-admin`** - New command `audiosilo [--data DIR] reset-admin [username]`. It sets a fresh random password for the only admin (or the one named) through the console's own set-password path, prints it, and revokes the first-run auth code printed beside the old password. Sessions end. A running server accepts the new password at once, and a cached session ends within a minute (restart to end it at once). - It refuses a missing or non-AudioSilo database, pending migrations, and a backup restore waiting for the next start. Exit codes: 0 ok, 1 refused or failed (if revoking the code fails, the new password is still printed first), 2 usage. - Until the first-run admin first signs in to the console with a password (or changes it), every later start logs a reminder with the exact command for where it runs: `docker exec -u <uid>:<gid> <container> audiosilo --data /data reset-admin` in our image (the server's own ids, so no root-owned files), the full `/app/audiosilo/audiosilo --data /config` path on the linuxserver image. The plaintext is never stored; the reminder is a hint only (owner decision, V2.7.0-PLAN decision 6). - The state is a `server_state` row `first_admin` written only by `ensureAdmin` when it creates the admin, so upgraded servers and `--setup` installs never show the reminder. No migration. - The auth token cache re-reads a cached row once it is a minute old, so a reset from another process reaches a running server. **Console setup flow** - New first step **Your account**: change the username and choose a password (skippable). Reverses FIRST-RUN-PLAN.md decision 1 (V2.7.0-PLAN decision 5). The password changes before the rename, so a wrong current password leaves the name alone. - **Connect a phone** moves to the end and explains sign-in: scan with the app, the log's auth code works there, other people need an invite. - The sign-in page says what a new server's username and password are and where the password is; the "open the setup link" line shows only while `/setup` is open. - **#192:** the Audible marketplace step preselects the marketplace nearest the admin (browser language region, then time zone, nearest-marketplace mapping, US fallback) while no region was ever chosen; config.yaml and `AUDIOSILO_METADATA_REGION` are never overridden. Settings > Community metadata offers the same suggestion as a row until a region is chosen. (The issue assumed the default was `us`; it is "No preference".) ## Wire (all additive) - `GET /admin/setup`: `printed_password` (bool). - `GET /admin/settings`: `metadata.region_chosen` (bool). - `PATCH /admin/users/{id}`: optional `username`; 400 `username_required`, 409 `username_taken`. `POST /admin/users` also answers `username_required`. - `POST /auth/password`: a wrong current password is still 401, now with `code: current_password_incorrect` (shared with the player; shipped builds unaffected). - `GET /setup` on a server that never enabled the wizard now 303s to `/admin` (was 404), the same answer as a closed wizard, so the console's probe logs no 404 and a signed-out caller learns less than before. - Setup step ids: `account` first, `phone` last. ## Tests and review - Go: reset-admin (one admin, named, several unnamed, unknown name, no admin, foreign file, pending migrations, pending restore, revoke failure, the code it revokes and keeps), the reminder state (upgrade, `--setup`, once-only writes, failed mark), rename authz (allowed and denied), the /setup answers, the token cache re-read. Console: the account step (order, wrong password, refused name), the locale/time zone mapping, preselect only when never chosen. - Gate green: `scripts/build-admin.sh`, `go build/vet/test -race`, golangci-lint, and the native-engine vet/test/lint. - Review: /simplify + /code-review two passes at the ★ level (high-x2, Fable 5.2), then two fix rounds at high (Opus). ## Verified live (Playwright, real builds) - Fresh headless start: banner; restart: the reminder; `reset-admin` while running: the new password signs in at once, the old code and sessions get 401; after a console sign-in, no reminder. A data dir from main (3b03e1c) upgraded: no reminder. `--setup`: no reminder. - Docker: the printed `docker exec -u 1026:100 ...` command works on a non-root volume and files stay 1026:100. - Sign-in page: hint shown, no console errors, setup line only with `/setup` open. - Setup: Your account first (wrong password message in English and German, rename + new password, skip), phone step last with its Invite link. - Marketplace: en-GB/London -> United Kingdom, de-AT/Vienna -> Germany, unmapped -> United States, env-locked and config values kept, the Settings offer row. Not verified: a real NAS (Synology/Unraid) container run. Pairs with #390 (independent) and KodeStar/audiosilo-docs#307.
KodeStar
added a commit
to KodeStar/audiosilo-docs
that referenced
this pull request
Nov 14, 2026
…ng a missing book; invite and job wording (#307) Documents v2.7.0 batch C on the server. - **First run:** where the password is in a NAS or Docker container's log, the start-up reminder, and `reset-admin` with the exact command per install type (our image, Unraid/linuxserver, Synology, Linux package, a terminal), its refusals and exit codes. The setup flow's nine steps, with Your account first and Connect a phone last, and the marketplace preselect. - **Troubleshooting:** "I lost the first-run admin password" now leads with `reset-admin` before resetting the database. - **Matching:** the Overview/Books notice, Add it to the community database (new "Adding a missing book" section in community/meta-site.md), and Health's "ASIN not in the community database" state. - **Invites and Bridge:** both certificate warnings, the typed-code invite wording, and a failed job's last step. - **Developer Docs:** the additive API fields and codes (`printed_password`, `metadata.region_chosen`, `PATCH /admin/users` `username`, `current_password_incorrect`, `username_required`, `unknown_asin`, the `/setup` 303), the first-run state, and the token cache's one-minute re-read. - **Screenshots:** a new setup/account.png; the setup flow, the login page, and the Overview and Books shots with the not-matched notice recaptured. Reviewed at high (Opus) with every command, string and field fact-checked against the server branches. `npm run build` green. Pairs with KodeStar/audiosilo-server#389 and KodeStar/audiosilo-server#390.
KodeStar
added a commit
that referenced
this pull request
Nov 14, 2026
…393) Part of #366 (moving from Audiobookshelf ends up matched). This is the identifiers half; the certificate error, the import inside setup and the match offer follow in a second PR once #389 and #390 merge. ## What An applied Audiobookshelf import now keeps ABS's ASIN and ISBN for every ABS book it can pair with one of ours, as a **match hint**: - New path-keyed table `book_hints` (migration 0074: `library_id`, `path`, `source`, `asin`, `isbn`, `import_id` with 0 = none, `set_at`). It survives a rebuild, follows a moved folder (`moveBookState`) and goes with its library. - A hint is never the book's own ASIN, never on the player wire and never on `/admin/books` (tested). Nothing shows until a match run is applied. - Match runs and the match dialog send it: a book's own ids first, then the ABS hint, then tag hints. A tag ASIN goes only when the import gave neither identifier (metaserve asks the ASIN first and stops on a hit, so a tag ASIN beside an ABS ISBN would outrank the import). A known ASIN whose length, title and series agree matches confidently and is recorded the normal way (`book_matches` how=auto); a disagreeing one goes to review. - Pairing: path tier and title tier, every ABS book item, all-access. Contested and gone items are skipped; books already matched are counted, not hinted. Undo removes that import's hints; the last applied import owns them. - Every import carries them. The review's summary gains an additive `identifiers` block (`books`, `asin`, `isbn`, `by_title`, `already_matched`), shown as a review row; the stored payload gains `identified` (omitempty, payload version unchanged). - An import that brings identifiers but no listening for the person can now be applied ("No listening to import"), **unless** it would undo an import already applied for them: then Apply is off with a warning, and the server refuses with 409 `import_not_ready` and changes nothing. "Listening" matches the console's rule exactly (sessions, days, bookmarks, progress that is new or moves position, finished or finish date). - The console's note on what matching sends now names an ABS import. - The LibriVox placement clears a deleted book's hint along with its other leftovers. ## Tests Catalog apply/undo/re-import/move/library delete/rebuild of hints, the refusal (and that the applied import's rows are intact), the listening rule table, pairing tiers and skips, plan summary counts, QueryFor order, an ABS-hinted identifier hit confident vs disagreeing, migration 0074, the API's identifier-only apply and refusal, hints absent from `/item` and `/admin/books`, admin-only import routes. Console: identifiers row, identifier-only notice, disabled Apply + warning. Gate: `scripts/build-admin.sh`, `go build/vet/test -race ./...`, `golangci-lint run` (0 issues), native-tag vet/test -race/lint. Reviews: one ★ review (two passes), one fix round. ## Verified live Scratch server from this branch against a real Audiobookshelf 2.37.2 in Docker and live meta.audiosilo.app, four tagless books with no metadata.json: 1. Import with listening: review shows "ASIN or ISBN for matching: 4 books"; after Apply, 4 `book_hints` rows, `books.asin` empty, no hint in `/item` or `/admin/books`. 2. Match automatically: 4 confident, applied as `book_matches` how=auto on the hinted recordings (two only cleared the auto bar through the identifier hit). 3. An ASIN meta doesn't know: no identifier reason, matched by title/author/length to the right recording, never the fake ASIN. 4. Undo removes the hints; a new import writes them again; rescan keeps them; a moved folder's hint follows its new path. 5. Identifier-only: a user with nothing applied gets the notice and Apply works; a user with an applied import sees Apply off and the warning, a direct POST gets 409 `import_not_ready`, their progress byte-identical and their import still applied. ## Notes - The de/es/fr/it/pt strings for the new console text are machine translated: a native read is welcome. - Follow-ups are filed separately (hint lifetime after a book is deleted, a few pairing edge cases, undo dialog and history silent on hints, index use for undo's deletes). - Docs (admin/import.md and friends) land in one audiosilo-docs PR after the second #366 PR.
KodeStar
added a commit
that referenced
this pull request
Nov 14, 2026
…etup, offer Match automatically (#395) Part 1 of #366 (Audiobookshelf import). Part 2, the identifiers kept as match hints, merged as #393. ## What - **`abs_certificate`**: when Audiobookshelf's https certificate isn't trusted (self-signed or a private CA), Connect, Status and a background fetch now fail with `abs_certificate` (still 502) instead of `abs_unreachable` / `not_abs`. The console shows it under the address field and points at ABS's plain http:// address. Nothing is ever trusted or skipped. Detection is one `errors.As` on `*tls.CertificateVerificationError`: Go's client handshake wraps every verify failure (unknown authority, hostname, invalid) in that type. https to an http-only port stays `abs_unreachable` (`http.ErrSchemeMismatch`). - **Import inside setup**: `ImportFlow` (connect, map, review, history) is extracted from Settings > Import and rendered inline in the setup flow's Audiobookshelf step (`embedded`), replacing the new-tab link. Continue and Skip stay. While a books library is scanning, the step says "Wait for the scan to finish" and Connect is off (from the libraries the flow already polls, so no wire field). - **Match offer**: #390's `UnmatchedPrompt` (Match automatically) appears once an import is applied, in the Past imports card, and in the setup step once the scan is done. It links to Health (a new tab from setup) and never starts a run by itself. - #393's identifiers row and the identifier-only apply guard are unchanged inside the extracted flow. Error-code wire change is additive; no player change. ## Tests Go: a TLS fake gives `abs_certificate` from Status and Connect. A hostname mismatch with a trusted pool gives it too, and 127.0.0.1 connects as the control. https to an http port stays `abs_unreachable`, and plain http is unchanged. API: 502 on connect, and `error_code` on a failed background fetch. Console: the certificate row, ImportFlow inline in the abs step with the scan gate, the offer after an apply and held until the imports are read, and imports-model. Gate: build-admin.sh (1628 tests), go build/vet/test -race, golangci-lint 0. ## Verified live Real Audiobookshelf 2.37.2 in Docker: - **Certificate error**: http connects. Behind a private-CA proxy and a truly self-signed proxy, Connect shows the certificate message under the address field, and curl returns 502 `abs_certificate`. Switching to http:// connects. The same results came from the Linux binary inside the `:edge` image (Alpine, Go's own verifier). - **Setup step**: on a fresh server with a slowed first scan (97 s), the gate showed and lifted 0.8 s after the scan ended. Connect, map, fetch, review (the identifiers row read "4 books") and apply all ran inline. Exactly one offer appeared, in Past imports, and it opened Health in a new tab with no match run started. Continue and Skip both moved on. - **Layout**: no sideways scroll at 390 px or on desktop. Not verified: the image's native-SQLite (CGO) build. The certificate check runs before any database work. ## Notes - de/es/fr/it/pt text for `errors.absCertificate`, `imports.failure.abs_certificate`, `setup.abs.import` and `setup.abs.waitScan` is machine-translated and wants a native read. - Follow-ups go in one issue (scan gate covers Connect only; no-books-library wording; redirect-to-https hint; offer link icon; the map table is cramped on a phone; two Back buttons while mapping). Pairs with KodeStar/audiosilo-docs (docs for #366, PR to follow).
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.
A new user moving from Audiobookshelf could not find bulk matching, hit a dead end when a book was not in the community database, and had no way to see books whose own ASIN the database does not know. This puts Match automatically where people look, gives "not found" a next step, and folds in the invite and Bridge-job polish left from batch B.
Closes #370. Part of #386 (everything except the player's
has_cover, which needs a player change and stays open there).server#370: matching surfaces
https://meta.audiosilo.app/add?q=...prefilled with, in order, an ASIN the admin typed, the book's ASIN, the search words, or title and author. (meta's add page is/add;/contributetakes no prefill.)unknown_asin_reportstable, listed while the unknown-ASIN report (Help complete the community database) is on. Such books still count as matched: making them unmatched would let Match automatically replace an ASIN the admin set (follow-up). The table's userstate rule now moves the content generation on a new or flipped row so the Health cache stays exact (measured: one move per report commit).server#386: polish
model.ValidSlug; theAdoptedtri-state has named states; one consolefailureCode;useTrackedJobmoved into the bridge feature.Wire
Admin API only, additive: issue kind
unknown_asinin/admin/issues,?issue=, and the ignore endpoint. No migration, nothing the player reads.Tests and review
&,#,+,%,=, non-ASCII, lone surrogates), certificate reasons per case, share in flight, jobs' last step.scripts/build-admin.sh,go build/vet/test -race, golangci-lint, native-engine vet/test/lint.Verified live (Playwright, real builds)
q; meta's/addpage takes the prefill.Pairs with #389 (independent) and KodeStar/audiosilo-docs#307.