Skip to content

Gate the template contract rules and fix the side-cart settings filter argument - #66

Merged
next-devin merged 7 commits into
mainfrom
issue-51-contract-lint
Sep 21, 2026
Merged

next-devin merged 7 commits into
mainfrom
issue-51-contract-lint

Conversation

@next-devin

Copy link
Copy Markdown
Contributor

Addresses #51, items 3, 4, 6 and 7. Items 1 and 2 already landed on main (#54, mobile-nav focus trap). Item 5, a Django-based DTL parse gate, is deferred and stays open on the issue.

What changed

  • Side cart (item 4). partials/side_cart.html passed settings.gift_product as a |default: filter argument, which the platform 500s on once the setting is populated. It now binds the setting with {% with %} and picks the PK with {% firstof %}.
  • Template-contract lint (item 3). scripts/check-templates.py gains three checks over layouts/ templates/ partials/, so make verify-theme and the existing CI step pick them up with no wiring: settings.* as a filter argument, a {% firstof ... as X %} target later passed to purchase_info_for_product (firstof always yields a string), and hardcoded /products/ routes in href/action. Each has real-repo-passes, defective-fixture-fails (naming file and line), and legitimate-pattern-passes tests in tests/test_ci_gates.py. The settings-filter check was proven against the pre-fix side-cart line before the fix was applied.
  • Runtime hooks (item 3d). theme-contract.json now requires id="cart-badge", data-toggle="mobile-nav", id="mobile-nav" and <spark-cart-drawer, each with a why and a storefront verify snippet, reusing the Add a theme contract gate for derived themes #64 gate instead of a second mechanism. Deleting cart-badge previously passed every gate and failed only in the browser.
  • Docs (items 6, 7). docs/design-block-authoring.md states the rule tests/test_localization_contracts.py enforces: localized settings keep empty defaults, design copy goes on a separate key. The mobile reference width is decided as "match the Figma-defined frame width, 375 or 390, QA at both" in docs/figma-section-library-plan.md and docs/section-roster.md.

