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--okcolour 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-requeston 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-buttonadds a one-size product directly and, withpickSize, offers a size select;rulemakes the line carry the block's discount.shop/selection/item-compactshows 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,:amountreplaced 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) andpages/blog.htm(/blog) render entries made in Content › Pages and Posts, throughforge/content-plugin'scontentEntryandcontentList. 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:slugURL. - The builder's elements, as partials:
partials/builder/render.htmwalks the tree, andcontainer.htm,title.htm,text.htmandimage.htmare one element each. A child restyles an element by overriding one file. Images are served as resized WebP with asrcsetthat never exceeds the original andsizesthat account for the columns they sit in. - Product descriptions from the builder.
pages/product.htmshows 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 inforge.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) andcategories.htm(the.catstiles), whose data comes from Forge.ShopBlocks; a background image with an overlay incontainer.htm. Their layout inforge.cssfollows the shop's own breakpoints; the slider's arrows are inforge.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 inforge.css. Element partials print the ID and the editor's classes throughbuilder_style/builder_classes; a title or text set to Custom with no size yet keeps its level's size. - Canvas pages.
layouts/default.htmleaves out the announcement bar, header, search, footer and side cart when a page setscanvas(a Content entry displayed as Canvas), and addspage--canvasto the body.pages/post.htmprints the post's breadcrumbs, title, date, summary and "All posts" link only when the entry'sdisplayis Theme. - Builder colours and spacing: the element partials print
builder_classes(node)andbuilder_style(node).forge.cssgains Dark and Accent backgrounds and a dark tone for custom colours, each re-scoping--ink,--ink-mutedand--lineinside 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 fromtheme.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 intheme.yaml—translate: { sl: config/lang-sl.yaml }— and runsphp 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 theaccountname, 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-cardforwardsopenSideCarttoui/add-buttoneven when the caller never set it, which hands over null; the button'sis not definedtest then read null as "do not open". The test is nowis 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-flashso they show as a flash message like everything else.
Added
- Made-to-order products in a grid —
ui/product-cardshows "Made to order" instead of 0,00 € andui/add-buttonlinks to the product's page (the configurator) instead of adding an empty line to the basket. - Online payment step —
pages/payment.htmat/payment/:hash, pairing Responsiv.Pay'spaymentcomponent with the shop'spaymentPage. Gateway forms render frompartials/pay/stripepayment.htmandpartials/pay/paypalpayment.htm; the PayPal Buttons script sits apart inpay/paypal-script.htmso a child can override the copy without carrying it.partials/payment/form.htmis the AJAX target when the method changes, andpartials/checkout/order-summary.htmrenders 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.cssgains.notice--actionand the.payformblock. Responsiv.Pay would otherwise generate thepay/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.htmandcheckout/paypal-buttons-script.htmrender 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) andcheckout/card-form-script.htm(behaviour) put Stripe's card fields under the online option inpages/checkout.htm; Place order validates the card, places the order and confirms the payment without leaving/checkout. The summary's total row carriesdata-total-centsso 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.htmis the shared token-to-Stripe mapping both card scripts use. - Card form —
partials/pay/stripecardpayment.htmmounts Stripe's Payment Element (cards only) for the shop'sStripeCardPaymentdriver, with the theme's own Pay button. The script inpay/stripe-card-script.htmreads 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.htmmounts Stripe Checkout in the page for the shop'sStripeEmbeddedPaymentdriver; the script is separate inpay/stripe-embedded-script.htm, as with PayPal.forge.cssreserves 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 insidethemes/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 emptiedthemes/forgeentirely. Exit code 0, one line of output. The empty nestedthemes/forge/themes/forgewas 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 whileAPP_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.htmandpartials/cart/{totals,default}.htmexist in no cache or transcript and were rebuilt, not recovered. Each carries aRECONSTRUCTEDcomment at the top. Their structure is verified — handler names and validation fields againstAccount::onSignIn(), the AJAX target ids againstcart/items.htm, every accessor against theCartmodel, every class againstforge.css— but the wording is not the original's and should be rewritten when someone has time.pages/account-login.htmadditionally reproduces four lines captured by a grep of the original at their exact line numbers.partials/cart/default.htmis 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-buttonnow renders a disabled "Sold" state first, before every other branch, and the card carries.pcard--soldso 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:
/trgovinascrolled 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-bandtakeslevel = 1so the band can be the page heading. - The shop outline jumped
h1 -> h3.ui/product-cardtakes alevel(default 3, so it stays correct nested in a section) and the archive passes 2; the filter sidebar's heading is anh2. - 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.htmextracted 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.jsholds 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.htmrenders the product count on child categories, not just on the top-level ones.partials/ui/product-card.htmspreads 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.htmrendersselection.defaultBlocks— the built-in maker and shelf blocks — aboveselection.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
#cartItemsand#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.#cartItemsand#cartTotalsare now in the base update set inui/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:677returns an emptyNodeList), 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.htmoverride 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.
- Off the cart page those selectors match nothing and the framework skips them
(
- 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>.htmbeats the placement's partial for a rule carrying that code. Every variant emitsdata-selectionfor CSS targeting. ui/add-button.htmgained 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.formlessfixes 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 bycheckout/summaryon 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'checkout/summary'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.jsnow 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.htmacceptsimage_urlon 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 onerole="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__layerinforge.css. Every layer fills the same box withobject-fit: containand 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.htmacceptssrc— a ready-made URL, so a theme asset can be used anywhere a product photo can.home/hero,home/storyandhome/feature-bandpass it through asimage_url. -
demo_photostheme 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/photothen 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_namenorplate_metawas 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.jscallspreventDefault()for click and submit), repaints#wizardStepand#wizardRail, andforge.jskeeps 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}.htmnow read their wording from{{ configurator.labels }}and their trust lines from{{ configurator.assurances }}instead of partial arguments — seeforge/shop-plugin1.0.20 for why. Partial arguments do not survive an AJAX re-render.partials/configurator/step.htmgained a hiddenstepinput 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 theajax:request-successevent. 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. popstatereplays the matching rail link by dispatchingajax: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 nextpopstatearrives.
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 liketheme.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 intheme.cssand break that file's documented scope so the next re-skin silently drops it, or shadowlayouts/default.htmjust 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.htmis a dispatcher; the markup lives inpartials/shop/selection/{product_page,cart,side_cart,sidebar,brand}.htmplushead.htmanditem.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 thecartplacement but two different contexts, so they get two files. partials/ui/add-button.htm— the add-to-basket control, extracted fromui/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.htmattached thebrandplacement 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 itsif (!hero) returnreturned 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 whypages/home.htmwas 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.htmback 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 contractthemes/forge-child/pages/home.htmproves — same partials, same argument shapes — with the parent's own unbranded copy, and markedRECONSTRUCTEDin 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.mddocuments 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 reachedpages/cart.htmandpartials/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.htmat/brand/:slug— a producer's own page.pages/producers.htmnow 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). .selectionblock inforge.css, plus.pgrid--compactfor 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.htmattaches[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-levelselectionused 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 ownCHANGELOG.md. show_plugins_navtheme option (Brand tab, off by default). It gates both the masthead link and the pages themselves — with the switch off,/pluginsreturns 404, so a client site inheriting this theme never exposes the section..platformcomponent block inforge.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 thecart,accountandnewslettercomponents, 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/andaccount/. - Three-layer stylesheet split:
tokens.css(structural scale, never overridden),theme.css(brand tokens, the one file a child replaces) andforge.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 · yearviapartials/ui/origin— used on the hero, on every product card and on the story band.