1. Sidecar / gallery-dl cookie quirk fixes (8243740, 2394e47). Two platforms whose source checks had been broken since FC-3 shipped:
SubscribeStar: the _personalization_id age-confirmation cookie expires annually and can't be refreshed from a logged-in browser session (SubscribeStar's frontend JS suppresses the age popup via localStorage once dismissed). gallery-dl's own login flow sets 18_plus_agreement_generic=true for this — credential_service._augment_subscribestar now mirrors that for cookies-via-extension auth.
HentaiFoundry: gallery-dl checks cookies.get("PHPSESSID", domain="www.hentai-foundry.com") with requests' EXACT domain matching, but the extension was rewriting host-only PHPSESSID to a subdomain-wide form (.hentai-foundry.com). gallery-dl's lookup missed it and fell into a 401'ing ?enterAgree=1 fallback. Backend _augment_hentaifoundry injects host-only duplicates; extension cookies.js v1.0.5 also respects c.hostOnly natively for fresh captures.
2. Platforms package refactor (abafc32, b447c42, 6d7116c). Promoted services/platforms.py (single file) → services/platforms/ package, one module per platform. Each module owns ALL its quirks: cookie augmentation, post-URL derivation, sidecar-key chain overrides. Adding a new source is now a three-step recipe: drop services/platforms/<name>.py, declare an INFO = PlatformInfo(...) with any of the four optional hooks, add the import to __init__.py. Sidecar parsing, cookie materialization, and /api/platforms pick it up automatically — no more touching three files per quirk.
ShowcaseView listens for R key anywhere on the page → store.shuffle(). MasonryGrid gains an opt-in animateFromIndex prop for stagger fade-in entry animation (12px translateY, 0.25s, 60ms stagger per item, honors prefers-reduced-motion). Initial load + shuffle animate; infinite-scroll appends are silent.
min-dim Delete button audit: root cause crypto.subtle is Secure-Context-gated (undefined on plain-HTTP origins per the homelab posture). The Tier-C token computation threw TypeError inside an unawaited click handler, modal never opened. Same shape as the v26.05.26.0 navigator.clipboard fix on ErrorDetailModal.
Full project sweep for Secure-Context-gated APIs: crypto.subtle, navigator.clipboard, serviceWorker, mediaDevices, Push, Web USB/Bluetooth/HID/Serial, cookieStore, queryLocalFonts, WebAuthn, geolocation. Two hits found:
MinDimensionCard.vue — fixed.
BulkEditorPanel.vue — fixed. Bonus: this UI also had a latent kind-vs-token mismatch that would have failed even on HTTPS (kind="images-selection" built delete-images-selection-<sha8> while backend expected delete-images-<sha8>).
DestructiveConfirmModal gains an expectedTokenOverride prop so call sites can pass the backend's canonical token directly, decoupling UI label from wire-format token. Both crypto-dependent flows now consume the backend-computed token via the dry-run/preview response.
Banked feedback_no_secure_context_apis.md with the full API table + which ones FC currently uses + how each is handled.
Migrations
None.
Test plan
CI green (Forgejo Actions)
Deploy: :latest pulled, services force-recreated
HF source check no longer 401s on /?enterAgree=1 (no browser re-export needed — the backend injects host-only PHPSESSID at cookies-file materialization)
SubscribeStar source check no longer aborts on /age_confirmation_warning (same — backend injects 18_plus_agreement_generic)
/showcase: press R to reshuffle; new items fade in with stagger
Settings → Cleanup → Min-Dim: Preview, then click Delete → modal opens (no more silent click)
Gallery → bulk-select → Delete → modal opens, token is the backend's delete-images-<sha8>, confirm dispatches the task
## Summary
Seven commits across three themes.
**1. Sidecar / gallery-dl cookie quirk fixes (`8243740`, `2394e47`).** Two platforms whose source checks had been broken since FC-3 shipped:
- **SubscribeStar:** the `_personalization_id` age-confirmation cookie expires annually and can't be refreshed from a logged-in browser session (SubscribeStar's frontend JS suppresses the age popup via localStorage once dismissed). gallery-dl's own login flow sets `18_plus_agreement_generic=true` for this — `credential_service._augment_subscribestar` now mirrors that for cookies-via-extension auth.
- **HentaiFoundry:** gallery-dl checks `cookies.get("PHPSESSID", domain="www.hentai-foundry.com")` with `requests`' EXACT domain matching, but the extension was rewriting host-only PHPSESSID to a subdomain-wide form (`.hentai-foundry.com`). gallery-dl's lookup missed it and fell into a 401'ing `?enterAgree=1` fallback. Backend `_augment_hentaifoundry` injects host-only duplicates; extension `cookies.js` v1.0.5 also respects `c.hostOnly` natively for fresh captures.
**2. Platforms package refactor (`abafc32`, `b447c42`, `6d7116c`).** Promoted `services/platforms.py` (single file) → `services/platforms/` package, one module per platform. Each module owns ALL its quirks: cookie augmentation, post-URL derivation, sidecar-key chain overrides. Adding a new source is now a three-step recipe: drop `services/platforms/<name>.py`, declare an `INFO = PlatformInfo(...)` with any of the four optional hooks, add the import to `__init__.py`. Sidecar parsing, cookie materialization, and `/api/platforms` pick it up automatically — no more touching three files per quirk.
**3. Showcase IR-parity + secure-context audit (`12be188`, `df6d89c`).**
- ShowcaseView listens for `R` key anywhere on the page → `store.shuffle()`. MasonryGrid gains an opt-in `animateFromIndex` prop for stagger fade-in entry animation (12px translateY, 0.25s, 60ms stagger per item, honors `prefers-reduced-motion`). Initial load + shuffle animate; infinite-scroll appends are silent.
- **min-dim Delete button audit:** root cause `crypto.subtle` is Secure-Context-gated (undefined on plain-HTTP origins per the homelab posture). The Tier-C token computation threw TypeError inside an unawaited click handler, modal never opened. Same shape as the v26.05.26.0 `navigator.clipboard` fix on ErrorDetailModal.
- **Full project sweep** for Secure-Context-gated APIs: `crypto.subtle`, `navigator.clipboard`, `serviceWorker`, `mediaDevices`, Push, Web USB/Bluetooth/HID/Serial, `cookieStore`, `queryLocalFonts`, WebAuthn, geolocation. Two hits found:
- `MinDimensionCard.vue` — fixed.
- `BulkEditorPanel.vue` — fixed. Bonus: this UI also had a latent kind-vs-token mismatch that would have failed even on HTTPS (kind="images-selection" built `delete-images-selection-<sha8>` while backend expected `delete-images-<sha8>`).
- `DestructiveConfirmModal` gains an `expectedTokenOverride` prop so call sites can pass the backend's canonical token directly, decoupling UI label from wire-format token. Both crypto-dependent flows now consume the backend-computed token via the dry-run/preview response.
Banked `feedback_no_secure_context_apis.md` with the full API table + which ones FC currently uses + how each is handled.
## Migrations
None.
## Test plan
- [ ] CI green (Forgejo Actions)
- [ ] Deploy: `:latest` pulled, services force-recreated
- [ ] HF source check no longer 401s on `/?enterAgree=1` (no browser re-export needed — the backend injects host-only PHPSESSID at cookies-file materialization)
- [ ] SubscribeStar source check no longer aborts on `/age_confirmation_warning` (same — backend injects `18_plus_agreement_generic`)
- [ ] /showcase: press R to reshuffle; new items fade in with stagger
- [ ] Settings → Cleanup → Min-Dim: Preview, then click Delete → modal opens (no more silent click)
- [ ] Gallery → bulk-select → Delete → modal opens, token is the backend's `delete-images-<sha8>`, confirm dispatches the task
- [ ] Reinstall extension from `ext-1.0.5` XPI; fresh HF cookie captures preserve `hostOnly` natively
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Seven commits across three themes.
1. Sidecar / gallery-dl cookie quirk fixes (
8243740,2394e47). Two platforms whose source checks had been broken since FC-3 shipped:_personalization_idage-confirmation cookie expires annually and can't be refreshed from a logged-in browser session (SubscribeStar's frontend JS suppresses the age popup via localStorage once dismissed). gallery-dl's own login flow sets18_plus_agreement_generic=truefor this —credential_service._augment_subscribestarnow mirrors that for cookies-via-extension auth.cookies.get("PHPSESSID", domain="www.hentai-foundry.com")withrequests' EXACT domain matching, but the extension was rewriting host-only PHPSESSID to a subdomain-wide form (.hentai-foundry.com). gallery-dl's lookup missed it and fell into a 401'ing?enterAgree=1fallback. Backend_augment_hentaifoundryinjects host-only duplicates; extensioncookies.jsv1.0.5 also respectsc.hostOnlynatively for fresh captures.2. Platforms package refactor (
abafc32,b447c42,6d7116c). Promotedservices/platforms.py(single file) →services/platforms/package, one module per platform. Each module owns ALL its quirks: cookie augmentation, post-URL derivation, sidecar-key chain overrides. Adding a new source is now a three-step recipe: dropservices/platforms/<name>.py, declare anINFO = PlatformInfo(...)with any of the four optional hooks, add the import to__init__.py. Sidecar parsing, cookie materialization, and/api/platformspick it up automatically — no more touching three files per quirk.3. Showcase IR-parity + secure-context audit (
12be188,df6d89c).Rkey anywhere on the page →store.shuffle(). MasonryGrid gains an opt-inanimateFromIndexprop for stagger fade-in entry animation (12px translateY, 0.25s, 60ms stagger per item, honorsprefers-reduced-motion). Initial load + shuffle animate; infinite-scroll appends are silent.crypto.subtleis Secure-Context-gated (undefined on plain-HTTP origins per the homelab posture). The Tier-C token computation threw TypeError inside an unawaited click handler, modal never opened. Same shape as the v26.05.26.0navigator.clipboardfix on ErrorDetailModal.crypto.subtle,navigator.clipboard,serviceWorker,mediaDevices, Push, Web USB/Bluetooth/HID/Serial,cookieStore,queryLocalFonts, WebAuthn, geolocation. Two hits found:MinDimensionCard.vue— fixed.BulkEditorPanel.vue— fixed. Bonus: this UI also had a latent kind-vs-token mismatch that would have failed even on HTTPS (kind="images-selection" builtdelete-images-selection-<sha8>while backend expecteddelete-images-<sha8>).DestructiveConfirmModalgains anexpectedTokenOverrideprop so call sites can pass the backend's canonical token directly, decoupling UI label from wire-format token. Both crypto-dependent flows now consume the backend-computed token via the dry-run/preview response.Banked
feedback_no_secure_context_apis.mdwith the full API table + which ones FC currently uses + how each is handled.Migrations
None.
Test plan
:latestpulled, services force-recreated/?enterAgree=1(no browser re-export needed — the backend injects host-only PHPSESSID at cookies-file materialization)/age_confirmation_warning(same — backend injects18_plus_agreement_generic)delete-images-<sha8>, confirm dispatches the taskext-1.0.5XPI; fresh HF cookie captures preservehostOnlynatively