Verification

  • python3 -m unittest discover -s tests: 90 tests pass (78 on main)
  • make verify-theme: css-check, tests, theme contract gate (62 templates) all pass
  • node tests/js/*.test.js: 12 pass

Reviewer notes

  • The new contract requirements carry no since; the pixels entry says 1.3.0. Stamp the next release number if wanted.
  • Contract requirements are file-specific, like pixels. A derived theme that moves header markup to another partial fails with "does not contain".
  • The firstof check is per-file; a name carried through {% include ... with %} is not traced.

🤖 Generated with Claude Code

next-devin and others added 3 commits September 21, 2026 15:16
The platform raises a 500 on every route once gift_product is set,
because settings.* cannot be used as a filter argument. Bind the
setting with {% with %} and pick the PK with {% firstof %} instead.
The data-gift-product-id attribute and the surrounding guard are
unchanged.

Refs #51 (item 4)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
check-templates.py now fails on three patterns a derived theme has
re-introduced after they were fixed:

- [settings-filter-argument]: settings.* on the right of a filter
  (|default:settings.foo). The platform raises a 500 on every route
  once the setting has a value. Proven against the pre-fix
  partials/side_cart.html:37 before that line was rewritten.
- [firstof-object]: a {% firstof ... as X %} target later handed to
  {% purchase_info_for_product %}. firstof always stores a string, so
  X can never select a product object.
- [hardcoded-route]: a /products/ literal inside href or action.
  Routes come from {% url %} or get_absolute_url.

theme-contract.json now also requires the runtime hooks the shipped JS
resolves by id or attribute (id="cart-badge", data-toggle="mobile-nav",
id="mobile-nav", <spark-cart-drawer). Removing any of them passed every
existing gate and failed only in the browser.

Refs #51 (item 3)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
design-block-authoring.md states what test_localization_contracts.py
enforces: a setting with a {% t %} fallback keeps an empty schema
default and empty settings_data, and starter design copy belongs on a
separate non-localized key.

The Figma mobile reference width is decided: match the Figma-defined
frame width (375 or 390) and capture QA at both. The plan and the
section roster now state it as a rule instead of an open choice.

Refs #51 (items 6 and 7)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@next-devin
next-devin marked this pull request as ready for review September 21, 2026 08:30
Comment thread scripts/check-templates.py
Comment thread scripts/check-templates.py
Comment thread scripts/check-templates.py
Comment thread scripts/check-templates.py Outdated
Comment thread theme-contract.json
Comment thread theme-contract.json
@kilo-code-bot

kilo-code-bot Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Incremental review (e707d54..06c367f, 4 commits)

All four prior findings are addressed in this range:

  • f1aee22 adds STRING_LITERAL_RE + blank_string_literals so quoted string literals do not match as settings.* lookups, and rewords the hardcoded-route message to list every root.
  • 7f08660 drops the wrong categories/account roots, splits the list into PLATFORM_ROUTE_ROOTS (catalogue, cart, checkout, blog, search, support, accounts) and FOREIGN_ROUTE_REFLEXES (products, collections, account), and pins the wording in test_other_platform_route_literals_fail_too.
  • e707d54 replaces the all-pairs firstof scan with an in-order event walk that opens/closes a with/for shadow stack, bounds FIRSTOF_AS_RE so a firstof without as cannot swallow the next tag, and tightens HARDCODED_ROUTE_RE to require a local / so external URLs pass.
  • 06c367f runs with bodies through blank_string_literals before WITH_BINDING_RE matches, so a default of 'use /search as a fallback' no longer reads as an as fallback binding.

The new logic is exercised by test_settings_inside_a_quoted_filter_argument_is_text_not_a_lookup, test_dot_access_on_a_firstof_target_is_still_flagged, test_other_platform_route_literals_fail_too, test_firstof_without_as_does_not_claim_the_next_tags_binding, test_rebinding_the_name_to_an_object_shadows_the_firstof_string, test_external_urls_containing_a_route_word_pass, and test_as_inside_a_with_string_literal_is_not_a_binding. I traced each by hand against the new code paths and they all hold: the shadow-stack correctly suppresses the rebinding call (line 4) while still flagging the post-block call (line 6); blank_string_literals returns spaces of equal length so line numbers stay intact; the (?:(?!%}).)*? body bound stops firstof without as from claiming the next tag.

theme-contract.json now stamps since (1.0.0 / 1.1.0 / 1.1.1) on the new runtime hooks, and the cart-drawer needle is the closing tag </spark-cart-drawer> so a <spark-cart-drawer-other> cannot satisfy it.

No new issues found in the incremental range.

Files Reviewed (8 files)
  • scripts/check-templates.py - 0 issues
  • tests/test_ci_gates.py - 0 issues
  • tests/test_theme_contract.py - 0 issues
  • theme-contract.json - 0 issues
  • docs/design-block-authoring.md - 0 issues
  • docs/figma-section-library-plan.md - 0 issues
  • docs/section-roster.md - 0 issues
  • partials/side_cart.html - 0 issues
Previous Review Summaries (4 snapshots, latest commit e707d54)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit e707d54)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
scripts/check-templates.py 72 WITH_BINDING_RE matches as <name> outside string literals, so a default value containing the literal text as foo is captured as a shadow binding.
Files Reviewed (2 files)
  • scripts/check-templates.py - 1 issue
  • tests/test_ci_gates.py - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 7f08660)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • scripts/check-templates.py - 0 issues
  • tests/test_ci_gates.py - 0 issues

Previous review (commit f1aee22)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
scripts/check-templates.py 70 STOREFRONT_ROUTE_PREFIXES includes "categories" even though no allowlisted URL name resolves to a /categories/ route — adds a false-positive surface.
scripts/check-templates.py 280 Error message ends with a double slash: "literal /" + "/, /".join(...) + "/ path." produces ... /search// path..
Files Reviewed (8 files)
  • partials/side_cart.html - 0 issues
  • scripts/check-templates.py - 2 issues
  • tests/test_ci_gates.py - 0 issues
  • tests/test_theme_contract.py - 0 issues
  • theme-contract.json - 0 issues
  • docs/design-block-authoring.md - 0 issues
  • docs/figma-section-library-plan.md - 0 issues
  • docs/section-roster.md - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit dc9a35b)

Status: 6 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
scripts/check-templates.py 49 SETTINGS_FILTER_ARGUMENT_RE doesn't respect quoted string literals, so `{{ x
scripts/check-templates.py 62 HARDCODED_ROUTE_RE only catches /products/; /cart/, /checkout/, /blog/, /account/, /search/ literals slip through.
scripts/check-templates.py 216 inspect_firstof_object_selection is per-file; a firstof-bound name carried through {% include %} or {% with %} across files is not traced.

SUGGESTION

File Line Issue
scripts/check-templates.py 232 purchase_info_for_product request featured.children.first would be falsely flagged because the dot-access heuristic only looks at the root name.
theme-contract.json 14 New requirements omit the since field that pixels carries; stamp a release for consistency.
theme-contract.json 17 must_contain is a plain substring match, so id="cart-badge" would also match id="cart-badge-counter". Consider tightening.
Files Reviewed (8 files)
  • partials/side_cart.html - 0 issues
  • scripts/check-templates.py - 4 issues
  • tests/test_ci_gates.py - 0 issues
  • tests/test_theme_contract.py - 0 issues
  • theme-contract.json - 2 issues
  • docs/design-block-authoring.md - 0 issues
  • docs/figma-section-library-plan.md - 0 issues
  • docs/section-roster.md - 0 issues

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 0 · Output: 0 · Cached: 0

- settings-filter-argument: quoted string literals are blanked before
  matching, so "settings." inside a literal is text, not a lookup.
- hardcoded-route: covers every platform storefront route root, not only
  /products/, and the message lists them.
- firstof-object: the per-file scope is stated in the docstring and the
  message; dot-access on a firstof target stays flagged, with a test.
- theme-contract.json: each runtime hook carries the release it first
  appeared in as its since, and the drawer needle is the closing tag so
  a differently named element cannot satisfy it by prefix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread scripts/check-templates.py Outdated
Comment thread scripts/check-templates.py
next-devin and others added 2 commits September 21, 2026 15:55
/categories/ was never a storefront root: products and categories both
resolve under /catalogue/, and accounts under /accounts/. The gate now
lists the documented platform roots plus the foreign reflexes ported
templates carry (/products/, /collections/, /account/), which 404 here.
The test pins the message wording.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- hardcoded-route only matches a local absolute path, so an external URL
  that contains a route word passes.
- firstof matching stops at the tag boundary, so a firstof with no as
  clause cannot claim the next tag's assignment.
- The firstof check walks the file in order: calls before the firstof
  pass, and a with/for that rebinds the name shadows the string until its
  end tag, so the recommended remedy passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread scripts/check-templates.py
A quoted default containing "as <word>" was read as a rebinding and
could shadow a firstof string, hiding the defect the gate exists for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@next-devin
next-devin merged commit 1f43e0f into main Sep 21, 2026
2 checks passed
@next-devin
next-devin deleted the issue-51-contract-lint branch September 21, 2026 13:26
next-devin added a commit that referenced this pull request Sep 25, 2026
…oks bind Spark (#71)

* feat: scope theme-contract requirements to the fleet or to Spark's own copy

Every requirement in theme-contract.json now carries a required `scope`.
`fleet` binds every Spark-derived store theme; `spark` binds Spark's own
working copy only. check-theme-contract.py validates the field, enforces
only fleet rules by default for a live theme (--store/--theme-id) and all
rules by default for a working copy (--root), and takes --scope to
override either. The gate's output names the scope and how many
requirements ran, and a scope that selects nothing refuses rather than
passing.

`pixels` is the only fleet rule. The four runtime hooks #66 added
(cart-badge, mobile-nav-toggle, mobile-nav, cart-drawer) are scoped to
Spark: the first fleet sweep reported nine of thirteen live themes failing
mobile-nav on the file rule while four of them served #mobile-nav from
another file. Derived themes are forks; a file-location rule against the
fleet reports forks, not faults.

Tests cover both defaults, both overrides, the load errors, the empty
selection, the block-override rule at fleet scope, and the exact
invocation the next-mind fleet sweep makes, against a stub admin API that
rejects the wrong path or key.

* docs: document the two contract scopes and the invocation for each case

theme-contract.md lists all five rules with their scope, explains why the
runtime hooks bind Spark only, states the rule for promoting one to fleet,
and shows the command for a fork's working copy, a derived live theme, and
Spark's own copy on a dev store. The Makefile contract target's comment
says the same.

* chore: changelog entry and follow-up TODOs for contract scoping

Unreleased entry for the scope field. TODOs for the three findings the
pre-landing adversarial pass surfaced and this change does not take on:
HTML-comment masking in the needle search, live-check transport hardening,
and the fleet sweep passing --scope fleet explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant