Files
FabledCurator/extension
bvandeusen fe48e77821
CI / lint (push) Successful in 3s
Build images / sign-extension (push) Successful in 4s
CI / extension-version (push) Successful in 3s
Build images / build-agent (push) Successful in 8s
CI / frontend-build (push) Successful in 22s
extension / lint (push) Successful in 25s
CI / backend-lint-and-test (push) Successful in 29s
Build images / build-web (push) Successful in 1m57s
Build images / build-ml (push) Successful in 2m38s
CI / integration (push) Successful in 3m50s
ci(extension): retire the manual-bump guard, true up the docs (step 5)
The guard asked whether a packaged extension file changed without the
version moving. Since step 4 nobody moves the version by hand, so it was
checking a fact that had stopped existing — and it was not merely dead
weight: it would have failed the lane on every real extension change,
demanding a bump that decides nothing. Removed rather than left running
beside the new mechanism (rule 22).

What replaces it is thinner and true. The extension-version lane now
asserts the derivation resolves on this commit, that the derived value is
the plain dotted-numeric shape AMO accepts, and that MAJOR.MINOR agrees
between manifest.json and package.json. MAJOR.MINOR is the one part still
hand-set, and packaging.sh reads it from manifest.json ALONE, so a
divergence ships a version package.json disagrees with. The lane keeps
fetch-depth: 0 — checking that the derivation survives a real checkout is
half its remaining value.

Deliberately not checked there: that the derived value beats what is
already signed. That guard belongs in build.yml, where it compares against
the real ext-* releases. Comparing against origin/main in a lane would be
wrong, because dev legitimately derives a LOWER value whenever main is
ahead on the extension, and a lane that fails for being behind is a lane
people learn to ignore.

packaging.sh is down to two consumers from three. version.spec.js's
"ci.yml derives its pathspec" test would have gone red on that, so it is
rewritten to assert the property rather than the consumer: no workflow
inlines an :(exclude)extension/ literal, across all three. That keeps the
#2397 anti-regression value while surviving consumers coming and going.
A second test pins build.yml to packaging.sh version and fails if it goes
back to grepping the committed value — which is not a style regression but
the #3092 bug itself. build.yml joins extension.yml's trigger paths, since
the suite now asserts against it.

The lockstep test narrows from the whole version string to MAJOR.MINOR.
The committed patch numbers are inert now; asserting on them would fail
for a difference that changes nothing.

Docs. extension/README.md's Release section described extension.yml
signing on main and committing the XPI into frontend/public/ — untrue
since 2026-05-25, and it told the reader to hand-bump both files, which is
now exactly the wrong instruction. Rewritten, with a Versioning section
that says plainly that editing the patch number does nothing and why the
key is commit time rather than a count. ci-requirements.md drops the third
packaging.sh consumer and names every job that needs full history. Root
README no longer claims the extension is signed on main only.
2026-08-27 11:29:44 -04:00
..

FabledCurator Firefox Extension

Self-hosted Firefox extension that pushes session cookies from supported platforms (Patreon, SubscribeStar, Hentai-Foundry, Discord, Pixiv) 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

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"
  • Pixiv OAuth: click Pixiv card → login redirects, token stored
  • Add as source: visit patreon.com/, click floating button → toast
  • Subscriptions list: popup → "Sources" tab → list renders
  • Check now: click play icon on source row → no error toast

Versioning — don't hand-edit the patch number

The shipped version is derived, not committed. scripts/packaging.sh version returns MAJOR.MINOR from manifest.json plus a patch component that is the commit time of the newest change to a packaged extension file, in minutes since 2020-01-01. 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 patch number does nothing. It is overwritten before web-ext ever reads it. There is no bump to make, and none to forget.
  • MAJOR.MINOR is still yours. It carries the deliberate meaning, it is read from manifest.json alone, and CI fails the extension-version lane if the two files disagree on it.
  • 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 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).

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.