Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## What this repository is

Nundar is a **shop plugin and a commerce theme for Mallok**, packaged as a Mallok site. It is not a standalone application: Mallok (the `mallok` npm package, pinned to an exact version, currently `0.1.0-rc.9`) provides routing, rendering, content, languages, hreflang, the sitemap, the admin, sign-in, media, email and the edge cache. Nundar adds only commerce.
Nundar is a **shop plugin and a commerce theme for Mallok**, packaged as a Mallok site. It is not a standalone application: Mallok (the `mallok` npm package, pinned to an exact version, currently `0.1.0-rc.11`) provides routing, rendering, content, languages, hreflang, the sitemap, the admin, sign-in, media, email and the edge cache. Nundar adds only commerce.

`docs/superpowers/specs/2026-09-30-nundar-on-mallok-design.md` is the source of truth for the architecture, with the owner's decisions in its §11. The commerce rules in `docs/superpowers/specs/2026-09-03-nundar-design.md` (§4–§7) still hold; its stack and architecture are superseded. The previous standalone Next.js implementation is at the tag `nextjs-final` — read it for reference, never restore it.

Nundar is in development. Several features wait for extension points Mallok does not have yet (design §7). **Do not work around a missing Mallok capability** — no writing into Mallok's core tables, no `onRequest` hook to intercept pages, no client-side fetching of prices. The Mallok-side work is done by the owner in the Mallok repository; do not edit that repository from a session here.
Nundar is in development. `mallok@0.1.0-rc.11` carries plugin API 2 — the extension points of design §7 — and several features are not built on them yet (the list is below). **Do not work around a missing Mallok capability** — no writing into Mallok's core tables, no `onRequest` hook to intercept pages, no client-side fetching of prices. A defect or a gap met in Mallok is written up for the owner, who does the Mallok-side work in the Mallok repository; do not edit that repository from a session here.

## Commands

Expand Down Expand Up @@ -60,27 +60,33 @@ Mallok decides the contracts on both sides. Its documentation is the reference:
### The theme and content

