Folio

Changelog

Every change to Folio, newest first. Versions follow semantic versioning: the major number changes when your existing settings would need attention.

All notable changes to Folio are recorded here. Versions follow

[semantic versioning](https://semver.org): the major number changes when a

merchant's existing settings would need attention.

[Unreleased]

Added

caption centred across the seam.

which tells a shopper there is more without an arrow or a row of dots.

The caption is one overlay across the whole section in both, so it does not

travel when the rail scrolls and never has to be repeated per panel. Each

panel takes its own link, caption and alt text. Split stacks on a phone; the

rail keeps scrolling and neither makes the page scroll sideways.

Changed

their values from section settings, so every product on a template showed the

same model note, the same fit scale and the same composition — the jacket was

claiming the leggings' fabric. They now read

custom.model_note, custom.fit_scale, custom.fit_detail and

custom.composition from the product, falling back to the block for a shop

that has not set them.

product fit and shipping, the FAQ answers, the size-guide advice and the

story lead. Instructional text is fine as onboarding but Shopify prohibits it

on a demo store, and the same templates drive both.

Fixed

Shopify rejects at upload — that setting type permits only <p>, <ul>,

<ol> and headings. The table moved to the size-guide page, which is what the

block's page picker was there for, and bin/validate-richtext.mjs now catches

disallowed tags at build time rather than at push.

Fixed

grid rebuild dropped the closing } of a media query in

snippets/product-grid.liquid. Shopify concatenates every {% stylesheet %}

block into one styles.css, so an unbalanced brace swallows every block

compiled after it — the search drawer, quick view, the size-guide modal and

the media lightbox all lost position: fixed and rendered inline in the page,

on every template.

Theme Check does not look inside stylesheet blocks and reported no offenses

throughout. bin/validate-css.mjs now checks every block for balance and runs

in CI, so this class of failure cannot ship silently again.

Fixed

role="dialog" and aria-modal from JavaScript, but their aria-label sat

on an inner panel, so screen readers announced an unnamed dialog. Caught by

re-running Lighthouse after the header and menu work — accessibility had

slipped from 100 to 97 and is back at 100.

Added

panel block:

What a short menu like Help actually needs.

against a rule, the rest as columns beside them.

Left on *Match the menu*, the shape is read from the menu structure: a child

with grandchildren is a column, a child without is a shortcut, and a menu made

only of shortcuts gets the compact dropdown. So a well-built menu gets the

right panel with no configuration, and the block is there to override it.

Fixed

stretched five links across the full viewport with nothing to structure them.

They now size to their content and group to the left.

Added

centred; menu left with the mark centred; stacked, where the mark sits on its

own row above the menu; and minimal, which keeps the menu in the drawer at

every width for a long or seasonal menu.

The header markup was restructured to make this possible. The navigation used

to be written twice with a conditional choosing one — which does not scale

past two arrangements, and put the same landmark in the document twice. There

is now one nav, four fixed children, and every layout is a grid placement

over them.

Fixed

header height. The nav sits after the brand in the DOM but belongs in the

first column, and grid auto-placement never moves its cursor backwards, so

the row has to be pinned explicitly.

Added

from a dozen:

stop the scroll, and pairs with a contrasting colour scheme.

the field reduced to a single rule and the submit to an arrow. Falls back to

bundled photography so it never collapses to one column before a merchant

picks an image.

the input loses its box and the button becomes an arrow that slides on hover.

Fixed

{{ style }} inside it was emitted verbatim and the style modifier never

applied. The class is now built before the tag.

Fixed

fallback_asset branch, so the video hero, campaign split, product spotlight

and lookbook all fell through to Shopify's line-art placeholder instead of

their onboarding photography. Restored, with decoding="async" added.

flat scrim, which dims the entire image to protect one corner of text. It now

uses a directional gradient anchored to the chosen content position —

heaviest where the copy sits, clearing to nothing across the rest of the

frame — plus a text shadow for the bright detail an average-based scrim

misses.

Fixed

collection with fewer than two full rows of products. Fixing the earlier

