Base Theme — Decisions Log
What this is: a running log of architecture questions raised against the Base Theme Architecture Reference, and their rulings. This is cross-project — decisions here apply to any future client build, not just Bites Vitamins. Project-specific findings (e.g. "does the Bites Vitamins build follow this?") live in that project's own audit doc.
Resolved
Q: If the settings contract is required, why do 23 of Base's own 48 sections not have it? Does the reference theme fail its own standard?A: Two separate things are going on, and neither is a problem.
Most of the 23 are sections the contract does not apply to. Counted directly: cart, product, collection, article, blog, page, search, 404, password, login, register, order, account, addresses, activate-account, reset-password, header, footer, announcement-bar. These are template main sections and section-group members — a merchant cannot place them freely, and a presets entry on cart would be actively wrong, since it would let someone add a second cart to a page. The contract exists to guarantee merchant editability, so a section nobody can position is outside its scope.
The genuine gaps are a handful of content sections — hero, featured-products, featured-collections and a few others are missing padding settings they arguably should have. Those predate the rule.
They are deliberately not being retrofitted in this change. Editing them means touching theme code that every client fork inherits, for no functional gain, in a change whose purpose is tooling. The rules state forward direction; development records current practice; where they disagree the rules win and the gap is logged — which is what this entry is.
The gate is scoped so this never becomes friction. It only checks sections added in a commit, so editing any of the 23 does not trigger it. That was a deliberate design decision made after measuring the repo, not a convenient exemption: a check that fires on a third of the theme during unrelated work would be switched off within a week.
If someone wants the content-section gaps closed, that is a separate, small, purely additive change — and worth doing on its own so it can be reviewed as a theme change rather than buried in a tooling branch.
Q: When behavior is needed on several pages, does it become one shared component-* file or a section-* file per page?A (Naish, Aug 2026): Apply a test rather than a blanket preference.
Is the behavior and appearance genuinely the same thing everywhere, differing only in configuration values?
- Yes → one shared
component-*, built so consumers configure it via data attributes and CSS custom properties.- No → separate
section-*files.
Both answers already exist in the codebase and both are correct in their own context. Base's three product carousels (Featured Products, Related Products, Featured Collections v2) are separate files because each wraps Swiper with genuinely different configuration — three carousels sharing a library, not one carousel duplicated. A scroll-snap-plus-dots carousel used by several content sections is the opposite case: identical behavior and markup contract, only the cards and values differ.
How this relates to Moemen's earlier position. On Aug 3, 2026 Moemen flagged section-about-carousel.js in the Bites Vitamins build as an architecture mismatch, and the Collection Page Audit's revised leaning was option B — give each page its own carousel file. Naish's ruling is that the DRY cost of duplicating genuinely identical behavior is not worth paying. These are reconcilable rather than opposed: the compliance checklist's item A3 already carved out an exception for "a genuine atomic UI primitive", and the test above is how to tell whether you are in it. The shared-primitive answer is that exception being applied, not overridden.
Everyone agrees the original state was wrong. The disagreement was only ever about the remedy, and it turns on whether a given carousel is one thing or several — which the test answers.
Still open for Moemen: whether he accepts the test as stated, and specifically whether a configurable scroll-snap carousel qualifies as an atomic primitive in his view. Recorded here so it is visible in the PR rather than settled quietly.
Consequences already applied: checklist item A3 rewritten from a blanket preference for duplication to the test above; .claude/rules/naming-conventions.md carries the test plus guidance on building a primitive that absorbs later designs; both review agents check naming against actual scope.
Q: Should product-card JS handle swiping between photos of a single product?A (Moemen): Base's current product card doesn't have this behavior at all. If it's ever added, it belongs in the product-card JS file, not a section file. Confirmed this is specifically about single-product photo swipe — not about carousels of multiple different products (see below).
Q: Where does swiping between multiple different products (a container-level carousel) belong — card or section?A: Section-level, based on direct precedent — Base implements this pattern independently in three places (Featured Products, Related Products, Featured Collections v2). component-product-card.js never touches carousel/swipe logic in any of them; it only handles card-local behavior (swatch → image swap).
- This principle transfers to new pages that need a multi-product carousel, even where Base's own version of that page has no carousel at all (e.g. Base's Collection page is grid + pagination + infinite scroll, never a carousel — but the ownership principle still applies once a carousel is required by a design).
- Status nuance: resolved as a transferable principle from repeated Base precedent. Not yet separately re-confirmed by Moemen for the specific case of a collection page needing a carousel — treat as provisionally settled unless he raises an objection.
Q: CollectionCarousel and CollectionInfo now both live in section-collection.js — doesn't that violate the one-file-one-class convention above?A: Yes, deliberately — this is a named exception, not a new default. Base's section-*.js files (FeaturedProducts, RelatedProductsCarousel, FeaturedCollectionsV2) confirmed zero exceptions to one file/one class/one custom element per section. But none of those sections also do AJAX section-rendering of their own markup, so none of them needed to answer "how does nested JS survive an innerHTML swap?" Collection already has a working answer to that sitting in the same file: component-product-card.js's <product-card> survives every filter/sort/page fetch for free because it's a separately registered custom element — the browser auto-upgrades it when CollectionInfo.fetchSection replaces the grid's innerHTML, no glue code required. Folding the carousel into CollectionInfo as plain methods would forfeit that and require inventing a manual re-init call that has no precedent anywhere in this codebase. Keeping CollectionCarousel as its own customElements.define — just relocated into section-collection.js instead of its own file — reuses that exact mechanism instead. Scoped to this file only; not a signal to start putting multiple classes in other section files.
Q: Does Base consider the Search page the same architectural pattern as Collection?A (from the code, 15 Sep 2026): Yes. sections/search.liquid renders <collection-info data-section> with section-collection.js, calls the same three filter snippets with results: search and context: 'search', and uses the same sort/form=, data-render-section, active-filter and grid markup — down to the copied schema name. A new faceted listing page does the same. See the Header, Cart Engine & Search reference.
Q: Keep the Cursor mirror, or drop it now that the team is moving to Claude Code?A (Naish, 15 Sep 2026): Keep it, until the move has happened. Developers will move from Cursor to Claude eventually, not now. The maintenance cost is removed instead: the pre-commit hook regenerates .cursor/ and stages it, so there is no command to run and nothing to remember; .claude/hooks/ is mirrored too. The audit's recommendation to delete it stands as the end state, not the current one.
Q: Where does the record of a mistake live, and who creates it?A (15 Sep 2026): In the repo where it happened, committed, created by one command at the moment it happened — /record-incident writing docs/ai-workflow/incidents/<date>-<slug>.md with scope, should_have_caught and status. The v1 answer (a gitignored mistake log, created only on offer-and-consent, at the end of the build) produced zero entries across four client builds. Incidents do not travel with the tooling overlay; the rules they produce do, via /harvest.
Q: In what order is a store rebuilt?A (15 Sep 2026): By dependency, not by page: recon → foundations → global chrome → shared components → transactional templates → homepage and content → long tail. See .claude/workflows/rebuild-playbook.md. The v1 vision's "Collection → PDP → header/cart later" is superseded; the MiLB build showed that most of a rebuild is chrome and overlays every page depends on.
Q: How does a client fork receive Base's tooling and send learnings back, given none of the forks share history with Base?A (15 Sep 2026): Without merging. base-link.sh adds Base as a remote and a worktree (a worktree checks out any fetched ref; no common ancestor needed); pull-base-tooling.py overlays the tooling three-way against a .base-version stamp so a fork's edits are kept and listed; check-tooling-drift.py lists harvest and pull-down candidates; /harvest edits and pushes from the worktree and opens the Base PR. CI runs Base's workflow through a six-line caller. The CLAUDE.md claim "14 of 16 rules identical to BPN" (measured: 6) is replaced by the drift report.
Q: Mobile padding — is it a multiple of the desktop value, or its own setting? (Raised four times across the Pique rebuild; ruled there 2026-09-17, adopted in Base 2026-09-21.) A: Its own setting. Every new section exposes four padding settings — padding_top, padding_bottom, padding_top_mobile, padding_bottom_mobile — and the 0.75 × multiplier is retired.
The multiplier was inherited from Dawn. On the first v2 pilot four sections contradicted it before anyone ruled, and the fourth settles it. A header measured 9 on desktop against 15 on mobile, a ratio of 1.67: desktop was the tighter band because its tallest child was a 46px search field in a 64px bar while mobile fitted a 26px logo in 56px. A constant cannot express a ratio that crosses 1.0, so 0.75 × was never a number to tune — it was the wrong shape. A footer wanted 0.43 and 1.70 on the two edges of one section; seven homepage bands wanted about 0.5, and rendered 19–21px low at 393 until they were corrected by hand.
The earlier recommendation, recorded while the question was open, was to add *_mobile ids only when the measured ratio is not about 0.75. Rejected as the ruling: it leaves a per-section judgment call at exactly the moment a developer is least equipped to make it — before the section has been measured against its frames — and it is why four sections ended up with three spellings between them. Four settings always, no condition to evaluate.
What changed with the ruling: .claude/rules/sections.md and schemas.md — the contract table, the {%- style -%} boilerplate and the schema example; check-section-contract.py requires all four on a new section; new-section.py and the scaffold reference emit them; the two labels were added to locales/en.default.schema.json under sections.all.padding; report-compliance.py prints the pair as its own line rather than folding it into the headline number.
Existing sections are not retrofitted. None of Base's 48 carries the mobile pair; on the pilot, 12 of 49 did. The rest stay on the multiplier until touched for their own reasons — the same standing rule as the 750/769 breakpoint split — and fall under the retrofit ruling below.
Open
- Retrofit Base's own sections to the contract. The earlier ruling (above) left 24 of 46 sections non-compliant to keep a tooling change pure. The audit of 14 Sep 2026 argues the other way: the reference implementation is the training data — agents copy the code they read more reliably than they obey the rules that describe it (the same mechanism as the translation pattern-matching failure), and every fork copies 48% compliance on day one. Recommended: one additive theme PR — the 24 sections, the 558 bare labels, the
max-widthqueries, constructor wiring moved toconnectedCallback— reviewed as a theme change. Needs Moemen's ruling; not done in the v2 tooling branch.check-conventions.py --allgives the standing count. - Admin API writes from a session (create pages, metafield definitions, menus) would close the "inert until someone creates the page" gap the MiLB build hit four times. Needs a decision on scope and confirmation per write;
/store-reconis read-only until then. - Collection filters — single-open-desktop accordion: current
<collection-filters>custom element (Aug 5, 2026 rewrite) treats all filter groups as independent — any number open at once, both breakpoints — matching Base's owncomponent-filters-sidebar.liquidpattern exactly. The desktop filter bar in the approved Figma (emfC8d9CtGm0Ewfb8p3LgZ, node13438:4321) renders as a row of independent horizontal dropdown chips, not Base's vertical stacked accordion — which structurally implies opening one should probably close any other open one (adjacent dropdown panels would otherwise visually collide), but no frame or annotation in the file actually specifies that behavior. Confirmed by layout inference, not by an explicit spec — needs a yes from Neeraj before single-open-desktop exclusivity ships. Until then, independent-per-group is the shipped behavior (a deliberate simplification, not a placeholder bug). updateFiltersglobal scope: usesdocument.querySelectorAll('collection-info .js-filter')— could twocollection-infoinstances on one page cross-contaminate facets?- Infinite scroll vs. filter/URL pipeline: is infinite scroll intentionally disconnected from the filter/URL pipeline (separate pipeline, doesn't update the URL, resets from page 1 on filter change), or is this a gap worth fixing?
- Responsive layout switch (grid desktop / carousel mobile): no Base precedent exists either way — Base's own collection page never switches its rendering mode by breakpoint. Being worked through via the Bites Vitamins Collection Page audit; will get logged here once a pattern is settled, since it'll likely recur on future client builds.
Minor / Low-Stakes (team can decide, no need to loop in Moemen)
- Click delegation uses
matches()instead ofclosest()— icon clicks inside filter badges may not register. CollectionInfo.formgetter looks like dead code (queries a nestedcollection-infothat doesn't exist).- Internal docs (
section-collection.md, etc.) reference IDs that differ from the actual source — treat code as source of truth until docs are updated. featured-products.liquidduplicates card markup instead of using the sharedcomponent-product-cardsnippet.
Change Log
- Aug 3, 2026 — Moemen's Base Theme code review flagged
section-about-carousel.jsandcollection-filters.jsin the Bites Vitamins build as architecture mismatches vs. Base Theme standard. - Aug 4, 2026 — Base Theme Collection/PDP architecture recon completed (see Architecture Reference doc).
- Aug 4, 2026 — Moemen confirmed card-level photo-swipe behavior (not currently built, but belongs in card JS if added).
- Aug 4, 2026 — Container-level carousel ownership resolved as a transferable principle from Base precedent (see Resolved, above).
- Aug 5, 2026 — Confirmed the Bites Vitamins Collection page's shared carousel component (
section-about-carousel.js) was built by an AI model before Base Theme standards existed for this project, and its accompanying.cursor/references/shared-page-sections.mdis a post-hoc AI rationalization, not a reviewed team decision — it should not be treated as precedent. See Bites Vitamins Collection Page Audit for the resulting fix options. - Aug 5, 2026 — Collection's dedicated carousel component (
section-collection-carousel.js/<collection-carousel>) folded back intosection-collection.js(see Resolved, above) —CollectionCarouselstays a separate registered custom element for AJAX-survival reasons, but the file is now consolidated. Filter drawer Alpine block simplified to a singledrawerOpenboolean; body scroll lock moved from JS to a CSS:has()rule matching Base'scomponent-filters-drawer.liquid; accordion/outside-click/Escape/ focus-restoration logic moved into a new, real, registered<collection-filters>custom element (component-collection-filters.js) with a reactivematchMediachangelistener, fixing the live-resize bug. Single-open-desktop exclusivity intentionally not implemented pending confirmation (see Open, above). - Aug 26, 2026 — Shared-vs-section-owned resolved as a decision test rather than a blanket preference (see Resolved, above). Compliance checklist item A3 rewritten accordingly; new checklist Section E added covering schema and merchant controls, which had no coverage before and is part of why the Bites Vitamins audits missed the settings contract.
- Aug 26, 2026 — These standards docs migrated from the Bites Vitamins repo into Base, where forks can inherit them. Project-specific audits stay in the client repo.
- Aug 26, 2026 — Recorded why Base's own sections do not all carry the settings contract, and why they are not being retrofitted in the tooling change (see Resolved, above).
- Sep 14, 2026 — Audit of the v1 workflow against its vision: five of eight pipeline steps real; inheritance a policy with no mechanism (no fork could pull Base down; Base's hook never ran in a fork); ~5% of the rules mechanically enforced; verification by eye; the mistake log never created; Base itself at 48% contract compliance. The retrofit ruling reopened (see Open).
- Sep 15, 2026 — Workflow v2 landed in six phases: runtime hooks that gate at edit and turn end; three-way tooling distribution with drift reporting and a reusable CI workflow;
verify-against-figmaas a measured report;/record-incidentand/harvest;/recon-theme,/store-reconand the dependency-ordered rebuild playbook; the Header, Cart Engine & Search reference. Cursor mirror kept and made maintenance-free. Rulings above dated Sep 15. - Sep 21, 2026 — First v2 pilot (Pique) reviewed before the branch goes to
development. Mobile padding ruled: four padding settings per section, the0.75 ×multiplier retired, existing sections not retrofitted (see Resolved). The fork CI caller now fires on every pull request — it had filtered ondevelopment/mainand never ran once on the pilot's nineteen PRs. Tooling fixes the pilot made locally harvested: CSS comments ignored by the conventions check,file:line-linecitations,--evalfor verifying overlay states, a hot-reload-free verify server, the orphaned Cursor prompt paths.developmenthad gone red on its own AI-config check after a direct push ofaccessibility.md; inventory and caps corrected here.