Skip to content
Open
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
19 changes: 15 additions & 4 deletions CLAUDE.md

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![CI](https://github.com/fobstack/Nundar/actions/workflows/ci.yml/badge.svg)](https://github.com/fobstack/Nundar/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](LICENSE)

> **Status: in development.** Nundar is being rebuilt on Mallok. The catalogue side works today. Prices and a cart are on the pages; checkout is not there yet — see [What works today](#what-works-today). It is not ready to run a real shop.
> **Status: in development.** Nundar is being rebuilt on Mallok. The catalogue side works today. Prices and a cart are on the pages, and a cart can be sent as a request for a quote; checkout is not there yet — see [What works today](#what-works-today). It is not ready to run a real shop.

---

Expand Down Expand Up @@ -42,7 +42,7 @@ Nundar is not a second application beside Mallok. A shop is a Mallok site with t

| Part | Where | What it owns |
|---|---|---|
| **Shop plugin** | `src/plugins/shop/` | Variants, prices per currency, stock, minimum order quantities, the cart, exchange-rate repricing |
| **Shop plugin** | `src/plugins/shop/` | Variants, prices per currency, stock, minimum order quantities, the cart, the cart sent as an inquiry, exchange-rate repricing |
| **Commerce theme** | `src/theme/` | How the home page, the specification finder and the product, application, collection, case study, question and reference pages look, in four languages |
| **Sample content** | `content/`, `seed/`, `site.json` | A titanium fastener catalogue in four languages: six products in five collections, five industry pages, four case studies, questions and answers, reference pages, and the site's own copy |

Expand All @@ -59,7 +59,8 @@ Nundar builds on `mallok@0.1.0-rc.11`, the first release with the plugin API the
| **Pages** | A home page with a specification finder; product pages with their sizes and SKUs, and for each size its price, minimum order, availability and lead time, read from the shop while the page is rendered and cached with it; the same offers in the page's `Product` structured data; a starting price under each product in the finder and the catalogue; 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; prices in US dollars on English pages and in euros on the others, with a switch to any currency the shop prices in; no client JavaScript except three small scripts, each added to a page that is complete without it: filters for the finder, the currency switch, and calculators on the engineering reference page | Prices beside the products a collection page lists (Mallok does not yet tell a plugin which products a content page shows) |
| **Catalogue data** | Variants, prices as integer minor units, stock, MOQ, lead time, a made-to-order policy — edited in the admin, in a form under each product's editor; every change of stock goes into a ledger; a deleted product takes its variants with it | |
| **Pricing** | USD base price; EUR and GBP derived from ECB rates with a buffer, rounding to a price point and a drift threshold; manual prices never overwritten; the pages of a product are purged from the cache when one of its prices moves | |
| **Cart** | A form beside each size on a product page, and a cart page in the site's own design and language: set a quantity, remove a line, choose a currency. No script anywhere in it. MOQ and stock are enforced by the server, which says what it refused on the cart page | Submitting a cart as one inquiry; checkout |
| **Cart** | A form beside each size on a product page, and a cart page in the site's own design and language: set a quantity, remove a line, choose a currency. No script anywhere in it. MOQ, stock and the most a line may hold (500) are enforced by the server, which says what it refused on the cart page | Checkout |
| **Inquiries** | The cart sent as one request for a quote, from a form on the cart page: who to answer, and a message. A size with no price can go in the cart to be asked about. The buyer lands on the inquiry's number; the seller is told by email, answerable straight to the buyer, and finds every inquiry in the admin with its lines — to mark, export as CSV, or delete | A challenge in front of the form (it waits for Mallok), which is why the email confirming to the buyer is off unless switched on |
| **Orders and payment** | The logic, tested and not yet reachable: orders with line snapshots, a payment that takes stock exactly once however often Stripe reports it, oversold orders, refunds that return stock, Stripe signature checks, order emails in four languages | The checkout and order pages, the webhook route, and order handling in the admin |

The reasoning and the plan are in [`docs/superpowers/specs/2026-09-30-nundar-on-mallok-design.md`](docs/superpowers/specs/2026-09-30-nundar-on-mallok-design.md).
Expand All @@ -70,7 +71,9 @@ The reasoning and the plan are in [`docs/superpowers/specs/2026-09-30-nundar-on-
- A page says whether a size can be had, never how many are left: a count would be wrong after the next sale, a state rarely is.
- The structured data offers exactly the prices the page prints. A test compares the two digit for digit.
- The cart stores variants and quantities only — never a price.
- MOQ is enforced by the form *and* by the server, because a form can be bypassed.
- MOQ, and the most a cart line may hold, are enforced by the form *and* by the server, because a form can be bypassed.
- An inquiry is a snapshot of the cart it was sent from, stored once however often the form arrives: tests send the same cart twice at the same moment.
- Nothing a buyer typed comes back through an address, becomes markup in an email, or runs as a formula in an exported spreadsheet.
- A public page is the same for every visitor and stays in the cache: nothing about a cart is in it, and the cart's cookie is sent only to the shop's own routes.
- Stock carries a database constraint, so a payment's decrement cannot go negative: a test proves the whole D1 batch rolls back.
- Stock comes off when a payment is confirmed, never before, and once: tests deliver the same payment twice at the same moment.
Expand Down Expand Up @@ -145,7 +148,7 @@ The sample is a supplier of titanium fasteners that does not exist. Its products

## Deploying

Not yet. A deployed shop today would show a catalogue with prices and a cart that leads nowhere: there is no checkout, and a cart cannot be sent as an inquiry. Nothing here has been measured on a real Cloudflare account either. When Nundar is ready, deployment is Mallok's own: `npx mallok create . --slug <slug>`, which creates the Worker, the D1 database and the R2 bucket on your own Cloudflare account. That path has not been run for this repository.
Not yet. A deployed shop today would show a catalogue with prices and a cart that can be sent as a request for a quote and cannot be paid for: there is no checkout. Nothing here has been measured on a real Cloudflare account either. When Nundar is ready, deployment is Mallok's own: `npx mallok create . --slug <slug>`, which creates the Worker, the D1 database and the R2 bucket on your own Cloudflare account. That path has not been run for this repository.

## Languages and currencies

Expand Down
13 changes: 8 additions & 5 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

**[Mallok](https://github.com/fobstack/mallok) 的商城插件和商城主题。** 为想靠买家真实搜索词获得流量的跨境卖家而做,不去和所有人争抢同一个大词。

> **状态:开发中。** Nundar 正在 Mallok 上重建。页面上已有价格和购物车;结账还没有,见[现在能用什么](#现在能用什么)。目前还不能用来经营真实店铺。
> **状态:开发中。** Nundar 正在 Mallok 上重建。页面上已有价格和购物车,购物车可以作为询价单发出;结账还没有,见[现在能用什么](#现在能用什么)。目前还不能用来经营真实店铺。

---

Expand Down Expand Up @@ -41,7 +41,7 @@ Nundar 不是 Mallok 旁边的第二个应用。一个商城就是一个加了

| 部分 | 位置 | 负责什么 |
|---|---|---|
| **商城插件** | `src/plugins/shop/` | 规格、各币种价格、库存、起订量、购物车、按汇率重新定价 |
| **商城插件** | `src/plugins/shop/` | 规格、各币种价格、库存、起订量、购物车、把购物车作为询盘发出、按汇率重新定价 |
| **商城主题** | `src/theme/` | 首页、规格查找表,以及商品页、应用场景页、聚合页、案例页、问答页和资料页的外观,四种语言 |
| **示例内容** | `content/`、`seed/`、`site.json` | 一套四种语言的钛紧固件目录:五个聚合页下的六个商品、五个行业页、四个案例、问答、资料页,以及站点自己的文案 |

Expand All @@ -58,7 +58,8 @@ Nundar 基于 `mallok@0.1.0-rc.11`,这是第一个带有商城其余部分所
| **页面** | 带规格查找表的首页;列出尺寸和 SKU 的商品页,每个尺寸带价格、起订量、库存状态和交期(渲染页面时从商城读出,随页面一起缓存);同样的报价写进页面的 `Product` 结构化数据;规格查找表和目录里每个商品名下显示起价;聚合页、行业页、案例页、问答页、工程资料页和联系页——全部有英、德、法、西四种语言,带 `hreflang`、canonical、sitemap 和 FAQ 结构化数据;字体由站点自己提供;英文页面以美元标价,其余语言以欧元标价,并可切换到商城支持的任一币种;除了三个小脚本之外没有客户端 JavaScript,而且页面没有它们也是完整的:给规格查找表加筛选、币种切换、工程资料页上的计算器 | 聚合页所列商品旁的价格(Mallok 目前不会告诉插件一个内容页列出了哪些商品) |
| **目录数据** | 规格、以整数最小单位存储的价格、库存、起订量、交期、按单生产策略——在后台每个商品编辑器下方的表单里编辑;库存的每次变动都记入流水;删除商品时一并清理它的规格 | |
| **定价** | 美元基准价;欧元和英镑按 ECB 汇率换算,带缓冲、价位取整和漂移阈值;手动价格永不被覆盖;某个商品的价格变动时,清除该商品页面的缓存 | |
| **购物车** | 商品页每个尺寸旁有一个表单;购物车页沿用站点自己的设计和语言,可改数量、移除、选币种,全程不需要脚本。起订量和库存由服务端校验,被拒绝的原因直接显示在购物车页上 | 把购物车作为一次询盘提交;结账 |
| **购物车** | 商品页每个尺寸旁有一个表单;购物车页沿用站点自己的设计和语言,可改数量、移除、选币种,全程不需要脚本。起订量、库存和单行数量上限(500)由服务端校验,被拒绝的原因直接显示在购物车页上 | 结账 |
| **询盘** | 在购物车页填一张表单(回复给谁、留言),把整个购物车作为一张询价单发出。没有价格的尺寸也可以加入购物车来询价。买家提交后看到询盘编号;卖家收到一封邮件,直接回复即可回到买家,并在后台看到每张询盘及其明细,可以标记、导出 CSV 或删除 | 表单前面的人机验证(等 Mallok 提供),因此给买家的确认邮件默认关闭,需要手动开启 |
| **订单与付款** | 逻辑已写好并有测试,但还没有入口可以用到:带行快照的订单、无论 Stripe 通知多少次都只扣一次库存的付款、超卖订单、退还库存的退款、Stripe 签名校验、四种语言的订单邮件 | 结账页和订单页、webhook 路由、后台的订单处理 |

原因和计划见 [`docs/superpowers/specs/2026-09-30-nundar-on-mallok-design.md`](docs/superpowers/specs/2026-09-30-nundar-on-mallok-design.md)。
Expand All @@ -69,7 +70,9 @@ Nundar 基于 `mallok@0.1.0-rc.11`,这是第一个带有商城其余部分所
- 页面只说一个尺寸能不能买到,不说还剩多少:数量在下一笔成交后就错了,状态很少变。
- 结构化数据里的报价与页面上印出的价格完全一致,有测试逐位比对。
- 购物车只存规格和数量,绝不存价格。
- 起订量由表单校验,**并且**由服务端再校验一次,因为表单可以被绕过。
- 起订量和单行数量上限由表单校验,**并且**由服务端再校验一次,因为表单可以被绕过。
- 询盘是发出时购物车的快照,无论表单到达多少次都只存一张:测试会让同一个购物车在同一时刻提交两次。
- 买家填写的内容不会经由网址带回页面,不会在邮件里变成标记,也不会在导出的表格里被当作公式执行。
- 公开页面对每个访客都一样,可以一直留在缓存里:页面上没有任何与购物车有关的内容,购物车的 cookie 只发给商城自己的路由。
- 库存带数据库约束,付款时的扣减不可能变成负数:有测试证明整个 D1 batch 会回滚。
- 库存只在付款确认后扣减,绝不提前,而且只扣一次:有测试让同一笔付款在同一时刻到达两次。
Expand Down Expand Up @@ -144,7 +147,7 @@ npm run seed:local

## 部署

暂时不要部署。现在部署出来的商城有带价格的目录和购物车,但购物车没有下一步:还不能结账,也不能把购物车作为询盘提交;并且这里的一切都还没有在真实的 Cloudflare 账号上实测过。等 Nundar 就绪后,部署走 Mallok 自己的流程:`npx mallok create . --slug <slug>`,它会在你自己的 Cloudflare 账号上创建 Worker、D1 数据库和 R2 存储桶。这条路径还没有在本仓库上跑过。
暂时不要部署。现在部署出来的商城有带价格的目录和购物车,购物车可以作为询价单发出,但还不能付款:没有结账;并且这里的一切都还没有在真实的 Cloudflare 账号上实测过。等 Nundar 就绪后,部署走 Mallok 自己的流程:`npx mallok create . --slug <slug>`,它会在你自己的 Cloudflare 账号上创建 Worker、D1 数据库和 R2 存储桶。这条路径还没有在本仓库上跑过。

## 语言和币种

Expand Down
Loading
Loading