CI / lint (push) Successful in 2s
CI / extension-version (push) Successful in 2s
Build images / sign-extension (push) Successful in 3s
Build images / build-ml (push) Successful in 6s
Build images / build-agent (push) Successful in 6s
Build images / build-web (push) Successful in 5s
Build images / smoke-web (push) Skipped
Build images / promote (push) Skipped
extension / lint (push) Successful in 17s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 2m7s
`PLATFORM_ARTIST_PATTERNS` (extension/lib/platforms.js) and `_PLATFORM_PATTERNS` (extension_service.py) are two hand-kept copies of one table whose only guard was the comment "keep in sync by hand; reviewers catch drift" — the same guarantee manifest.json had before #3069, where deviantart sat in the manifest for seven weeks after the product dropped it. Drift here is worse than the manifest case, because the two copies gate opposite halves of ONE interaction: the JS copy decides whether the "Add to FC" button appears, the Python copy decides whether the resulting POST is accepted. JS looser than Py shows a button that 400s; Py looser than JS silently never offers a button for a URL the backend would take. #1485 (the Patreon /c/ and /cw/ shapes) was the second of those, and its fix had to be applied to both files by hand. The two-runtimes objection to a shared SOURCE file is fair, so this tests the invariant instead of the source. `extension/test/artist-url-samples.json` is one table of 25 URL samples — match (with the expected slug) and no_match, each with a `why` — read by BOTH suites and asserted against each one's own copy of the patterns. Neither runtime imports the other; a change to one copy alone turns the other runtime's suite red. Both halves also assert their own coverage: the sample platforms must equal the platforms that actually have an artist pattern, and every platform must have samples in both directions. Without that, deleting a platform's samples would make the guard pass by testing less. Discord is deliberately in neither table — it is channel-based and has no creator page to put a button on. The no_match half asserts `_derive` RAISES rather than merely missing the platform: it tries every pattern in turn, so a nav page some other platform's pattern happened to swallow would still be accepted by the backend — the same defect wearing a different platform name. Samples live under extension/test/ because that path is excluded from both the XPI file set and the extension version derivation (packaging.sh: NOT_PACKAGED_TRACKED and NOT_VERSION_RELEVANT both carry `test/**`), so adding samples ships no bytes and forces no re-sign. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
139 lines
5.5 KiB
JSON
139 lines
5.5 KiB
JSON
{
|
|
"$comment": [
|
|
"THE SHARED ARTIFACT for the JS<->Py artist-pattern mirror (issue #3093).",
|
|
"",
|
|
"extension/lib/platforms.js PLATFORM_ARTIST_PATTERNS and",
|
|
"backend/app/services/extension_service.py _PLATFORM_PATTERNS are two hand-kept",
|
|
"copies of one table, and they gate OPPOSITE HALVES of a single interaction:",
|
|
"the JS copy decides whether the 'Add to FC' button appears, the Python copy",
|
|
"decides whether the resulting POST is accepted. Drift is therefore never",
|
|
"cosmetic -- JS looser than Py shows a button that 400s, Py looser than JS",
|
|
"silently never offers a button for a URL the backend would take. Issue #1485",
|
|
"was the second of those, and its fix had to be applied to both files by hand.",
|
|
"",
|
|
"Neither runtime imports the other. This file is the shared artifact instead:",
|
|
"both suites read it and assert it against their OWN copy of the patterns, so",
|
|
"a change to one copy alone fails the other runtime's suite.",
|
|
" - extension/test/platforms.spec.js (vitest)",
|
|
" - tests/test_extension_artist_patterns.py (pytest)",
|
|
"",
|
|
"Lives under extension/test/ because that path is excluded from BOTH the XPI",
|
|
"file set and the extension version derivation (see scripts/packaging.sh:",
|
|
"NOT_PACKAGED_TRACKED and NOT_VERSION_RELEVANT both carry 'test/**'), so",
|
|
"adding samples here never ships bytes and never forces a re-sign.",
|
|
"",
|
|
"Adding a case: put the URL in the platform's match/no_match list with a",
|
|
"'why'. Run both suites. If only one goes red, you have found drift, which is",
|
|
"the entire point of the file.",
|
|
"",
|
|
"'slug' is read by the Python side only -- its _derive returns (platform,",
|
|
"slug) where the JS isArtistPage returns a boolean. It is not optional on a",
|
|
"match entry; the backend deriving the WRONG slug from a URL both copies",
|
|
"agree on is its own defect class, and this pins it."
|
|
],
|
|
|
|
"patreon": {
|
|
"match": [
|
|
{
|
|
"url": "https://www.patreon.com/maewix",
|
|
"slug": "maewix",
|
|
"why": "bare creator root"
|
|
},
|
|
{
|
|
"url": "https://patreon.com/maewix",
|
|
"slug": "maewix",
|
|
"why": "www is optional"
|
|
},
|
|
{
|
|
"url": "http://patreon.com/maewix",
|
|
"slug": "maewix",
|
|
"why": "http as well as https"
|
|
},
|
|
{
|
|
"url": "https://www.patreon.com/c/Atole",
|
|
"slug": "Atole",
|
|
"why": "#1485: the /c/ creator shape"
|
|
},
|
|
{
|
|
"url": "https://www.patreon.com/cw/Atole",
|
|
"slug": "Atole",
|
|
"why": "#1485: /cw/ is the 'creator workspace' URL Patreon serves once you are SUBSCRIBED -- exactly when the button matters most, and exactly what the pre-#1485 pattern missed"
|
|
},
|
|
{
|
|
"url": "https://www.patreon.com/cw/Atole/posts",
|
|
"slug": "Atole",
|
|
"why": "#1485: a creator inner page still derives the creator"
|
|
},
|
|
{
|
|
"url": "https://www.patreon.com/Atole/membership",
|
|
"slug": "Atole",
|
|
"why": "#1485: inner page on the bare shape"
|
|
}
|
|
],
|
|
"no_match": [
|
|
{ "url": "https://www.patreon.com/home", "why": "nav page, not a creator" },
|
|
{ "url": "https://www.patreon.com/search", "why": "nav page" },
|
|
{ "url": "https://www.patreon.com/messages", "why": "nav page" },
|
|
{ "url": "https://www.patreon.com/notifications", "why": "nav page" },
|
|
{ "url": "https://www.patreon.com/library", "why": "nav page" },
|
|
{ "url": "https://www.patreon.com/settings", "why": "nav page" },
|
|
{ "url": "https://www.patreon.com/posts", "why": "nav page; also the post-permalink prefix" },
|
|
{ "url": "https://www.patreon.com/home/anything", "why": "a nav page's inner path is still not a creator" },
|
|
{ "url": "https://www.patreon.com/settings/profile", "why": "as above" }
|
|
]
|
|
},
|
|
|
|
"subscribestar": {
|
|
"match": [
|
|
{
|
|
"url": "https://www.subscribestar.com/foobar",
|
|
"slug": "foobar",
|
|
"why": "creator root on the .com TLD"
|
|
},
|
|
{
|
|
"url": "https://subscribestar.adult/foobar",
|
|
"slug": "foobar",
|
|
"why": "creator root on the .adult TLD"
|
|
},
|
|
{
|
|
"url": "https://subscribestar.adult/foobar/",
|
|
"slug": "foobar",
|
|
"why": "trailing slash is tolerated"
|
|
}
|
|
],
|
|
"no_match": [
|
|
{ "url": "https://subscribestar.adult/feed", "why": "nav page" },
|
|
{ "url": "https://subscribestar.adult/messages", "why": "nav page" },
|
|
{ "url": "https://subscribestar.adult/library", "why": "nav page" },
|
|
{
|
|
"url": "https://subscribestar.adult/foobar/posts",
|
|
"why": "SubscribeStar's pattern end-anchors on the creator root, unlike Patreon's -- an inner page does NOT derive. Pinned so the asymmetry between the two platforms stays deliberate rather than becoming a silently-fixed bug in one copy only."
|
|
}
|
|
]
|
|
},
|
|
|
|
"hentaifoundry": {
|
|
"match": [
|
|
{
|
|
"url": "https://www.hentai-foundry.com/user/Foo",
|
|
"slug": "Foo",
|
|
"why": "user page"
|
|
},
|
|
{
|
|
"url": "https://www.hentai-foundry.com/user/Foo/profile",
|
|
"slug": "Foo",
|
|
"why": "inner page still derives the user"
|
|
},
|
|
{
|
|
"url": "https://hentai-foundry.com/user/Foo",
|
|
"slug": "Foo",
|
|
"why": "www is optional"
|
|
}
|
|
],
|
|
"no_match": [
|
|
{ "url": "https://www.hentai-foundry.com/pictures/popular", "why": "gallery listing, not a user" },
|
|
{ "url": "https://www.hentai-foundry.com/", "why": "site root" }
|
|
]
|
|
}
|
|
}
|