overlapping-tiles bug had gated the whole arrangement behind a product count,

so a merchant switching to Editorial on a four-product collection saw no

change at all — a control that does nothing is worse than one that looks odd.

Editorial is now two devices rather than one. Alternating tiles are offset

down the page, unconditionally, so the arrangement always reads differently

from the even grid whatever the product count. The double-width feature tile

layers on top of that once there are enough products for two full rows, and

is exempt from the offset so it anchors its row.

The offset uses margin-block-start with align-items: start on the grid.

Margin alone is not enough: while the grid stretches its items, a top margin

makes a tile shorter instead of moving it, so the cards ended up unequal

heights. Measured on the live store — even: four tiles all flush at the same

top; editorial: alternating tiles offset by 51 px, all heights equal, right

edge flush, no overlap.

Fixed

fourth tile was widened to two columns regardless of how many columns the grid

had, so in a four-column grid the wide tile could not fit the one remaining

column, wrapped, and left a hole in the row above. Alternating tiles were

offset with transform: translateY, which reserves no space, so they

overlapped their neighbours and the baseline went ragged. There was no dense

packing to backfill anything.

It is now built to tile exactly: each row is one double-width tile plus

however many single tiles fit beside it, so the repeating unit is

columns - 1 items and a row is always full. The rule is written per column

count because nth-child cannot read a custom property. The wide tile keeps a

16:10 frame, which is almost exactly the height of a single portrait tile at

double width, so row tops stay aligned.

The arrangement also now needs two full rows of products before it engages.

Four products in a four-column grid gave one full row and a single stranded

tile, which read as a mistake; short runs stay on the even grid.

Added

real product and the Journal blog rather than at nothing.

exist.

what still has to be done by hand.

Fixed — found by running the theme on a real store

sets display: none on the placeholder, and a display: none element never

reports as intersecting, so the IntersectionObserver that triggers the fetch

never fired. "Pairs well with" and "You may also like" rendered a heading over

empty space on every product page, on every store, since the section was

written. The observer now watches the section rather than the hidden

placeholder.

hero, campaign split, slideshow, media banner, editorial grid, rich text,

countdown, shoppable story, the button block and the menu promotion. Every one

of them scrolled the shopper to the top of the page, which is the complaint

originally raised about the collection index. They now fall back to the

all-products collection, which exists on every store.

Fixed — found by diffing the live store against source

carried a {% javascript %} tag, which Shopify supports in sections, blocks

and snippets but *not* in templates. The upload silently dropped the file —

Theme Check does not flag it, the CLI reports success, and the only symptom is

that the addresses page is missing on the storefront. Six of seven customer

templates uploaded; this was the seventh.

The script moved to assets/folio-account.js, loaded on account routes beside

account.css.

the same pattern that was sending shoppers to the top of the page from the

collection index. It is a real <button> now.

Added

instead of navigating, with the link to the collection inside it where it is

an explicit choice. Built on native <details>, so it is keyboard operable

and works with JavaScript off. Rows with no text stay plain links.

uploaded video still wins when both are set — it autoplays faster, carries no

third-party cookies, and can be driven by the on-screen pause control, which

a cross-origin embed cannot.

process → voices; page.our-story is chapter-led with alternating splits, a

film and a milestone timeline.

most enquiries resolve before the form; page.contact-visit leads with a

banner and a map for a physical space.

long-form reading measure with a last-updated line and a route to a person at

the end.

for this category, ready to paste into Shopify admin.

spotlight, not just the fabric block.

Fixed

JavaScript removed only its own placeholder, so "Pairs well with", "You may

also like" and "Recently viewed" each kept a heading with nothing under it

when the API had no results. The whole section is now removed on the

storefront; in the theme editor it stays and shows demo cards, so the merchant

can still see and select it.

with a pointer to the starter drafts when viewed in the editor.

Fixed — found on a real store

no default display, and thirty-two of them carried layout only through a

class. site-header had none, so the sticky header painted its background

behind text fragments instead of the whole bar — page content showed straight

