Skip to content

Invites and pairing for new users: store links, a phone-friendly invite page, scan-with-the-app wording; LibriVox add keeps an admin's match - #384

Merged
KodeStar merged 20 commits into
mainfrom
feat/new-user-b
Nov 14, 2026
Merged

KodeStar merged 20 commits into
mainfrom
feat/new-user-b

Conversation

@KodeStar

@KodeStar KodeStar commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

Closes #367, #379, #380. What they leave open moved to #385 (LibriVox identity edges) and #386 (invites and pairing polish).

Invites and pairing (#367)

A new user (Synology, Android, coming from Audiobookshelf) scanned the pairing QR with the phone's camera, landed in the web player, and found the app still signed out. Our copy said the camera opens the app. It doesn't: the app claims no domains (and can't on self-hosted addresses), so only the app's own Scan a QR code pairs it.

  • Copy fixed in every console locale and the connect page: "Open the AudioSilo app and tap Scan a QR code. The phone's camera opens the web player instead." The invite QR encodes /connect#code=, so for invites the copy says the camera opens the invite page.
  • Store links (App Store, Google Play, APK from the latest release) on / and /connect, before and after a redeem. links gains ios, android and a new apk (additive).
  • The invite page on a phone leads with "Get the app, then come back to this link", the store links and the web player, and hides the self-scan QR. The Admin button shows only for an admin's code: the redeem payload gains admin: true (from the code's own user, absent for a listener).
  • Invite dialog: 7 days by default (the API's default is unchanged), a Share button (share sheet, copy fallback on plain http), an optional password for a new person (the existing createUser field), and warnings for a localhost link, a home-network-only link and https at an IP address.

LibriVox add (#379, #380)

  • The fold reads the Bridge's new adopted result field (KodeStar/audiosilo-bridge, fix/librivox-fetch-followups). If the folder was already there, an existing match (an admin's rematch) is kept. If it is newly placed, the match is replaced and a deleted book's leftover ASIN/ISBN overrides and enrichment at that path are dropped. If the field is absent (an older Bridge), behaviour is unchanged.
  • The finished event carries librivox_match (matched, kept or unmatched). The notification claims the recording only when matched, and an unmatched fetch goes out as a warning.
  • Fetch params carry title, so a failed or parked fetch is named by its book. Work and recording ids are checked against the Bridge's slug pattern. A finished job no longer shows its last step. The not_found toast is in the console's words. es/fr/it/pt use the feminine for the Bridge.
  • Not in this PR: rebuilding a book's identity from .audiosilo/librivox.json, which needs scanner work (follow-up).

Review and verification

  • Review: /simplify, then two /code-review high --fix passes on the whole branch. One data-loss finding across the seam (a resumed placement reported adopted:false) was fixed on the Bridge side. Gate green, including the admin console build and tests, go test -race, golangci-lint and the native-engine run.
  • Verified live against a scratch server with the real Bridge on its branch:
    • the invite dialog: 7 days, the password, Share copying on http, all three warnings;
    • the setup and Pair a device copy;
    • store links on / and /connect;
    • the phone invite page under iPhone 15 and Pixel 7 device profiles, with the Admin button only for an admin;
    • a LibriVox add reporting matched, then Add again keeping an ASIN and an admin rematch set in between ("kept").
  • Not verified live: the unmatched notification (unit-tested). A real NAS run from Setup deep links open their page; Docker permission failures name the folder, the user and the fix #381 is still owed.

Pairs with KodeStar/audiosilo-frontend (feat/new-user-b), KodeStar/audiosilo-bridge (fix/librivox-fetch-followups) and KodeStar/audiosilo-docs (docs/new-user-b, to follow).

…ale ASIN

A done librivox.fetch now reads the Bridge's additive `adopted` result
field. An adopted folder keeps the match recorded there (an admin's
rematch) and is recorded only when it has none; a newly placed folder
drops a deleted book's leftover ASIN/ISBN overrides and enrichment,
which /meta asked before the match. A result without the field keeps
the L6 rule.

The finished notification now says how the book is matched after the
fold (librivox_match: matched, kept, unmatched) and claims the
recording only when it is matched to it. The job's params carry the
work's title (additive, the Bridge ignores it), so a failed or parked
fetch names the book instead of the library, and the work and recording
ids are checked against the Bridge's slug shape at queue time.

Part of #379. Part of #380.
The bell's "Added from LibriVox" now says when the server couldn't
match the book to its recording (with how to fix it) or found it in the
library with another match. Settings > Bridge > Jobs names a fetch by
its work's title when the server sent one, and a finished job no longer
shows its last step. With several libraries, an open fetch says which
library it is going into. A project the metadata no longer lists is
toasted in the console's words, not the server's English sentence, and
the LibriVox strings in es, fr, it and pt call the Bridge feminine like
the rest of the console.

Part of #379. Part of #380.
…ole button

A new user scanned an invite with the phone's camera and landed on the
web player, not the app, and nothing on the page said where to get the
app. The connect page now shows the App Store, Google Play and APK links
before and after a redeem, and on a phone or tablet it leads with "Get
the app, then come back to this link" and the web player instead of a
QR code the phone holding it can't scan. The QR hint says to use the
app's Scan a QR code, since the camera opens the web player.

The Admin section button shows only for an admin's invite: the redeem
payload gains an additive `admin` flag. The payload's `links` now carry
the store links (`ios`, `android`, and an additive `apk`), which were
never set.

Part of #367.
…warnings

Adding someone was the roughest part of a new user's first day: the
invite lapsed after a day, there was no way to send it from the dialog,
a password could only be set afterwards, and nothing said the link would
only open at home or behind a certificate warning.

The invite now lasts 7 days unless changed. The invite card has a Share
button (the device's share sheet, else the message and link copied) and
says when the link is localhost, a home-network address, or https at an
IP address (a self-signed certificate warning). A new person can be
given a web player password at once (the existing createUser field; how
invites redeem is unchanged). The invite and setup copy now says to scan
with the app's Scan a QR code, since a phone's camera opens the web
player or the invite page instead, in every console locale.

Part of #367.
Covers shareOrCopy on its own: a dismissed share sheet is no failure, and no share sheet or a refused share copies the message and link.

Part of #367.
i18next never falls back to _other within a language, so these locales need _many too (the i18n test checks every plural form).

Part of #367.
golangci-lint's rowserrcheck. Part of #379.
A deleted book's book_enrichment row alone also reaches books.asin, which /meta asks before the match, so the fold's drop of it is checked on its own.

Part of #379.
matched / kept / unmatched is the result of FoldLibrivoxFetch's rule, but
the api layer worked it out itself from a second reading of the adopted
flag, with the outcome names owned by notify. catalog.LibrivoxFetchMatch
now sits beside the fold and owns the names; notifyBridgeJob only copies
the value, and the carrier struct it needed goes.

Part of #379.
…'s helpers

LibrivoxFetch spelled out bridge.CleanText's body, and the fold test
hand-rolled the overrides read and the enrichment write that
queryOverrides and setEnrichment already do.

Part of #379 and #380.
Both dialogs mirrored the server's minimum length in a constant of their
own, so a change to the rule could leave them disagreeing.

Part of #367.
The Jobs list and the LibriVox page each guarded progressOf with the
job's state, so the next surface to read a job's progress would show a
finished job's last step again. openProgress owns the rule.

Part of #380.
The navigator.share stand-in was spelled four ways across two test files
and the three invite tests each repeated the created account and the
steps to open the dialog. The connect page's userAgentData test is the
one comparison it always was.

Part of #367.
LibrivoxFetchMatch read the match at whatever library and path the result
named. A result naming another library or project than the job's is one
FoldLibrivoxFetch refuses, yet a book matched as the same recording at
that path made the notification say it was matched to its recording.

CONVENTIONS.md also says what an adopted fold leaves alone: any match
already at the path, so adding a book again records its recording only
while the path holds none.

Part of #379.
LibrivoxFetchOf's error is also a done job's unreadable result, with the
params read fine. The title was only taken when there was no error, so
a done fetch with a result of the wrong shape, which names no book, was
announced by the library's name instead of the work it was adding.

Part of #380.
The redeem payload's admin is set for any code /auth/redeem resolves to
an admin's account, a recovery code included; the comment said invites
only, which is what the player's mirror and the API reference would
have copied.

Part of #367.
A fetch whose book couldn't be matched to its recording went out to
ntfy, Discord and webhooks with the success tick and the info colour,
while the console's bell shows the same event as a warning. An admin
skimming notifications read it as a clean add.

Part of #379.
The work's title travelled in a variable to a fallback after the book
lookup. It is set up front now and the indexed book's title replaces it
when there is one, so a book indexed with no title keeps the name its
job already had (an Audible backup's too).

Part of #380.
LibrivoxFetchMatch checked one of the fold's refusals (another library
or project) and then read whatever match the path held. A result naming
a path with no indexed book, where a deleted book's match as the same
recording remains, was reported as matched although the fold recorded
nothing. It now asks for the indexed book in a books library with its
match, and shares the result check with the fold.

Part of #379.
…ocalhost links

Audiobookshelf writes metadata.json beside a book only with "Store metadata with item" on (off by default), so the Audiobookshelf step now says AudioSilo reads them if they are there. The phone step's summary names the app's Scan a QR code, as the step's body does. An invite link uses the public address, else the request's own address, never the home one, so its localhost advice now names only what fixes it: a public address, or opening the console at the server's network address. All six locales.
@KodeStar
KodeStar merged commit 3b03e1c into main Nov 14, 2026
4 checks passed
@KodeStar
KodeStar deleted the feat/new-user-b branch November 14, 2026 10:21
KodeStar added a commit to KodeStar/audiosilo-docs that referenced this pull request Nov 14, 2026
…vites on a phone, and LibriVox fetch changes (#306)

Closes #278, #304. Docs for KodeStar/audiosilo-server#384,
KodeStar/audiosilo-frontend#297 and KodeStar/audiosilo-bridge#70.

## User Guide
- **New page "Move over from Audiobookshelf"**
(`getting-started/coming-from-audiobookshelf.md`), linked from first-run
step 5, the sidebar and Features. It covers:
- Audiobookshelf's `metadata.json`, which is off by default ("Store
metadata with item"; Audiobookshelf writes a book's file only when it
saves a change to it);
  - the import, Match automatically, and what unmatched means;
- adding a missing book, layout differences, and running both side by
side.
- **Pairing:**
- `connecting.md` leads with: install the app, tap Scan a QR code, and
where the QR is for an admin and for someone invited.
- The camera claim is fixed in `first-run.md`, `connecting.md` and
`account.md`: the phone's camera opens the web player, or for an invite
the invite page.
  - New troubleshooting entry: "Scanning the QR code opened a web page".
- **Invites** (`admin/users-and-invites.md`): the 7-day default, Share,
the optional password, the three notices, the phone invite page and the
store links.
- **Matching** (`health.md`, `books.md`): the community database has
gaps; link to "add a missing book".
- **LibriVox:**
  - a fetch is followed after you leave the page;
  - the Bridge-away notice and the bad-link page;
  - the matched, kept and unmatched notifications;
- sections are kept through an archive.org outage, and abandoned
downloads are cleared after 7 days.
- **Proxy headers** (`remote-access.md`): a set goes to the one https
address it was entered for. A plain-HTTP home address never gets them.

## Developer Docs
- `bridge/librivox.md`: `adopted` (false only when this run placed the
folder), the outage rule, meta refusals failing fast, a parked fetch
freeing the lane slot, the sweep, the server id in the state, and the
fold by `adopted`.
- `api/reference.md`: the redeem payload's `admin`; `links`
`ios`/`android`/`apk`; librivox.fetch `title` and `adopted`;
`librivox_match`.
- Also updated: the contract mirror and the data model, auth, web UI and
frontend pages.

## Screenshots
- Regenerated against the PR branches:
  - `admin/invite.png`
  - `setup/abs.png`
  - `setup/phone.png`
  - `server/connect-page.png`
- the new `server/phone-connect-invite.png`, taken in an iPhone context
  - `web-player/connect*.png`
  - `web-player/find-librivox.png`, now taken in capture-web (#304)
- The capture-web path ran for real through the shared helpers.
- The native app's connect screen is not in the pipeline (web only).

## Review
/simplify plus one `/code-review high --fix`; the console strings match
server commit 0f7420f. Gate: `npm run build` green (links and anchors
checked); old anchors kept.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant