Repository navigation
feat: prices on pages, a currency switch, a cart page and variants in the admin - #6
Merged
Merged
Conversation
The shop plugin declares plugin API 2 and a renderData hook. While Mallok renders a product page, a list or the home page, the hook reads the variants and prices of the products shown, in one batch, and the theme prints them: on a product page a row for each size with its price in the page's currency, its minimum order, its state and its lead time; in the finder and the catalogue, what each product starts at. The same offers go into the page's Product structured data, and each page is tagged with the products it depends on, for a price change to purge. A page says whether a size can be had, never how many are left. Mallok tells the hook what a list shows and not what a content page lists through a reference, so the products on a collection's page have no prices yet.
A page is rendered in its language's own currency, which is what a crawler reads and what its structured data states. Each price now also carries its amount in every other currency the page offers, and a third declared script puts one in its place when a buyer asks, keeping the choice in the browser's own storage — not a cookie, which would take that visitor's pages out of the shared cache. The switch is in the page hidden and absent when there is one currency or none. The sample variants gain a euro and a sterling price, entered by hand, so that the sample shows what the switch is for. An asset was added, so the theme is 0.7.0.
A product page now offers a form beside every size that can be ordered, and the cart is a page of the site: the shop plugin says what is in it, priced now, and the theme's shop/cart layout draws it in the site's own header, footer and language. Quantities, removing a line and choosing a currency are plain forms; nothing in the flow needs a script. The forms post to cart/update, a second route, because Mallok keys a handler by its path. A change that goes through answers 303 to the cart page. One that is refused — below the minimum order, more than there is — answers with the cart page saying what was refused, where the buyer can put it right. The page and an order are built from one reading of the cart, readCartFacts. The header gains a link to the cart, the same for every visitor. With it the one-line bar ran past the page's edge at 1280px in three languages, so the gap between its links is tighter where that bar begins and opens out from there. The theme is 0.9.0.
The variants panel is a form under every product's editor now. Mallok checks each value against the field the manifest declares and calls the shop's handlers, which judge what only the shop can — a SKU that is taken, a lead time that ends before it starts — and write the variant, its prices and the stock ledger in one batch. A base price derives the other currencies; a price entered by hand replaces the derived one and is never recomputed. Stock is set to a figure and the ledger records the difference. A variant that has been ordered is never deleted, from the form or with its product: it is archived. A product deleted in its last language takes its other variants with it. Whoever changes what a page shows now purges that product's pages: a save or a delete here, and a repricing run for the products whose prices moved. A save does not wait for its purge, which Mallok holds for two seconds to gather others.
Prices on pages, the currency switch, the cart and variants in the admin, each against the section of the design it carries out; phase 1's exit criteria one by one, two of them not met; and what waits for a decision or for a deployed site.
… pages A public page carries nothing of a cart; a cross-site form is refused; a page states availability and never a count. Three scripts, one of which keeps a currency code in the browser. And two residual risks said plainly: a cached page can show a price that has since changed, and stock set in the admin overwrites a sale that lands in between.
…ge-data defects Found by an independent review of phase 1B. With no currency every priced variant shares, a page prints no price, and its offers were still built in the language's own currency. They are built only in a currency the page shows. The starting price in each currency was the price of whichever variant is cheapest in the page's own; a price entered by hand can make another variant the cheapest in euros. It is the lowest in each currency now. A variant whose minimum order is more than a cart line may hold was offered a form that every submission of was refused. It has none. The lead time in the structured data was in days where the page says business days. It is stated in business days. The rule for which currency a page is in moves to one function, for the cart to share.
…cts in the variant form Found by an independent review of phase 1B. Whether to derive a price again was decided from the reading at the start of a save. Another save that changed the base price in between left euros derived from a dollar price that had been overwritten. The question is asked of the table inside the write now. A saved form wrote back the stock it had been opened with. The field comes to the form empty, and a save that names no figure writes none. A purge that was not attempted, or was turned down, was counted as done: Mallok's purge resolves either way. Its answer is read. A price entered by hand needs a base price, a price cannot be nothing, and a minimum order cannot exceed what a cart line may hold. Archiving a variant takes it out of carts, as its help text said. A variant kept for an order after its product is deleted gives its SKU back.
Found by an independent review of phase 1B. The path a form may send the buyer back to was judged by the text that was sent. /.//host begins with one slash and is //host, another site, once its dot segment is resolved. It is judged by the resolved path. A refused change was answered with the cart page, rendered by the POST. Mallok lists a page under the route that rendered it, so that page's language links and canonical pointed at an address that only takes a POST. A refusal is a redirect to the cart page now, with the reason and the variant in the address; the page reads the figures from the database, so a made-up link puts no words of its own on it. A cookie that names no live cart is no longer taken up: an expired cart's lines came back with the next thing added, and a visitor could choose the id that protects a cart. A product published for a later date could be put in a cart by its variant's id. A product can be bought when a visitor can see it. A part whose product is out in another language only was called gone when its buyer changed language, and a part priced only in euros could be left without a price in a dollar cart. The name falls back to any language the product is out in, and the cart settles its currency by the rule a page does. A line links to its product, and each Update button is named for its line. The theme is 0.10.0.
…hanged Eleven defects and seven smaller things, each checked before it was acted on and each fixed with a test that fails without it; the one suggestion not taken, and why; and what is still open.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Phase 1B of the design (
docs/superpowers/specs/2026-09-30-nundar-on-mallok-design.md, §12 and the new §17): the part of the catalogue that waited for Mallok's plugin API 2. Four things are built on it, with nothing worked around.Productstructured dataHow it works, in short
"pluginApi": 2and arenderDatahook. While Mallok renders a page, one batch of two statements reads the variants and prices of the products shown; the theme prints them, and the page is cached with them. A cold product page is three D1 round trips.GET /_mallok/p/shop/cartis the cart page, drawn by the theme'sshop/cartlayout.POST …/cart/updateis where every form posts; it only ever redirects. A change the shop refuses is sent to the cart page, which says what was refused.The commits
feat: show each size's price…renderDatahook, the page data, the theme's rows, the structured data, the cache tagsfeat: add a currency switch…feat: add a cart page…feat: edit variants, prices and stock in the admindocs: record what phase 1B built…,docs: bring the security notes up to date…SECURITY.mdfix:commits and adocs:An independent review, and what it found
The finished work was given to a second reviewer with the properties it must hold and no account of how it holds them. It found eleven defects and seven smaller things. Each was checked before it was acted on, and each fix has a test that fails without it. The three that mattered most:
/.//hostis one slash as typed and another site once resolved.The rest are listed in the design's §17, with the one suggestion that was looked at and not taken.
How it was verified
npm run lint,npm run typecheck,npm run buildnpm run test:projectnpm run test:shopnpm run smoke,npm run smoke:shop,npm run preview -- --checkfix:commitThe measuring found one regression before it was committed: with the cart's link in it, the header's one line ran past the page's edge at 1280 px in three languages. The gap between its links is tighter where that line begins.
Not verified
Not built
For the owner to decide
.99, which turns $1.60, $1.85 and $2.05 alike into €1.99. The sample uses hand-entered prices instead.