through it — and transform: translateY(-100%) never applied, which also

broke the scroll-up hide. Declared once in base.css; any component that

wants flex or grid still sets it on its own class.

fell through to href="#". Rows without a destination are no longer links,

and each block gained an optional link setting.

(0.35 → 0.6). A light storefront needs more scrim than a dark one, so colour

and strength are both controllable.

anchors to the foot of the frame, with a setting for beside, below or over

the image.

over-filtered result now says so and offers to clear the filters, rather than

suggesting the shopper browse everything and discard the intent they just

expressed.

("orders over 100"), which reads as broken on every store.

Added

poster is the LCP and loads eagerly; the video is lazy, muted, inline, never

autoplays under reduced motion, and always carries a pause control (WCAG

2.2.2). Degrades to a still banner when no video is set.

are tappable as mailto: or tel:, opening hours, an image, social links,

and a map that takes an embed URL, an image, or neither (address plus a

directions link). Optional subject and order-number fields route enquiries.

page, landing pages or the foot of an article, where it excludes the piece

being read. Grid or rail.

only.

related reading and a newsletter; both were a single bare section before.

journal section rather than rich text impersonating one.

Added

size options in the buy box and opens a panel fed by a page the merchant

already maintains. Falls back to a standalone link if no product option

matches the configured name.

with a model note. Fit is comparative, and a position on a scale reads faster

than a sentence.

performance claims.

four-way stretch, compression, squat-proof, breathable, moisture-wicking,

lightweight, seamless, pockets, recycled and care.

data. The product trail follows the collection the shopper arrived through,

so "back" means the collection they were actually browsing.

product spotlight, collection index and lookbook onboarding states.

Airweight Zip Jacket and Sculpt Unitard — that are replaced automatically

when a merchant assigns a real collection.

lookbook stories for a convincing fresh install.

and five JavaScript modules.

cart drawer.

blog, article, page, contact, 404, password, gift card, and the seven

customer-account templates.

lookbook, image hotspots, before/after comparison, media banner, video,

slideshow, marquee, timeline, testimonials, FAQ, rich text.

featured product, product spotlight, shoppable story, recently viewed,

product recommendations, complementary products, quick order list.

custom Liquid, app blocks.

page (lookbook, FAQ, landing, size guide).

install state.

Changed

which collapsed the display-versus-body hierarchy the token layer builds.

Display is Jost; body stays Assistant.

height raised to match.

increased. The fit and fabric blocks are separated by hairlines so each reads

as its own answer rather than one undifferentiated column.

and buy, then fit, then fabric.

Fixed

97.6% cut, taking the theme package from 11.78 MB to roughly 0.6 MB. The

fresh-install hero is the LCP element and was shipping 1.92 MB to phones,

which would have failed the performance gate on its own.

scrolled back up to the form. It is now a real product form inside a

data-variant-target region, so the same section render that updates the

price also keeps the posted variant, availability and button label correct —

no extra JavaScript, no client-side money formatting.

now defaults to off; the cards still render in the theme editor through

request.design_mode, which is where they do their onboarding job.

text and pointed at /pages/faq and /pages/size-guide, which 404 on a fresh

install, and the branch was chosen by comparing a merchant-editable heading

against the English string 'Shop'. Empty columns now stay empty on the

storefront and show a prompt in the editor.

demo product card.

the dynamic scheme group on upload.

so a fresh Shopify upload cannot collapse into unstyled serif text when the

dynamic token layer is unavailable.

placeholder instead of blank numbered rows.

and timezone scheduling; disabled incompatible accelerated checkout for gift

card recipient forms.

gift cards.

and rich media plus dynamically loaded styles to quick view.

translation keys.

Notes

Nothing has shipped yet. The first public version will be tagged 1.0.0 on

Theme Store submission.

The display font handle jost_n5 has not yet been confirmed against Shopify's

hosted font library, because the theme has still never been rendered by

Shopify. Verify it on the first store push; if it is rejected it is a one-word

change in config/settings_schema.json and config/settings_data.json.