The new Istrian oil harvest has landed · Free delivery over 60 €

Solina

Theme Kiwwwi 1.5.3

Forge

The base theme. Layout, header, footer, UI partials and the design tokens.

Forge is the Flatsome-equivalent for this platform: one theme that every client site inherits from and that no client site edits. It holds the layout, the header and footer, every UI and section partial, the structural tokens and all component CSS.

A .themelock file makes that guarantee enforceable rather than a matter of discipline. October refuses to activate a locked theme and hides it from the theme picker, but still offers it as a parent — so it can only ever be inherited from.

Three core mechanisms do the inheriting, and none of them is registered by any code: templates resolve child-first through a stacked datasource, assets fall back to the parent copy, and the theme options form is inherited while each child stores its own values.

What is inside

Stylesheet layers

Loaded in this order by layouts/default.htm. The split is what stops a client re-skin from breaking the spacing scale.

  • tokens.css — type scale, spacing rhythm, container, radii, motion curves. Parent only, never overridden.
  • theme.css — colour, type family, elevation. The one file a child replaces, wholesale.
  • forge.css — reset, structure, components, responsive, motion. Parent only.

Pages

  • Storefront — home, shop archive, product, producers, brand pages, about, contact
  • Commerce — cart, checkout, order confirmation
  • Account — sign in, register, forgot, reset, details, order history
  • Support and legal — delivery, returns, FAQ, terms, privacy, cookies, subscribe, 404
  • Platform — this section

The signature

One memorable element, used everywhere and nowhere else.

  • The label grammar — origin · producer · year — set in the display face with hairline separators and the place name in the accent. It carries the hero, every product card, the story band, and the header of every page in this section.

Theme options

config/fields.yaml. Defined once here; every child inherits the form and stores its own values.

  • Brand — store name, tagline, announcement bar
  • Brand — show the Plugins section (off by default)
  • Contact — email, phone, address, opening hours

See it running

Every link below is this site, not a screenshot.

  • Homepage The hero, the one orchestrated entrance in the whole theme
  • Shop archive The sidebar-and-grid layout and the mobile filter drawer
  • About The story band and the prose measure
  • Not found The one page a locked parent still has to get right

Changelog

[Unreleased]

Added

  • Header menu with dropdowns — theme option Menu (Settings › Theme options): links, each with optional sub-links that open under it on hover or keyboard focus, and stack under the link in the mobile menu. Empty keeps the three default links.
  • "Will it fit me?" on the product page (partials/product/size-guide.htm): a button with a hanger icon that opens the category's size guide in a dialog. Nothing shows without a guide.
  • The 30-day lowest price under a sale price (partials/product/lowest-price.htm), as the EU requires when a reduction is announced — "the same as the current price" when it is.
  • The size picker marks its availability line data-state="in" / "out"; "In stock" takes the theme's --ok colour when the child defines one, the ink otherwise.

Fixed

  • The size picker wrote English on the button ("Add to basket", "Out of stock", "10 in stock") whatever the site's language: those words were in forge.js. The picker now passes its translated words to the script (data-vpick-words).
  • <html lang> held the page's whole definition as JSON instead of a language code, on every page of every site — screen readers and search engines got no language. It now reads the site's locale (Settings › Sites), then the theme option.
  • "Includes VAT (22 %)" was hard-coded English in the checkout, the confirmation and the account; it is a |_ message now (Includes VAT (:rate)). Country names in the checkout too.
  • Strings built by joining English words — the basket icon's label, "Remove …", the product counts, the filter button — are |_ messages with parameters; counts read "Products: 116" so they are right in languages with more than two plural forms.

