CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 4s
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 7s
CI / frontend-build (push) Successful in 25s
extension / lint (push) Successful in 26s
CI / backend-lint-and-test (push) Successful in 34s
Build images / build-web (push) Successful in 1m5s
Build images / smoke-web (push) Skipped
Build images / build-ml (push) Successful in 1m54s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m19s
Milestone #406 phase 2, with issue #3980 folded in. Phase 1 (2026-09-13) unregistered pixiv so nothing could reach it; the code has sat in the tree uncalled since. DeviantArt is why the second half is not left for later — #3069 retired it in code on 2026-08-27 and its stored session was still in the database seven weeks on. Step 5 — the code. Deletes pixiv_client, pixiv_downloader, pixiv_ingester, platforms/pixiv and their three test modules and fixture, then edits out every remaining reference: the dispatch entry, the campaign-id and verify branches in download_backends, the display-name branch in extension_service, and the comments that still described pixiv as live. The consolidation check the step asked for comes back negative: native_ingest_common has seven non-pixiv callers (patreon, subscribestar, membership_reconcile, membership_roster, ingest_core), so nothing there drops to a single user. Step 6 — the data, alembic 0102. Drops pixiv_seen_media and pixiv_failed_media, and deletes credential rows whose platform is not registered. Written as "not registered" rather than "pixiv" at the step's explicit ask, which is what makes one migration cover two retirements: the pixiv OAuth refresh token and DeviantArt's leftover session (#3980). It is also the only way either row can go — the credentials UI renders one card per platform from /api/platforms and looks the credential up by key, so an unregistered platform's row has no card and no Remove button. Pixiv's Source rows are KEPT, changing the milestone's original data table on the operator's call. `platform` is stored only on Source; neither Post nor ImageRecord carries it. Both FKs are ON DELETE SET NULL, so a delete would not lose the art — but it would drop every pixiv image into the gallery's __unsourced__ bucket and strip the platform chip off every pixiv post. The rows stay disabled (0097) and unregistered, so nothing schedules or downloads through them. Keeping them costs nothing and keeps the attribution that "the art already downloaded from pixiv stays" is about. Step 7 — the guard. test_pixiv_code_and_tables_are_gone asserts absence from the module table and from Base.metadata, not from prose (snippet #3352's trap). The extension and registry negative assertions were already in place from phase 1. The final sweep found one real residue step 4 missed: extension/README.md still advertised pixiv support and carried a "Pixiv OAuth" manual-test item. Also replaces the two deleted dispatch tests with one over the whole NATIVE_INGESTER_PLATFORMS set, so adding a platform and forgetting its ingester class now fails at unit level rather than as a mid-download KeyError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
113 lines
5.4 KiB
Markdown
113 lines
5.4 KiB
Markdown
# FabledCurator Firefox Extension
|
|
|
|
Self-hosted Firefox extension that pushes session cookies from supported
|
|
platforms (Patreon, SubscribeStar, Hentai-Foundry, Discord)
|
|
into FabledCurator, and lets you add a creator as a Source from their
|
|
page in one click.
|
|
|
|
## Install (operator)
|
|
|
|
The signed XPI is bundled into the FC Docker image — `:dev` and
|
|
`:latest` each carry their own channel's build. Open FC →
|
|
Settings → Maintenance → Browser extension → click "Install Firefox
|
|
extension". Firefox shows its native install prompt. After installing,
|
|
open the extension's options page (about:addons → FabledCurator →
|
|
Preferences) and paste in the FC URL + extension API key shown on the
|
|
same card.
|
|
|
|
## Develop
|
|
|
|
```sh
|
|
cd extension/
|
|
npm install --no-save # web-ext only
|
|
npm run lint # web-ext lint
|
|
npm run test:unit # vitest — lib/ logic + packaging/version checks
|
|
npm run start # launches Firefox with extension loaded
|
|
npm run build # unsigned XPI in web-ext-artifacts/
|
|
```
|
|
|
|
## Smoke checklist (after every release that touches `extension/**`)
|
|
|
|
- [ ] `npm run lint` passes
|
|
- [ ] `npm run start` loads the extension in a clean Firefox profile
|
|
- [ ] Options page accepts FC URL + key, indicator turns green
|
|
- [ ] Cookie export: log into patreon.com, click Patreon card → "X cookies exported"
|
|
- [ ] Discord token: open discord.com, click Discord card → "Token captured"
|
|
- [ ] Add as source: visit patreon.com/<creator>, click floating button → toast
|
|
- [ ] Subscriptions list: popup → "Sources" tab → list renders
|
|
- [ ] Check now: click play icon on source row → no error toast
|
|
|
|
## Versioning — the committed number decides nothing
|
|
|
|
The shipped version is **derived**, not committed. `scripts/packaging.sh
|
|
version` returns `YYYY.M.D.HHMM` in UTC: the commit *time* of the newest change
|
|
to a packaged extension file. `build.yml` computes it and stamps it into both
|
|
`manifest.json` and `package.json` at build time. The stamp is never
|
|
committed — the commit carrying it would itself be a change to the extension,
|
|
which would move the version again.
|
|
|
|
So:
|
|
|
|
- **Editing the version does nothing.** All of it is overwritten before web-ext
|
|
ever reads it. There is no bump to make, and none to forget. There is no
|
|
hand-set part left either: MAJOR.MINOR went away with milestone 318 step 8.
|
|
- `npm run build` locally produces an XPI labelled with the *committed*
|
|
version, since nothing stamped it. Fine for loading into a test profile; not
|
|
what ships.
|
|
|
|
**Why the extension is the one artifact that does not zero-pad.** Every other
|
|
FC artifact emits rule 148's `YYYY.MM.DD.HHMM`. AMO will not take it: Mozilla's
|
|
grammar for addons.mozilla.org is
|
|
|
|
```
|
|
^(0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}$
|
|
```
|
|
|
|
— each segment is the single digit `0` or starts 1-9, so `08` and `0201` are
|
|
rejected, and at most four segments are allowed. The extension therefore emits
|
|
**the same numbers unpadded**: `2026.8.29.201` where the rest of the family
|
|
says `2026.08.29.0201`. Rule 148 already defines comparison as numeric per
|
|
segment, under which the two are equal, so nothing is reordered by the choice
|
|
and left-padding each segment recovers the family string exactly. `ci.yml`'s
|
|
`extension-version` lane checks the derived string against that regex on every
|
|
push — the cheap place to find out, because AMO 409s on re-signing and a
|
|
rejected version is burned for good.
|
|
|
|
Why commit time and not a commit count: a count is per-branch, so `dev` and
|
|
`main` count different histories of the same code and their versions end up
|
|
ordered by which branch accumulated more commits rather than by which is newer.
|
|
Commit time gives both branches the same number for the same source — which is
|
|
exactly what lets one AMO signature serve both channels (family rule 149, FC
|
|
issue #3092).
|
|
|
|
## Channels
|
|
|
|
`dev` and `main` each build and sign their own extension, and an install is
|
|
tied to whichever FC instance it points at — Firefox's static `update_url`
|
|
cannot apply here, since every FC install is a different host, so the extension
|
|
asks its configured backend. **The channel therefore IS the instance.**
|
|
Switching channel means repointing the FC URL in options and reinstalling from
|
|
that host; there is no separate channel setting, and adding one would
|
|
contradict each server build shipping its own extension.
|
|
|
|
The channel is reported *beside* the version, never inside it:
|
|
`/api/extension/manifest` answers `{"version": "...", "channel": "dev"}`. It is
|
|
optional — an instance that declares none simply omits the key, and the popup,
|
|
the toolbar tooltip and the Settings card all read exactly as they did before
|
|
the field existed. Do not be tempted to make it a `-dev` version suffix: the
|
|
comparator parses each dotted segment with `parseInt`, so a suffixed segment
|
|
reads as 0 and every dev build compares equal to every other, collapsing "no
|
|
update available" and "I cannot read this version" into one answer.
|
|
|
|
## Release
|
|
|
|
Nothing to do by hand. Push to `dev`: `build.yml` signs the extension if this
|
|
change moved the version, caches the signed XPI as a Forgejo `ext-<version>`
|
|
release, and bundles it into `fabledcurator:dev`. Merging to `main` derives the
|
|
same version, hits that cache, and bundles the byte-identical XPI into
|
|
`:latest` with no second AMO call.
|
|
|
|
AMO refuses to re-sign a version it has already issued, so signing is one-shot
|
|
per version — which is why the cache exists and why the version must never move
|
|
backwards.
|