- Templates are restricted Liquid. Output is escaped; only `content.html` and `page.head` are emitted verbatim. `page.head` carries hreflang and structured data from Mallok and must stay in `layouts/base.liquid`.
- Interface strings are in `locales/*.json` (flat maps, the default locale is the fallback). Site-specific copy — the home page's headline and sections, the footer, the links behind the buttons — is a theme option, never a string in a template; per-language option values go under `themeOptions.$locales` in `site.json`, and `test/project.test.ts` fails when a language is left without one.
- **Kinds**: `product`, `collection`, `application` (the sample's industry pages, at `/industries`), `case`, `faq`, `tool`, `article`, `page`. A kind with a `base` needs a `listLayout`: without one Mallok answers its base path with a 500.
- Interface strings are in `locales/*.json` (flat maps, the default locale is the fallback). Site-specific copy — the home page's headline and sections, the footer, the links behind the buttons — is a theme option, never a string in a template; per-language option values go under `themeOptions.$locales` in `site.json`, and `test/project.test.ts` fails when a language is left without one. The tagline is not an option: it is Mallok's own setting, a map of language to text in `site.json`. It follows the site's name in the home page's title, and describes that page unless the `home_description` option gives the language a fuller text.
- **Kinds**: `product`, `collection`, `application` (the sample's industry pages, at `/industries`), `case`, `faq`, `tool`, `article`, `page`. Every kind with a `base` has a `listLayout` here, because each list is a page worth having; a kind without one has no list page — its base path answers 404 — and no entry in `site.kinds`. A template links to a kind's list only through `site.kinds.<kind>`, inside `{% if %}`: the breadcrumb (`partials/crumbs.liquid`) and the home page's link to the catalogue do, and `test/theme/unlisted.test.ts` holds both on a site that serves products without a list.
- **A product is one page with its sizes on it**, not a page per size. `facets` are the attributes a buyer filters by; `sizes` maps each SKU to what distinguishes it; `specs` is the full table. `partials/spec-table.liquid` (the specification finder, the catalogue, a collection's products, a product's neighbours) takes its columns from the first product that has `facets` and fills every row by attribute name, so every product in a language must use the same names. Below 72rem the same table is laid out as cards, two to a row on a tablet: seven columns need about 1100px in German.
- **References resolve by slug within the same language.** An `application` and a `case` name their `product`; the product page lists them through `content.backrefs.application` and `content.backrefs.case`. A `product` names its `collection`; the collection page lists `content.backrefs.product`. Mallok resolves `reference` only, not `reference[]`.
- **A link in a Markdown body is plain text to Mallok**: it is not rewritten per language and nothing reports a dead one. Write the path of the page in the same language (`/de/products/<german slug>`); `test/content.test.ts` checks every one.
- `[[inquiry]]` on a line of its own becomes the inquiry plugin's form when that plugin is enabled. Its labels exist in English and Chinese only (Mallok), so the other languages show English labels.
- `[[inquiry]]` on a line of its own becomes the inquiry plugin's form when that plugin is enabled. The plugin has its own labels in English and Chinese and reads any other language's from the theme's pack: the six `inquiry_*` keys in `locales/de.json`, `fr.json` and `es.json`. They are deliberately not in `en.json` — a pack falls back to the default one, so English keys would replace the plugin's own text in every language this theme has no pack for.
- The header puts the site name, the navigation, the language control and one button on a single line from 1280px. The sample's ten links fit in all four languages with little to spare (German is the longest); a longer label in `site.json` overflows that line, and only a look at the page at 1280px shows it.
- **A script only adds to a page that is already whole.** There are two. `assets/finder.js` puts filters above the specification table on the home page and in the catalogue. `assets/calculators.js` runs the three fastener calculators on a `tool` page whose front matter says `calculators: fasteners`, above a text that prints the same formulas, constants and tables — and `test/theme-scripts.test.ts` holds every constant in the script, and every figure it computes, to that page. In both cases the form is in the page `hidden`, with its labels from the language pack and its numbers in `value` attributes, so the script reads no language and the page offers nothing it cannot do. A script asks for a field with `querySelector`, never through `form.elements`: that list answers `length` with a count, whatever a field is called. A script is a plain file — no build step, no imports — declared in `theme.json`'s `clientScripts` with its exact size, and loaded only as `<script src="{{ theme.asset_base }}/<file>" defer></script>`, only by the layout that has what it works on. It hands its pure functions to `module.exports` when a `module` exists, which is how `test/theme-scripts.test.ts` runs them under `node:vm`; what it does to a page is checked in a browser, by hand.
- Fonts are files in `assets/fonts/`, declared in `style.css` and preloaded in `layouts/base.liquid`; nothing is loaded from another host. Changing any asset means bumping `version` in `theme.json`: assets are served from a versioned path and cached for good.
- Each language of a bundle is its own content item with its own `slug` (`index.md`, `index.<locale>.md`); Mallok puts them in one `translation_group`. A bundle carries a `mallok.json` only when something outside the content must name it: each product's fixes the `translation_group` that `seed/shop-sample.sql` attaches its variants to. `test/content.test.ts` checks the reference rule, keeps every identity file in step with its bundle, and holds the seed's variants to the SKUs the product pages list.
- The default language (English) is unprefixed; others are `/<locale>/…`. English pages default to USD, the rest to EUR (`lib/currency.ts`), never by IP.

### Known limits of mallok 0.1.0-rc.9 that shape the code
### What is not built on plugin API 2 yet

- No render-time hook with database access, so prices and variants are not on pages yet.
- Plugin routes cannot render through the theme, so there is no cart page yet.
- Plugin admin panels are read-only tables; variants are seeded from `seed/shop-sample.sql`.
- Plugin routes receive a parsed body, so a Stripe signature cannot be checked in one: there is no webhook route, and so no checkout.
- `reference[]` fields are not resolved, so a product names one collection.
`mallok@0.1.0-rc.11` allows each of these; the code here does not use it yet.

Each is a task in Mallok's plan for plugin API 2. When Mallok ships one, upgrade, remove the corresponding limitation here, and prove the new behaviour with a test or the smoke run.
- No `renderData` hook, so prices and variants are not on pages.
- No page route and no `pluginLayouts` in the theme, so there is no cart page.
- The variants panel is a read-only table; variants are seeded from `seed/shop-sample.sql`.
- No raw-body route, so no Stripe webhook, and so no checkout.
- The sample's products name one collection each, though `reference[]` is resolved now.

When one is built, take it off this list and prove the behaviour with a test or the smoke run.

### Known limits of mallok 0.1.0-rc.11 that shape the code

- Switching a plugin on or off, or changing a setting, can leave cached pages as they were — and the admin's "Clear cached pages" can report success without clearing anything. So the plugins are switched on before the first publish (`scripts/lib/local-shop.mjs`, the README), and `test/shop/helpers.ts` deletes the cached home page by hand after changing settings. Keep both until a Mallok release says the purge can be trusted.

## Testing

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ Everything runs on Cloudflare Workers with D1 and R2. Local development needs no

## What works today

Nundar builds on `mallok@0.1.0-rc.9`. Some of the shop needs extension points Mallok does not have yet; those parts wait for them rather than being worked around.
Nundar builds on `mallok@0.1.0-rc.11`, the first release with the plugin API the rest of the shop needs. The right-hand column is what has not been built on it yet.

| | Works today | Waits for Mallok's next plugin API |
| | Works today | Not built yet |
|---|---|---|
| **Pages** | A home page with a specification finder; product pages with their sizes and SKUs; collection, industry, case study, question, engineering reference and contact pages — all in English, German, French and Spanish, with `hreflang`, canonicals, sitemap and FAQ structured data; self-hosted fonts; no client JavaScript except two small scripts, each added to a page that is complete without it: filters for the finder, and calculators on the engineering reference page | Prices, variants and availability on the page; `Offer` structured data |
| **Catalogue data** | Variants, prices as integer minor units, stock, MOQ, lead time, a made-to-order policy | Editing them in the admin (read-only for now; the sample data is loaded from SQL) |
Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ Nundar 不是 Mallok 旁边的第二个应用。一个商城就是一个加了

## 现在能用什么

Nundar 基于 `mallok@0.1.0-rc.9`。商城的一部分功能需要 Mallok 目前还没有的扩展点,这些部分会等扩展点就绪,不做绕路实现。
Nundar 基于 `mallok@0.1.0-rc.11`,这是第一个带有商城其余部分所需插件 API 的版本。右侧一列是尚未在它之上实现的部分。

| | 现在可用 | 等 Mallok 的下一版插件接口 |
| | 现在可用 | 尚未实现 |
|---|---|---|
| **页面** | 带规格查找表的首页;列出尺寸和 SKU 的商品页;聚合页、行业页、案例页、问答页、工程资料页和联系页——全部有英、德、法、西四种语言,带 `hreflang`、canonical、sitemap 和 FAQ 结构化数据;字体由站点自己提供;除了两个小脚本之外没有客户端 JavaScript,而且页面没有它们也是完整的:一个给规格查找表加筛选,一个在工程资料页上提供计算器 | 页面上的价格、规格和库存状态;`Offer` 结构化数据 |
| **目录数据** | 规格、以整数最小单位存储的价格、库存、起订量、交期、按单生产策略 | 在后台编辑这些数据(目前只读,示例数据由 SQL 载入) |
Expand Down
Loading
Loading