Added

  • Shop page: the category's photo across the top with the name, breadcrumbs and sort on it (theme option, default on); the category's description under the products on its first page; products per page and the default sort as theme options (products_per_page, default_sort).
  • Colour filter: an attribute shown as colour swatches lists its values as swatches with names.
  • Badges on product cards from the product's badge categories (ui/badges).
  • Back in stock: product/stock-request on the product page when something is sold out; a sold-out card's button reads "Notify me" and leads there instead of a dead "Sold".
  • The basket's and side cart's button to the checkout reads "Go to checkout" — what it does — on a key of its own, so a translation can word it apart from the checkout page's title.
  • Checkout (Shop 1.3.0–1.6.0): company, house number, "Deliver to a different address" (opens without a script), the terms and newsletter boxes, the guarantee line; the coupon box (checkout/coupon, Enter applies instead of ordering); the hidden offer (shop/selection/checkout_offer); a line under each delivery method and, for one that books, a day and an hour under it; the summary names the delivery it counts.
  • Focused basket and checkout — theme option: on the basket, checkout, payment and thank-you pages the header, footer and side cart give way to site/checkout-header, a back arrow and the three steps.
  • Basket page: "Regular price / Your price" when something is below its regular price; delivery chosen on the page when there is a choice (theme option, default on).
  • Offers in merchandising rows: ui/add-button adds a one-size product directly and, with pickSize, offers a size select; rule makes the line carry the block's discount. shop/selection/item-compact shows the deal price.
  • Extension points, empty in the parent: cart/before-items, cart/before-totals, checkout/top, checkout/before-summary.
  • Free-shipping bar — partials/cart/free-shipping-bar.htm, one partial included in the side cart, the basket and the checkout summary: how much is still missing for free delivery, and a thin track that fills in the accent. Numbers from Shop 1.2.0 (Cart::free_shipping_*, on the net subtotal). Theme options under Basket: on/off (default on) and both texts, :amount replaced with what is missing. Hidden with no free-delivery amount in Settings › Shop › Shipping. A child restyles it by overriding the one file.
  • Discount lines in every total. The basket, the side cart, the checkout summary, the order confirmation and the account's order page list the shop's discount_lines (a coupon) by name under the subtotal, the amount in the accent. The side cart's subtotal is the net subtotal, after those lines. Needs Shop 1.0.39.
  • Offer notes on basket lines. A line priced by an upsell or bundle offer shows the offer's label under its name; one whose saving waits for its product says what to add ("Add Aidana for −15 %"). Basket, side cart, checkout summary. Needs Shop 1.1.1.
  • Pages and posts from the builder. pages/page.htm (/:slug), pages/post.htm (/blog/:slug) and pages/blog.htm (/blog) render entries made in Content › Pages and Posts, through forge/content-plugin's contentEntry and contentList. The theme's own pages keep their URLs; an entry can only take a slug nothing in the theme answers to. A further post type needs its own page here (or in a child) with [contentEntry] and a :slug URL.
  • The builder's elements, as partials: partials/builder/render.htm walks the tree, and container.htm, title.htm, text.htm and image.htm are one element each. A child restyles an element by overriding one file. Images are served as resized WebP with a srcset that never exceeds the original and sizes that account for the columns they sit in.
  • Product descriptions from the builder. pages/product.htm shows a product's builder layout in place of the plain description once one is live (or in the builder's preview), under the same "About this product" heading; nothing else on the page changes. Products without a layout render exactly as before. Spacing for it in forge.css (.section--desc-built).
  • Selling elements for the builder: partials/builder/button.htm; products.htm (the shop's cards as a grid or a one-row slider with arrows), spotlight.htm (one product with Add to basket) and categories.htm (the .cats tiles), whose data comes from Forge.ShopBlocks; a background image with an overlay in container.htm. Their layout in forge.css follows the shop's own breakpoints; the slider's arrows are in forge.js (delegated, so re-rendered sliders work).
  • Inside dark, accent and darkened-image sections a solid button's text turns dark, so a Main button stays readable.
  • Builder 2.3.0 elements: partials/builder/html.htm (the HTML element, an empty frame in the preview), styles for it in forge.css. Element partials print the ID and the editor's classes through builder_style / builder_classes; a title or text set to Custom with no size yet keeps its level's size.
  • Canvas pages. layouts/default.htm leaves out the announcement bar, header, search, footer and side cart when a page sets canvas (a Content entry displayed as Canvas), and adds page--canvas to the body. pages/post.htm prints the post's breadcrumbs, title, date, summary and "All posts" link only when the entry's display is Theme.
  • Builder colours and spacing: the element partials print builder_classes(node) and builder_style(node). forge.css gains Dark and Accent backgrounds and a dark tone for custom colours, each re-scoping --ink, --ink-muted and --line inside the section so every element's default colour turns light, plus Muted and Accent text colours.
  • Element styles in forge.css (Builder elements): every option an editor can pick is a class that resolves to a token, so a child retints all of it from theme.css. Plus the blog list's post cards and .shell--reading, a 46rem measure for posts.

Changed

  • Every interface string is now translatable. All 186 user-facing strings in the parent's pages and partials (the checkout, basket, account, payment, filters, reviews, empty states, labels, placeholders) go through RainLab.Translate's |_ filter with the English text as the key. A child theme supplies a language in theme.yaml — translate: { sl: config/lang-sl.yaml } — and runs php artisan translate:scan; nothing in the parent is copied. Content pages (about, FAQ, delivery, legal, home sections) stay per-site copy, as before. The header and footer are site content and are not translated.
  • Basket totals and the checkout summary show each fee by name (cart.fee_lines) instead of a bare "Payment fee".

Fixed

  • Sign-in, registration and the account pages run the shop's account component again. The layout and the seven account* pages declare [shopAccount account] instead of [account], following the shop plugin's rename in 1.0.36. RainLab.User had taken over the account name, which broke sign-in with "AJAX handler 'account::onSignIn' was not found".
  • The side cart did not open after adding from a product card. ui/product-card forwards openSideCart to ui/add-button even when the caller never set it, which hands over null; the button's is not defined test then read null as "do not open". The test is now is not same as(false), so only an explicit false suppresses the drawer.
  • Add-to-basket errors ("Only 1 left in stock") arrived as a browser alert; the forms now carry data-request-flash so they show as a flash message like everything else.

Added

  • Made-to-order products in a grid — ui/product-card shows "Made to order" instead of 0,00 € and ui/add-button links to the product's page (the configurator) instead of adding an empty line to the basket.
  • Online payment step — pages/payment.htm at /payment/:hash, pairing Responsiv.Pay's payment component with the shop's paymentPage. Gateway forms render from partials/pay/stripepayment.htm and partials/pay/paypalpayment.htm; the PayPal Buttons script sits apart in pay/paypal-script.htm so a child can override the copy without carrying it. partials/payment/form.htm is the AJAX target when the method changes, and partials/checkout/order-summary.htm renders a placed order's lines and totals for the side column. The order confirmation shows a Pay now block while an online order is unpaid and the paid date once it is. forge.css gains .notice--action and the .payform block. Responsiv.Pay would otherwise generate the pay/ partials in the child theme the first time the Payment Methods screen is opened — the parent copies mean it never has to.
  • PayPal buttons on the checkout page — partials/checkout/paypal-buttons.htm and checkout/paypal-buttons-script.htm render PayPal's buttons under the PayPal option and hide Place order while it is selected; the click places the order and pays in place.
  • Card fields on the checkout page — partials/checkout/card-form.htm (markup, copy) and checkout/card-form-script.htm (behaviour) put Stripe's card fields under the online option in pages/checkout.htm; Place order validates the card, places the order and confirms the payment without leaving /checkout. The summary's total row carries data-total-cents so the element's amount follows delivery changes. The checkout's empty state offers Pay now for an order placed but not yet paid. pay/stripe-appearance.htm is the shared token-to-Stripe mapping both card scripts use.
  • Card form — partials/pay/stripecardpayment.htm mounts Stripe's Payment Element (cards only) for the shop's StripeCardPayment driver, with the theme's own Pay button. The script in pay/stripe-card-script.htm reads the colour, radius and type tokens at runtime and hands them to Stripe, so the fields match the active child theme without any per-site CSS.
  • Embedded card form — partials/pay/stripeembeddedpayment.htm mounts Stripe Checkout in the page for the shop's StripeEmbeddedPayment driver; the script is separate in pay/stripe-embedded-script.htm, as with PayPal. forge.css reserves the iframe's height.

Changed

  • 2026-09-08 — theme directory wiped and recovered. At ~01:04 a single-argument rsync -a --delete themes/forge/ was run from inside themes/forge. With one argument rsync treats it as the source and the current directory as the destination, then deletes everything in the destination absent from the source; the source was an empty stray directory, so it emptied themes/forge entirely. Exit code 0, one line of output. The empty nested themes/forge/themes/forge was a separate artefact of an earlier mistyped rsync and has been removed. Recovered from four independent sources: the browser HTTP cache (tokens.css, forge.css, theme.css, forge.js, byte-exact), the compiled Twig cache, which embeds original template source while APP_DEBUG=true (23 templates, byte-exact bar a stripped trailing newline), the editing session's own context (18 files read off disk that day), and a replay of the Claude Code JSONL transcripts (27 files). Where sources overlapped they agreed exactly, which is what makes the transcript replays trustworthy for the files only they covered. Fully restored, with four exceptions. pages/account-login.htm, pages/cart.htm and partials/cart/{totals,default}.htm exist in no cache or transcript and were rebuilt, not recovered. Each carries a RECONSTRUCTED comment at the top. Their structure is verified — handler names and validation fields against Account::onSignIn(), the AJAX target ids against cart/items.htm, every accessor against the Cart model, every class against forge.css — but the wording is not the original's and should be rewritten when someone has time. pages/account-login.htm additionally reproduces four lines captured by a grep of the original at their exact line numbers. partials/cart/default.htm is the weakest: only its existence was ever established. Treat it as a placeholder.

[1.5.3] — 2026-09-09

Added

  • A sold-out product says so on the card. A stock-managed one-off is the normal case in a maker's shop: the piece sells once and is gone. The card still offered "Add to basket" and the customer found out only after clicking, from a flash message — the worst moment and the worst channel. ui/add-button now renders a disabled "Sold" state first, before every other branch, and the card carries .pcard--sold so the photo desaturates. The card stays fully legible: a sold piece is still the best evidence of what the maker makes.

Fixed

  • Accessibility and mobile sweep across every route, desktop and mobile:
    • /trgovina scrolled sideways on a phone — the sort control's native select and its nowrap label pushed the page 59px past the viewport. The toolbar now wraps and the select gives way instead of the page.
    • The configurator had no <h1>; home/feature-band takes level = 1 so the band can be the page heading.
    • The shop outline jumped h1 -> h3. ui/product-card takes a level (default 3, so it stays correct nested in a section) and the archive passes 2; the filter sidebar's heading is an h2.
    • Tap targets under 44px on mobile: footer and legal links, breadcrumbs, the main nav, the wordmark, the star-rating picker (~30px, on the one control that decides whether a review gets written), the sign-in alternatives, and the product card's title link — which at 26px was the ONLY way into a product, since the card's photo link is aria-hidden by design.
  • partials/site/skip-link.htm extracted from the layout, so a translated child no longer has to fork the whole layout to change the first three words a keyboard user hears.

[1.5.2] — 2026-09-09

Added

  • The configurator will not advance past an unanswered step. Next is a disabled button rather than a dead link, with a hint naming why; rail steps beyond the first unanswered one render as <span> rather than <a>, so they are out of the tab order entirely — there is nothing there to act on yet. Going back to an answered step stays free.

Fixed

  • The wizard could stop responding to navigation entirely. forge.js holds a step change that starts while a choice is in flight and replays it when the wizard repaints. Everything that released that hold hung off a DOM change, so a request that failed, was cancelled, or returned markup identical to what was there left the flag stuck and every later click was swallowed silently — no error, nothing in the console, navigation simply dead. There is now a 1.5s ceiling on the hold, and the queued navigation stores the step number and re-queries the link at replay time, because the rail repaints as part of the very response being waited on and the captured node is detached by then.

[1.5.1] — 2026-09-08

Fixed

  • partials/shop/filters.htm renders the product count on child categories, not just on the top-level ones.
  • partials/ui/product-card.htm spreads the demo photo fallback across ten Unsplash ids instead of one, keyed on product id so a product always gets the same picture. With one id, a fifty-product archive was fifty identical photographs, which makes a page impossible to read while testing. Pure scaffolding — irrelevant the moment products carry real images.

[1.5.0] — 2026-09-08

Changed

  • pages/product.htm renders selection.defaultBlocks — the built-in maker and shelf blocks — above selection.blocks, so a product page shows related products with nothing configured, and any offer rule the shop owner adds sits below them.

[1.4.1] — 2026-09-08

Fixed

  • The cart page did not refresh when adding from its own offer block. The add control repainted the header count and the side cart, but not #cartItems and #cartTotals — which is everything the customer is actually looking at on /cart. The item went into the basket; the page just did not say so. #cartItems and #cartTotals are now in the base update set in ui/add-button.htm, not passed per caller. Two reasons:
    • Off the cart page those selectors match nothing and the framework skips them (framework-bundle.js:677 returns an empty NodeList), so they cost nothing anywhere else.
    • In the base set, an override that forgets to pass them still gets them. The child's side_cart.htm override passes no targets at all and inherited the fix for free — had this been a per-caller parameter, that override would have stayed broken and nothing would have said so. Verified on /cart: lines 1 → 2, total 1,79 € → 2,91 €, header count 2 → 3.
  • The side cart no longer slides open when adding from the cart page's own offer block. The customer is already looking at the basket; the drawer was covering it.

[1.4.0] — 2026-09-08

Added

  • partials/shop/selection/checkout.htm — the checkout block, beside the order summary.
  • partials/shop/selection/item-compact.htm — a row-shaped item for the narrow placements (checkout, sidebar), replacing the full product card there. A card at ~350px is 440px tall on its photo alone, so three suggestions became a 1300px column nobody scrolls; the same three now fit in 443px. Two columns with the control under the price, because a third column for a long label like "Choose options" squeezes the product name to four lines.
  • Per-rule template overrides. The dispatcher asks the rule which partial to use, so partials/shop/selection/<code>.htm beats the placement's partial for a rule carrying that code. Every variant emits data-selection for CSS targeting.
  • ui/add-button.htm gained three optional parameters, each because checkout needs something no other placement does:
    • update — extra AJAX targets, appended to the base ones. A caller can extend the wiring but never replace it, so nobody can drop the cart-count repaint while adding their own.
    • openSideCart — false suppresses the drawer, which at checkout would slide over the form the customer is filling in.
    • formless — a button carrying its own request data instead of a <form>.

Fixed

  • Nested form at checkout. The block sits inside <form id="checkoutForm"> and the add control was itself a <form>. Browsers drop the inner one silently: the button renders, looks right, and does nothing. formless fixes it. The trade is that this one variant needs JavaScript where the form variant does not — confined to checkout deliberately.
  • The checkout block and the order summary now share one sticky wrapper (.checkout__side). As two separate grid children the block was auto-placed into row 2, which the tall form column pushes 850px down the page — technically in the right column, visually nowhere near the summary.
  • The checkout block renders outside #orderSummary. That element's contents are replaced wholesale by checkout/summary on every delivery and payment change, so anything inside it would vanish on the customer's first click.
  • Update-target lists render |raw. Escaped they still work — browsers decode entities when reading an attribute — but &#039;checkout/summary&#039; is unreadable to debug.

[1.3.5] — 2026-09-09

Fixed

  • Wizard requests are serialised, because clicking an option and then Next quickly lost the option. Both requests carry the session; the navigation loaded it before the choice had written, and saving it back at the end of the request put the stale copy on top. forge.js now holds a step navigation that starts while a choice is still in flight and replays it once the wizard repaints. Reproduced by clicking straight through all five steps with no delay — one answer went missing roughly half the time before, none after.
  • The address bar returns to step 1 with the wizard after add-to-cart, so a reload cannot jump the customer to the end of a build they no longer have.

[1.3.4] — 2026-09-08

Added

  • partials/home/swatches.htm accepts image_url on an item, so a material library can show the real material as a theme asset rather than a CSS gradient approximating it.

[1.3.3] — 2026-09-08

Added

  • partials/configurator/preview.htm — stacks the chosen options' layer artwork into one figure, falling back to the product photograph when a shop has drawn no layers. It is one role="img" with a single accessible name rather than a pile of images, so a screen reader hears "your knife: blade damascus, handle walnut" instead of five unlabelled graphics.
  • .knife / .knife__layer in forge.css. Every layer fills the same box with object-fit: contain and there is deliberately no per-layer offset: a position nudged in CSS is a position nobody can reproduce when the art is redrawn. Alignment belongs to the artwork.

[1.3.2] — 2026-09-08

Added

  • partials/ui/photo.htm accepts src — a ready-made URL, so a theme asset can be used anywhere a product photo can. home/hero, home/story and home/feature-band pass it through as image_url.

  • demo_photos theme option, default on. With it on, a product with no photo borrows an Unsplash picture so a new catalogue looks populated. Turn it off for a real shop: ui/photo then renders .photo--empty, a plainly-hatched frame, instead of a stock photograph of something the shop does not sell. Blacksmith ships with it off — a knife shop was showing landscapes and food.

    The empty frame is deliberately ugly. It should read as "this product still needs shooting", not pass for finished work in a screenshot.

Fixed

  • The hero's caption plate rendered as a small blank box over the corner of the photograph when neither plate_name nor plate_meta was supplied. It is now conditional.

[1.3.1] — 2026-09-08

Changed

  • The configurator walks between steps without reloading the page. Every step link keeps a real href — with JavaScript off the wizard walks normally, and that property is the first thing a later "simplification" of the hrefs would break. Larajax cancels the navigation only when it can actually handle it (core/trigger.js calls preventDefault() for click and submit), repaints #wizardStep and #wizardRail, and forge.js keeps the address bar and the history stack honest so a step stays linkable and Back walks backwards through the wizard rather than off the page.
  • partials/configurator/{step,rail,wizard}.htm now read their wording from {{ configurator.labels }} and their trust lines from {{ configurator.assurances }} instead of partial arguments — see forge/shop-plugin 1.0.20 for why. Partial arguments do not survive an AJAX re-render.
  • partials/configurator/step.htm gained a hidden step input on the choose form. It is load-bearing, not belt-and-braces: without it, choosing an option re-rendered the step column against the URL and could jump the customer back to step 1.
  • The step heading is id="wizardStepHead" tabindex="-1" and takes focus after each change, because the element that had focus was just replaced.

Notes for whoever touches forge.js next

  • The history hook is a MutationObserver on #wizardStep, not the ajax:request-success event. The step links live inside the region being replaced; Larajax dispatches its success event on the element that made the request, and by then that element is detached from the document, so a document-level listener never hears it. The obvious hook is silently dead.
  • popstate replays the matching rail link by dispatching ajax:trigger, not by calling .click(). A synthetic click on an anchor falls through to a real navigation whenever Larajax cannot process it, which is what happens when the link is mid-swap — pressing Back several times quickly caused a full page load. It also retries for ~1s before falling back to a reload, because the rail may still be swapping when the next popstate arrives.

Known limitation

  • Pressing Back rapidly through the whole wizard can still produce a single full page load at the end of the sequence. The step shown is correct either way, so this is polish rather than a defect. Verified in Chrome: forward navigation is 0 document loads across all four steps; a rapid four-press Back sequence produced exactly 1.

[1.3.0] — 2026-09-08

Added

  • assets/css/child.css — a sanctioned place for a child's own CSS. The layout now loads a fourth stylesheet, empty in the parent, that a child replaces like theme.css. It loads last, so it wins on equal specificity without !important. Before this, a child needing component CSS had two bad choices: put it in theme.css and break that file's documented scope so the next re-skin silently drops it, or shadow layouts/default.htm just to add a <link> — putting the theme's most structural file behind the override trap for the sake of one line.
  • Merchandising blocks are now overridable per placement. partials/shop/selection.htm is a dispatcher; the markup lives in partials/shop/selection/{product_page,cart,side_cart,sidebar,brand}.htm plus head.htm and item.htm. A child overrides any one of them and inherits the rest. Every block also carries .selection--<variant>, so CSS alone can restyle one placement with no template override at all. The override key is the variant, not the placement: the basket page and the side cart are both the cart placement but two different contexts, so they get two files.
  • partials/ui/add-button.htm — the add-to-basket control, extracted from ui/product-card.htm. One file now owns the cart wiring (handler, update targets, side-cart trigger) and both cards and merchandising blocks render it, so the two cannot drift. Verified identical output after the extraction: same 11 forms, same update targets, same 6 "Choose options" links.

Fixed

  • pages/brand.htm attached the brand placement but never rendered it, so a rule set to it produced nothing. It now renders below the producer's own shelf.

[1.2.3] — 2026-09-08

Fixed

  • assets/js/forge.js — the hero entrance block sat bare at the end of the file, so its if (!hero) return returned from the outer IIFE. On any page without a hero — every page but the homepage — everything after that point never ran, and anything appended to the file was silently dead code. It is now wrapped in its own IIFE like every other block, with a comment saying why. Found while reviewing the configurator step-navigation work, which had to be inserted before the hero block to run at all.

[1.2.2] — 2026-09-08

Changed

  • README.md — the copy-only override trap now also records why a shadowed parent template cannot be recovered from any cache: October compiles a template when it renders, a file the child overrides never renders, so nothing ever holds a copy. It is why pages/home.htm was the one file lost outright on 2026-09-08 while twenty-odd others came back byte-exact. The files most likely to be overridden are the files least likely to be recoverable.

[1.2.1] — 2026-09-08

Added

  • pages/home.htm back in the parent. It was lost in the wipe and, unlike the rest, was recoverable from nothing: the child shadows this file, so the parent copy never rendered and the Twig cache only ever held the child's version. Rebuilt against the section contract themes/forge-child/pages/home.htm proves — same partials, same argument shapes — with the parent's own unbranded copy, and marked RECONSTRUCTED in the file. Verified by lifting the child's override and rendering the parent's copy on its own: 200, every section present, no exceptions. Without it a fresh child inheriting from Forge had no homepage at all.

Changed

  • README.md documents the copy-only override trap: a child that shadows a parent partial only to reword it silently keeps serving its older structure when the parent changes. Found the hard way — the [selection cartUpsell] wiring added in 1.2.0 reached pages/cart.htm and partials/cart/drawer.htm, and a sibling site that shadowed both for translation lost the cart upsell with no error.

[1.2.0] — 2026-09-08

Added

  • partials/shop/selection.htm — renders the blocks a selection rule produces. Two variants: the full-width band under a product or the basket, and a compact one-column version for the shop sidebar, where a four-up grid would be unreadable at a quarter of the width. An empty result renders nothing at all — a heading with no products under it is worse than silence.
  • pages/brand.htm at /brand/:slug — a producer's own page. pages/producers.htm now links each card to it.
  • Four placements wired: product page (pages/product.htm), basket (pages/cart.htm), side cart (partials/cart/drawer.htm, in both the empty and filled states) and the shop sidebar (pages/shop.htm).
  • .selection block in forge.css, plus .pgrid--compact for the sidebar.

Changed

  • The product page's "From the same shelf" section, which was hardcoded to the product's own category, is now one selection rule among others. Same block by default; the difference is that the shop owner can now change what it selects, what it is called and how many it shows without touching a template.
  • layouts/default.htm attaches [selection cartUpsell]. The side cart repaints over AJAX from any page, so its component has to live in the layout rather than on the cart page — and it needs its own alias so it never collides with the page-level selection used for the product and sidebar placements.

[1.1.0] — 2026-09-08

Added

  • Platform section at /plugins — an index of every plugin and theme in this install with a subpage each, covering what it does, what is inside it, where to see it running, and its changelog rendered from the package's own CHANGELOG.md.
  • show_plugins_nav theme option (Brand tab, off by default). It gates both the masthead link and the pages themselves — with the switch off, /plugins returns 404, so a client site inheriting this theme never exposes the section.
  • .platform component block in forge.css: the index list, the detail two-column layout, the fact panel and the changelog typography.

[1.0.0]

Initial release. Reconstructed from the state of the theme before this file existed, so it carries no date.

Added

  • One layout (default.htm) attaching the cart, account and newsletter components, so every page can read the basket and the signed-in customer.
  • 23 pages covering the storefront, the account area, checkout and the legal set.
  • 30 partials across site/, home/, ui/, shop/, product/, cart/, checkout/ and account/.
  • Three-layer stylesheet split: tokens.css (structural scale, never overridden), theme.css (brand tokens, the one file a child replaces) and forge.css (structure and components).
  • config/fields.yaml — the theme options form every child inherits.
  • .themelock, making the theme inheritable but not activatable or editable.
  • The label grammar signature — origin · producer · year via partials/ui/origin — used on the hero, on every product card and on the story band.