Offer the userscripts as a download for mobile Violentmonkey (#26) #35

Merged
sulthan merged 4 commits from fix/mobile-userscript-download into main 2026-08-08 16:39:31 +07:00
Owner

Violentmonkey on mobile Chromium does not intercept navigation to a .user.js URL, so the Install link only renders the script as text — the owner hit this on their phone after the cutover.

?download=1 on the same session-gated install endpoint sets Content-Disposition: attachment, so the file saves and can be added from Violentmonkey's own menu. The setup panel gains two Download links and a line of copy.

The plain Install link stays inline deliberately: the updater polls the /u/ path and an attachment disposition there would break auto-update. TestInstallServesScriptWithCredential now asserts both halves, and that the downloaded copy still carries the Reader's own credential.

No CSS change — .setup-links already wraps; verified rendered at a 390px viewport.

go test ./... green.

Violentmonkey on mobile Chromium does not intercept navigation to a `.user.js` URL, so the Install link only renders the script as text — the owner hit this on their phone after the cutover. `?download=1` on the same session-gated install endpoint sets `Content-Disposition: attachment`, so the file saves and can be added from Violentmonkey's own menu. The setup panel gains two Download links and a line of copy. The plain Install link stays inline deliberately: the updater polls the `/u/` path and an attachment disposition there would break auto-update. `TestInstallServesScriptWithCredential` now asserts both halves, and that the downloaded copy still carries the Reader's own credential. No CSS change — `.setup-links` already wraps; verified rendered at a 390px viewport. `go test ./...` green.
sulthan added 4 commits 2026-08-08 16:36:56 +07:00
Dry-running CUTOVER.md against production surfaced four things that would
have failed mid-cutover:

- The volume is named after the compose project, which is the lowercased
  directory name (mangabookmark), not the repo name. Both runbooks hardcoded
  bookmarkmanager_bookmarks-data. Derive it from docker instead.
- §1 asserted a clean stop leaves no -wal. compose stop SIGKILLs after 10s,
  and a surviving -wal holds writes bookmarks.db alone does not, so the
  export would silently lose them. Check rather than assume.
- jq is not installed on the server; §5's read-path check now uses python3,
  which the runbook already requires.
- REDEPLOY §1 still listed the pre-split table set, omitting readers and
  sessions from both the \dt output and the pg_restore contents.

Also records the git-pull failure the redeploy hits on a checkout whose
remote is the HTTPS clone URL.
Review findings on the previous commit:

- README's config table listed 8 of the backend's 25 environment variables,
  omitting the whole DISCORD_* set that the backend refuses to start without.
  The canonical reference cannot be missing the vars that gate startup.
- `docker volume ls --filter name=` is a substring match. Both runbooks used
  it to find a volume they then mount read-only or delete; a second matching
  volume makes the mount fail obscurely and the delete take both. Derive the
  name exactly from the compose project instead.
- CUTOVER §6 removes the retired volume 'once the Postgres data has been
  trusted for a while', in a shell where §1's $VOL no longer exists.
- REDEPLOY and DEPLOY still pointed at /opt/bookmarkmanager, so CUTOVER §6's
  handoff to REDEPLOY §1 sent the operator to a path that does not exist.
The login gate called GET /guilds/{guild}/members/{user} — the Guild
resource's Get Guild Member, which wants a Bot token and the application
present in the guild. Handed a user Bearer token it answers 401, which
discordMember reports as an error, so every sign-in rendered "Discord
sign-in is unavailable right now" and nobody could get in.

The endpoint guilds.members.read actually grants is Get Current User Guild
Member, GET /users/@me/guilds/{guild}/member. Same single-guild question,
same privacy property, and it takes the token we hold. #18 flagged this as
verified from Discord's documentation but never from a live flow; it was
wrong.

The stub mirrored the implementation, so the suite could not see it. It now
serves the OAuth path and answers the bot path 401 the way Discord does —
without that, a regression falls through to 404 and reads as an ordinary
"not a member" refusal instead of failing.
Violentmonkey on mobile Chromium does not intercept navigation to a
.user.js URL, so the Install link renders the script as text and there is
no way to get it installed. Adding ?download=1 sets Content-Disposition:
attachment on the same session-gated endpoint, so the Reader saves the
file and adds it from Violentmonkey's own menu.

The plain link stays inline on purpose: the updater polls the /u/ path and
an attachment disposition there would break auto-update. The test asserts
both halves.

Refs #26
sulthan merged commit c2b47eb05b into main 2026-08-08 16:39:31 +07:00
Sign in to join this conversation.