Skip to content

Add a theme contract gate for derived themes - #64

Merged
next-devin merged 2 commits into
mainfrom
theme-contract-check
Sep 10, 2026
Merged

Add a theme contract gate for derived themes#64
next-devin merged 2 commits into
mainfrom
theme-contract-check

Conversation

@next-devin

@next-devin next-devin commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Closes #63.

A Spark-derived store theme never updates from this repo, so a fix that lands here does not reach it. The failure this gate targets is silent: the storefront renders, apps stay installed and enabled in the dashboard, and only the events go missing. Checkout keeps working, because the platform renders {% pixels %} from its own layout, which is what hides it.

#63 asked whether Spark should carry something that lets a derived theme discover it is missing a required block. This is that, built around the case the issue did not anticipate: a theme that had the block, was verified, and lost it again when an older working copy was republished over it.

What is here

theme-contract.json declares the integration points a derived theme must keep. One entry today: {% pixels %} in layouts/base.html, required since 1.3.0, with the reason the gate prints on failure.

scripts/check-theme-contract.py asserts them against either a working copy or a live theme:

python3 scripts/check-theme-contract.py --root path/to/theme
NTK_APIKEY=<key> python3 scripts/check-theme-contract.py \
    --store https://<store>.29next.store --theme-id <id>

The live mode is the one that matters. A store carries several theme copies, and republishing an old one undoes a patch applied to the active theme without touching it.

The gate reads more than the tag:

  • comments are masked, so a commented-out tag does not satisfy the contract
  • a child template that overrides the block without the tag fails, which a plain text search passes
  • a theme with no readable templates fails rather than reporting success

pixels is also added to REQUIRED_BASE_BLOCKS in check-templates.py, so dropping the block from Spark itself fails CI.

Why the other two options in #63 were not taken

An upgrade note helps only at the moment someone forks. An inventory would not have caught the case that prompted this, since that theme was already in ours and already patched. Neither survives a republish.

Verification

make contract and make verify-theme pass. 78 tests pass, 11 of them new, including the child-override case and a regression test for the contract defaulting to Spark's own copy rather than the theme being checked.

Run against real derived themes, the gate reproduced known-bad layouts and confirmed known-good ones, with the rendered storefront agreeing with the template read in every case. Anything it found is a store-side fix and does not belong in this repo.

Notes for review

  • Requirements should stay few. This is for integration points whose absence is invisible, not for style or structure.
  • The remote mode filters the template list client-side because the admin API's ?name= parameter is ignored and returns everything.
  • The gate deliberately has no fleet-sweep mode. Sweeping needs a store list and credentials, which do not belong in a public repo.
  • Verify on the published storefront, never the Theme Editor preview. The preview does not render the tracker frames, so it shows this fault whether or not the theme has it.

🤖 Generated with Claude Code

A Spark-derived store theme never updates from this repo, so a fix that
lands here does not reach it. The failure mode is silent: the storefront
renders, apps stay installed and enabled in the dashboard, and only the
events go missing. Checkout keeps working, because the platform renders
the tag from its own layout, which is what hides it.

theme-contract.json declares the platform integration points a derived
theme must keep. scripts/check-theme-contract.py asserts them against a
working copy (--root) or a live theme (--store + --theme-id, via the
store admin API). The live mode is the one that matters: a store carries
several theme copies, and republishing an old one silently undoes a patch
applied to the active theme.

The gate reads more than the tag. It masks comments, so a commented-out
tag does not satisfy the contract, and it fails a child template that
overrides the block without the tag, which a plain text search passes.

pixels is also added to REQUIRED_BASE_BLOCKS in check-templates.py, so
dropping the block from Spark itself now fails CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@next-devin
next-devin marked this pull request as ready for review September 10, 2026 03:05
This repo is public. Three things in the previous commit's prose were
written for an internal audience and should not have shipped here.

docs/theme-contract.md named a platform source file and an internal
request attribute to explain why the Theme Editor preview cannot be
trusted for this check. The behaviour is what a theme author needs: the
preview does not render the tracker frames, so it shows the fault whether
or not the theme has it.

The CHANGELOG entry and the checker's docstring carried the incident that
prompted the gate, including how long a store went untracked and how it
was eventually noticed. The rationale stands without it: a derived theme
can stop satisfying the contract without anyone editing it, when an older
working copy is republished over the active theme.

No behaviour change. Gates and 78 tests still pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@next-devin
next-devin merged commit bdf40f2 into main Sep 10, 2026
2 checks passed
@next-devin
next-devin deleted the theme-contract-check branch September 10, 2026 05:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spark 1.3.0 restored {% pixels %}, but derived themes forked before it are still dark

1 participant