The Patreon roster syncs, the favicon shows, and the logo sits behind every page #252

Merged
bvandeusen merged 4 commits from dev into main 2026-09-13 18:22:00 -04:00
Owner

Fixes for problems found while testing #251, merged at the operator's request. CI passed on the head commit 4b4e532 (run 6719: lint, unit, frontend, integration).

Patreon roster

  • Problem: the first live sweep failed with PatreonDriftError: … no campaign relationship. FC's unfiltered /api/members request returns 104 memberships. One of them, a membership that lapsed in 2017 to a creator whose campaign has been deleted, carries no campaign key, and that one row made FC reject the whole list.
  • Fix: a row with no campaign is now skipped when it's definitely not paying (a lapsed or free membership). An active or unrecognised row with no campaign still stops the sweep (240f11c).
  • Follow-up (4b4e532): MEMBERSHIP_STATUS and has_paid_access move to native_ingest_common, so the Patreon client never imports the roster. test_gated_reason enforces that separation.
  • Characterization: Scribe note #3992.

Brand mark

  • Favicon (dd766eb): favicon.svg had -- inside an XML comment. That's invalid XML, so both the favicon and the nav logo rendered blank. New test: tests/test_public_svgs.py.
  • Background logo (57c880a): the full logo.svg now sits behind every page as a faint fixed background. Done as a CSS background, so it doesn't change z-index stacking.

Deploy

No migrations and no stack changes. After deploy, press Settings → Maintenance → Subscription roster → Sync now, then reload.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9

Fixes for problems found while testing #251, merged at the operator's request. CI passed on the head commit `4b4e532` (run 6719: lint, unit, frontend, integration). ## Patreon roster - **Problem:** the first live sweep failed with `PatreonDriftError: … no campaign relationship`. FC's unfiltered /api/members request returns 104 memberships. One of them, a membership that lapsed in 2017 to a creator whose campaign has been deleted, carries no `campaign` key, and that one row made FC reject the whole list. - **Fix:** a row with no campaign is now skipped when it's definitely not paying (a lapsed or free membership). An active or unrecognised row with no campaign still stops the sweep (`240f11c`). - **Follow-up (`4b4e532`):** `MEMBERSHIP_STATUS` and `has_paid_access` move to `native_ingest_common`, so the Patreon client never imports the roster. `test_gated_reason` enforces that separation. - Characterization: Scribe note #3992. ## Brand mark - **Favicon (`dd766eb`):** `favicon.svg` had `--` inside an XML comment. That's invalid XML, so both the favicon and the nav logo rendered blank. New test: `tests/test_public_svgs.py`. - **Background logo (`57c880a`):** the full `logo.svg` now sits behind every page as a faint fixed background. Done as a CSS background, so it doesn't change z-index stacking. ## Deploy No migrations and no stack changes. After deploy, press Settings → Maintenance → Subscription roster → Sync now, then reload. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9
bvandeusen added 4 commits 2026-09-13 18:21:54 -04:00
fix: the favicon and nav brand mark rendered blank — "--" inside an XML comment
CI / lint (push) Successful in 2s
CI / extension-version (push) Successful in 2s
Build images / sign-extension (push) Successful in 4s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 32s
Build images / smoke-web (push) Skipped
Build images / promote (push) Skipped
Build images / build-agent (push) Successful in 5s
Build images / build-ml (push) Successful in 7s
Build images / build-web (push) Successful in 1m8s
CI / integration (push) Successful in 2m36s
dd766eb976
7ca6ee0's favicon.svg had a comment naming the `--fc-chrome-rgb` custom property. `--` is illegal inside an XML comment, so the file is not well-formed XML, and a browser renders an SVG-as-image only if it parses. It fails without any error, just a blank image. Because TopNav's brand glyph is the same file, the tab icon and the nav mark both went missing on the #251 deploy. logo.svg parses and was unaffected.

The comment now names the property without the hyphens, and says why it has to.

tests/test_public_svgs.py parses every SVG under frontend/public. It includes a vacuity guard (the directory really contains the two known files) and a positive control (the exact defect raises ParseError), so the check can actually fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9
feat: the full brand mark as a faint backdrop behind every page
CI / lint (push) Successful in 2s
CI / extension-version (push) Successful in 2s
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 5s
Build images / build-ml (push) Successful in 6s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 31s
Build images / build-web (push) Successful in 1m9s
Build images / smoke-web (push) Skipped
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m36s
57c880a623
The operator asked for one large version of the logo as the site background.
It is pinned to the viewport behind .fc-content at about 6% strength, so it
shows in the gutters and on bare page ground while cards and the nav cover it.
The series reader is immersive, skips the shell, and never draws over it.

It is a layered background (the page colour at 94% over logo.svg), not an
overlay element with opacity. An overlay needs the content z-indexed above it,
which turns every page into one stacking context under the nav's z-index 1000
and can trap an in-page overlay beneath the nav. A background changes nothing
about stacking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9
fix: one lapsed membership to a deleted creator no longer fails the Patreon roster
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 3s
Build images / sign-extension (push) Successful in 5s
Build images / build-agent (push) Successful in 7s
CI / backend-lint-and-test (push) Failing after 35s
CI / frontend-build (push) Successful in 32s
Build images / build-web (push) Successful in 1m20s
Build images / smoke-web (push) Skipped
Build images / build-ml (push) Successful in 2m17s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m26s
240f11c5aa
The first live sweep failed with "Patreon member resource has no campaign relationship". The operator ran FC's exact /api/members request in a browser and shared the response.
- It has 104 rows. The browser's settings page shows 43, because FC sends no membership-type filter and so also gets lapses back to 2016.
- Exactly one row has no `campaign` key at all: a former_patron whose membership ended in 2017. Its included reward has no campaign link either, so the creator's page is gone.
- Every other row, including the 4 is_active=false campaigns, has a campaign.

_membership returns None for a campaign-less row only when has_paid_access says it is definitely not paying (a known lapsed status, or a free member), and iter_memberships skips it. That changes no conclusion. A lapsed membership and an absent one both mean "not paying", and no Source can match a campaign with no id.

An active or unrecognised membership without a campaign still raises, because dropping a membership that might be paid would read downstream as a cancellation.

Paging still counts the rows the server sent, not the rows kept. A test pins that, so a skip can't re-read an offset or stop a page short.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9
fix: the membership status vocabulary moves beside Membership, so the Patreon client never imports the roster
CI / extension-version (push) Successful in 3s
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 4s
Build images / build-agent (push) Successful in 7s
CI / frontend-build (push) Successful in 26s
CI / backend-lint-and-test (push) Successful in 35s
Build images / build-web (push) Successful in 1m20s
Build images / smoke-web (push) Skipped
Build images / build-ml (push) Successful in 2m6s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m33s
4b4e532c56
240f11c made PatreonClient._membership import has_paid_access from membership_roster. test_gated_reason::test_no_fetch_path_can_read_the_roster failed on it, correctly: native_ingest_common is a fetch root, patreon_client is reachable from it, and no fetch path may be able to reach the roster. The roster is allowed to explain a skip, never to cause one.

MEMBERSHIP_STATUS and has_paid_access are pure platform knowledge with no database behind them. They move to native_ingest_common, next to the Membership type they interpret (the same move C7 made for Membership itself). membership_roster, membership_reconcile, patreon_client and the tests import them from there. There is no re-export from membership_roster.

The guard is unchanged. The lapsed-orphan skip from 240f11c stays as it was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9
bvandeusen merged commit 69d9c14e35 into main 2026-09-13 18:22:00 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledCurator#252