@feeef.dev/cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +94 -0
  3. package/dist/bin.js +2752 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.js +2730 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +67 -0
  9. package/scaffolds/blank/.cursor/rules/feeef-theme.mdc +34 -0
  10. package/scaffolds/blank/.cursor/rules/filterator.mdc +13 -0
  11. package/scaffolds/blank/.cursor/rules/order-form.mdc +17 -0
  12. package/scaffolds/blank/.cursor/rules/theme-source.mdc +32 -0
  13. package/scaffolds/blank/.cursor/skills/feeef-filterator/SKILL.md +15 -0
  14. package/scaffolds/blank/.cursor/skills/feeef-theme/SKILL.md +56 -0
  15. package/scaffolds/blank/.cursor/skills/feeef-theme/reference.md +73 -0
  16. package/scaffolds/blank/.vscode/extensions.json +3 -0
  17. package/scaffolds/blank/.vscode/settings.json +13 -0
  18. package/scaffolds/blank/AGENTS.md +104 -0
  19. package/scaffolds/blank/README.md +21 -0
  20. package/scaffolds/blank/docs/00-INDEX.md +33 -0
  21. package/scaffolds/blank/docs/03-CUSTOM-COMPONENTS.md +116 -0
  22. package/scaffolds/blank/docs/04-WORKFLOW.md +129 -0
  23. package/scaffolds/blank/docs/05-ANTI-PATTERNS.md +80 -0
  24. package/scaffolds/blank/docs/07-SHARED-COMPONENTS.md +144 -0
  25. package/scaffolds/blank/docs/08-ORDER-FORM.md +376 -0
  26. package/scaffolds/blank/docs/09-THEME-PORTING.md +211 -0
  27. package/scaffolds/blank/docs/10-SCHEMA-LIBRARY.md +127 -0
  28. package/scaffolds/blank/docs/11-PAGES-CHECKOUT-THANKS-PRODUCT.md +81 -0
  29. package/scaffolds/blank/docs/12-DARK-LIGHT-THEME.md +164 -0
  30. package/scaffolds/blank/docs/13-TEMPLATE-I18N.md +320 -0
  31. package/scaffolds/blank/docs/14-FILTERATOR.md +433 -0
  32. package/scaffolds/blank/docs/16-AUTHORING-TS-IMPORTS.md +73 -0
  33. package/scaffolds/blank/feeef.template.json +7 -0
  34. package/scaffolds/blank/locales/README.md +4 -0
  35. package/scaffolds/blank/locales/ar.json +10 -0
  36. package/scaffolds/blank/locales/en.json +10 -0
  37. package/scaffolds/blank/package-lock.json +1975 -0
  38. package/scaffolds/blank/package.json +23 -0
  39. package/scaffolds/blank/pages/home/components/hero.tsx +34 -0
  40. package/scaffolds/blank/pages/home/page.json +3 -0
  41. package/scaffolds/blank/props.json +6 -0
  42. package/scaffolds/blank/schema.ts +49 -0
  43. package/scaffolds/blank/tsconfig.json +24 -0
  44. package/scaffolds/blank/types/feeef-live-scope.d.ts +162 -0
  45. package/scaffolds/blank/types/feeef-template-schema.d.ts +84 -0
  46. package/vendor/template-kit/README.md +64 -0
  47. package/vendor/template-kit/bin/dev.mjs +61 -0
  48. package/vendor/template-kit/bin/feeef-template.mjs +212 -0
  49. package/vendor/template-kit/data/lithium-schema.json +4733 -0
  50. package/vendor/template-kit/lib/build.mjs +377 -0
  51. package/vendor/template-kit/lib/compile-component.mjs +188 -0
  52. package/vendor/template-kit/lib/component-meta.mjs +481 -0
  53. package/vendor/template-kit/lib/library.mjs +168 -0
  54. package/vendor/template-kit/lib/locales.mjs +68 -0
  55. package/vendor/template-kit/lib/paths.mjs +84 -0
  56. package/vendor/template-kit/lib/shared.mjs +268 -0
  57. package/vendor/template-kit/lib/theme-schema.mjs +300 -0
  58. package/vendor/template-kit/lib/unpack.mjs +324 -0
  59. package/vendor/template-kit/lib/validate.mjs +207 -0
  60. package/vendor/template-kit/schemas/component.schema.json +55 -0
  61. package/vendor/template-kit/schemas/template.schema.json +18 -0
@@ -0,0 +1,211 @@
1
+ # Theme Porting Playbook — Lessons from Dawn
2
+
3
+ > Theme package copy — paths assume this theme folder is the project root.
4
+
5
+ Use this when porting a reference storefront theme (Shopify Dawn, etc.) into Feeef Lithium custom components, or when starting a **new** theme after Dawn.
6
+
7
+ Companion: [`AGENTS.md`](../AGENTS.md), [`05-ANTI-PATTERNS.md`](./05-ANTI-PATTERNS.md), [`08-ORDER-FORM.md`](./08-ORDER-FORM.md), [`07-SHARED-COMPONENTS.md`](./07-SHARED-COMPONENTS.md).
8
+
9
+ ---
10
+
11
+ ## 0. Goal
12
+
13
+ Match **craft and behavior**, not pixel-perfect Shopify Liquid. Feeef has different APIs (products list, cart, COD). Document intentional gaps (mega-menus, Color metafields, newsletter).
14
+
15
+ ---
16
+
17
+ ## 1. Workflow (new theme or major port)
18
+
19
+ 1. Unpack / scaffold source under `templates/<theme>-src/` (Dawn: ``).
20
+ 2. Promote chrome to **`shared/components/`** on day one: header-bar, footer, search, cart drawer — pages only `$ref`.
21
+ 3. Pixel-compare reference vs local at **same viewport** (e.g. 1440px) with browser tools.
22
+ 4. Wire Feeef APIs early (list, search, cart, shipping) — UI polish without data looks “broken”.
23
+ 5. After every batch: `npm run build` (or `template:dev`).
24
+ 6. Hard-refresh the preview store (`FEEEF_TEMPLATE_DATA_PATH`).
25
+
26
+ ```bash
27
+ npm run dev
28
+ # store: http://<slug>.localhost.feeef.org:3000/
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 2. Shared chrome (mandatory)
34
+
35
+ | Block | Must be shared | Notes |
36
+ |---|---|---|
37
+ | Announcement + header + drawers | `shared.dawn-header-bar` (or theme equivalent) | Search + cart live here |
38
+ | Footer | `shared.dawn-footer` | |
39
+ | COD / order shell | Prefer native `product_order_form` or full `08` contract | Simplified stubs are incomplete |
40
+
41
+ **Do not** paste header/footer JSX into home + products + product shells. Duplication caused search/cart drift during Dawn port.
42
+
43
+ Open cart from PDP after add:
44
+
45
+ ```js
46
+ window.dispatchEvent(new CustomEvent('dawn:open-cart'));
47
+ // Header listens once:
48
+ // window.addEventListener('dawn:open-cart', () => setIsCartOpen(true));
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 3. Craft / visual rules (Dawn-class)
54
+
55
+ - Restraint: white chrome, thin rules, airy type — not purple gradients / card spam.
56
+ - Prefer **Assistant** (or theme font) loaded once via `<link>` id guard.
57
+ - Brand colors: `hsl(var(--primary))`. Chrome black/white OK only for rare craft accents; prefer tokens so dark/light works — see `docs/12-DARK-LIGHT-THEME.md`.
58
+ - Arabic / RTL stores: **multilang themes** use `useFeeefLocale()` + `dir={locale === 'ar' ? 'rtl' : 'ltr'}` and `t()` (see [13-TEMPLATE-I18N.md](./13-TEMPLATE-I18N.md)). Force **`dir="ltr"`** only when intentionally matching an LTR-only reference craft on an RTL store.
59
+ - Never wrap the whole page shell in `min-height: 100vh` in a way that pushes PDP content down — header chrome should be height-of-content.
60
+
61
+ ### Product cards
62
+
63
+ - Square media (`aspect-ratio: 1 / 1`).
64
+ - Hover secondary image when `media` has a **second distinct** URL:
65
+
66
+ ```js
67
+ const raw = [product.photoUrl].concat(product.media || []).filter(Boolean);
68
+ const seen = {};
69
+ const images = raw.filter((src) => {
70
+ if (seen[src]) return false;
71
+ seen[src] = true;
72
+ return true;
73
+ });
74
+ const secondary = images[1] || null;
75
+ ```
76
+
77
+ - `product.media` is **`string[]`**, not `{ url }[]`.
78
+ - Enable `showSecondaryImage` by default on PLP / featured.
79
+
80
+ ### Search
81
+
82
+ - Feeef list search param: **`params.search`** (also send `q` for compatibility).
83
+ - URL query stays `?q=` for the storefront.
84
+ - Prefer **predictive search** panel (live results + “Search for …” → `/products?q=`), not a bare full-screen input.
85
+
86
+ ### Footer
87
+
88
+ - Shopify “Subscribe to our emails” is **unsupported** on Feeef — replace with **About / store bio** (`store.description` stripped of HTML, or props fallback).
89
+ - Keep Quick links / Info / mission columns; social from `store.contacts`.
90
+
91
+ ### PLP filters
92
+
93
+ - Map reference facets to Feeef reality: Availability + Price + **Category** (not Shopify Color metafields unless you build metafield support).
94
+ - Empty search (`?q=product` with no matches) is valid — compare filled `/products` to a collection page.
95
+
96
+ ---
97
+
98
+ ## 4. Cart & PDP (high bug rate)
99
+
100
+ ### Add to cart
101
+
102
+ ```js
103
+ // WRONG — does not add a line
104
+ cart.updateCurrentItem({});
105
+ document.querySelector('[aria-label="Cart"]')?.click();
106
+
107
+ // RIGHT
108
+ if (!cart.getCurrentItem() || cart.getCurrentItem().product.id !== product.id) {
109
+ cart.setCurrentItem({ product, quantity: 1 });
110
+ }
111
+ cart.addCurrentItemToCart(); // or cart.add({ product, quantity, variantPath })
112
+ window.dispatchEvent(new CustomEvent('dawn:open-cart'));
113
+ ```
114
+
115
+ ### Bootstrap current item on PDP mount
116
+
117
+ Qty steppers and add-to-cart need `cart.getCurrentItem()`. Set it when product loads. Variants must `setCurrentItem` / `updateCurrentItem` safely.
118
+
119
+ ### Shipping bootstrap — **once per product**
120
+
121
+ Without native `OrderForm`, custom PDPs must wire shipping (see `lib/bootstrap-product-cart-shipping.ts` + scope API). Inline in react-live (no imports):
122
+
123
+ ```js
124
+ cart.setShippingMethod(product.shippingMethod || store, false);
125
+ cart.setShippingAddress({ type: 'home', state: null, city: null, country: 'dz', ... }, false);
126
+ // then ff.shippingPrices.find → cart.setShippingPrice(sp, false)
127
+ ```
128
+
129
+ **Critical bug (Dawn):** if the bootstrap `useEffect` depends on **`cartCtx`** (object identity), every `updateShippingAddress` recreates context → effect re-runs → `setShippingAddress({ state: null })` **wipes** state/city/delivery types.
130
+
131
+ **Correct pattern:**
132
+
133
+ ```js
134
+ const bootKey = React.useRef('');
135
+ const cartReady = !!cartCtx?.cart;
136
+
137
+ React.useEffect(() => {
138
+ if (!product || !cartReady || !store || !cartCtx?.cart) return;
139
+ const cart = cartCtx.cart;
140
+ // setCurrentItem if needed…
141
+ const key = product.id + ':' + store.id;
142
+ if (bootKey.current === key) return; // already bootstrapped
143
+ bootKey.current = key;
144
+ // setShippingMethod + setShippingAddress PRESERVING existing.state/city
145
+ // load shippingPrices once
146
+ }, [product?.id, store?.id, cartReady, ff]);
147
+ ```
148
+
149
+ - Depend on **`cartReady` (boolean)**, not `cartCtx`.
150
+ - Preserve `existing.state` / `existing.city` / `existing.type` when writing address.
151
+ - `getAvailableShippingTypes()` is **empty until state (wilaya) is set** — show a hint, then full-width delivery cards.
152
+
153
+ ### Floating labels on `<select>`
154
+
155
+ Empty selects that use a first option labeled `"State"` **overlap** the floating label.
156
+
157
+ - First option: `<option value="" disabled hidden />` (no text).
158
+ - Always float the label for selects (`.dn-field--select .dn-field__label` at top).
159
+ - Reserve `padding-top` on the select even when empty.
160
+
161
+ ---
162
+
163
+ ## 5. Order / COD
164
+
165
+ Follow **[`08-ORDER-FORM.md`](./08-ORDER-FORM.md)** for production behavior:
166
+
167
+ - Submit via `POST /api/orders/create` (not bare `ff.orders.send` in production).
168
+ - Draft + pending, geo cascade, security.
169
+
170
+ Dawn’s shared `dawn-order-form` and early PDP COD stubs are **visual** until they match that contract.
171
+
172
+ ---
173
+
174
+ ## 6. Intentional gaps vs Shopify Dawn
175
+
176
+ | Shopify Dawn | Feeef approach |
177
+ |---|---|
178
+ | Mega-menu Bags/Shoes | Flat links or custom later |
179
+ | Color metafield filters | Category (or skip) |
180
+ | Newsletter signup | Store bio / About |
181
+ | Best-selling sort | Map to Feeef `sort` values you support |
182
+ | Country/currency selector | Optional / store config |
183
+ | Predictive collections/pages | Products-only is enough |
184
+
185
+ ---
186
+
187
+ ## 7. New-theme checklist
188
+
189
+ - [ ] `shared/` header (search predictive + cart drawer + `dawn:open-cart`)
190
+ - [ ] `shared/` footer (bio, not fake newsletter)
191
+ - [ ] Home / products / product / contact use `$ref` for chrome
192
+ - [ ] PDP: `setCurrentItem` + shipping bootstrap **once** + real add-to-cart
193
+ - [ ] PLP: `search` param, secondary image hover, Dawn-like filter bar
194
+ - [ ] Selects: no label overlap
195
+ - [ ] Delivery cards: expanded, appear after state
196
+ - [ ] `dir="ltr"` on chrome if matching LTR reference on RTL stores
197
+ - [ ] `template:build` + browser verify vs reference
198
+ - [ ] Order form path follows docs/08 (or registry form)
199
+
200
+ ---
201
+
202
+ ## 8. File map (Dawn)
203
+
204
+ | Area | Path |
205
+ |---|---|
206
+ | Shared header | `shared/components/dawn-header-bar/` |
207
+ | Shared footer | `shared/components/dawn-footer/` |
208
+ | Home shell | `pages/home/.../custom-dawn-header-home/` |
209
+ | Products PLP | `.../dawn-collection-banner/` + `dawn-collection-grid/` |
210
+ | PDP | `pages/product/.../custom-1777344732828270/` |
211
+ | Reference Liquid | `templates/_references/dawn/` (read-only) |
@@ -0,0 +1,127 @@
1
+ # Schema Library (`schema.library.json`)
2
+
3
+ Theme-bundled **drag-drop catalog** for the merchant template editor.
4
+
5
+ Components listed here can be **unused in `data.json`**. That is intentional: the theme ships extras the merchant can add later from the UI.
6
+
7
+ ## Three planes (do not confuse)
8
+
9
+ | Plane | Theme source | Built output | Role |
10
+ |---|---|---|---|
11
+ | **Schema** | `schema.ts` (overlay) | `dist/schema.json` | Editor chrome: pages, layouts, root `propsSchema`, Lithium built-ins + overrides |
12
+ | **Library** | `library/components/` | `schema.library` on schema | Optional drag-drop extras (inline copy) |
13
+ | **Data** | `pages/`, `props.json` | `dist/data.json` | Live instances / merchant content |
14
+
15
+ | Catalog entry | Storage | Insert mode | Audience |
16
+ |---|---|---|---|
17
+ | **Built-in** | Lithium `components` (merged into schema) | Registry `type` + defaults | Stock blocks |
18
+ | **Theme library** | `schema.library.json` / `schema.library` | **Inline copy** | Theme author extras |
19
+ | **My library / Marketplace** | DB `template_components` | `type: "reference"` + `refId` | Merchant / public reuse |
20
+
21
+ ```mermaid
22
+ flowchart LR
23
+ schemaTs["schema.ts overlay"]
24
+ lithium["Lithium built-ins"]
25
+ disk["library/components/id/"]
26
+ build["template:build"]
27
+ schema["dist/schema.json"]
28
+ editor["Flutter Theme tab"]
29
+ data["templateData placement"]
30
+ schemaTs --> build
31
+ lithium --> build
32
+ disk --> build
33
+ build --> schema
34
+ schema --> editor
35
+ editor -->|"copy-on-drop"| data
36
+ ```
37
+
38
+ ## Authoring (template-kit)
39
+
40
+ ```
41
+ templates/dawn-src/
42
+ library/components/<id>/
43
+ component.json # type, code meta, props, schemas + catalog fields
44
+ component.jsx # optional
45
+ slots/… children/…
46
+ dist/
47
+ schema.library.json
48
+ data.json
49
+ ```
50
+
51
+ ### Catalog fields on `component.json`
52
+
53
+ | Field | Role |
54
+ |---|---|
55
+ | `libraryId` / folder name | Stable `id` in the library list |
56
+ | `title`, `subtitle`, `category`, `tags`, `imageUrl` | Editor card |
57
+ | `type` | Usually `custom`; may be a built-in registry type |
58
+ | `props` | Becomes `propsDefault` on drop |
59
+ | `code` / `component.jsx` | Custom JSX |
60
+ | `propsSchema`, `slotsSchema`, `slotsLayout`, `slots`, `children` | Seeded on drop |
61
+
62
+ Build:
63
+
64
+ ```bash
65
+ cd storefront && npm run template:build
66
+ # → dist/schema.library.json
67
+ ```
68
+
69
+ ## Wire shape
70
+
71
+ ```json
72
+ {
73
+ "version": "1.0",
74
+ "components": [
75
+ {
76
+ "id": "dawn-promo-banner",
77
+ "title": "Promo Banner",
78
+ "subtitle": null,
79
+ "category": "marketing",
80
+ "tags": ["banner"],
81
+ "imageUrl": null,
82
+ "type": "custom",
83
+ "code": "function App() { return <div>{props.text}</div>; }",
84
+ "propsSchema": { "text": { "type": "string" } },
85
+ "propsDefault": { "text": "Sale" },
86
+ "slotsSchema": null,
87
+ "slotsDefault": null,
88
+ "slotsLayout": null,
89
+ "children": null
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ ## Serving
96
+
97
+ | Endpoint | Behavior |
98
+ |---|---|
99
+ | `GET /api/template/schema` | Lithium `schema.json` **plus** `library: [...]` from `template/schema.library.json` |
100
+ | `GET /api/template/library` | Standalone library document |
101
+ | Dev | `FEEEF_TEMPLATE_LIBRARY_PATH` → kit `dist/schema.library.json` |
102
+
103
+ `StoreTemplate.schema` should include the same `library` array when publishing a theme so `TemplateFetchResult.fromStoreTemplate` exposes it via `rawSchema['library']`.
104
+
105
+ ## Editor behavior
106
+
107
+ - Tab **Theme** lists `controller.themeLibraryEntries` (`rawSchema['library']`).
108
+ - Drop / tap → **inline copy** into `templateData` (`buildThemeLibraryComponent`).
109
+ - Not a live link to the theme file: later theme updates do **not** rewrite existing placements (unlike DB `reference`).
110
+ - Prefer `shared/` + `$ref` for compile-time consistency across pages you ship in `data.json`; use **library** for optional blocks merchants may add.
111
+
112
+ ## vs `shared/`
113
+
114
+ | | `shared/components` | `library/components` |
115
+ |---|---|---|
116
+ | Purpose | DRY placements already in the theme tree | Optional catalog for the editor |
117
+ | Build | Inlined into `data.json` via `$ref` | Emitted as `schema.library.json` |
118
+ | Unused OK? | No (only if referenced) | **Yes** |
119
+
120
+ A component can exist in **both** (shared for shipped pages + library for drag-drop of the same block elsewhere).
121
+
122
+ ## Rules for AI
123
+
124
+ 1. New optional theme UI → add under `library/components/<id>/`, rebuild.
125
+ 2. Do not put library-only blocks into `data.json` unless the blank theme should include them.
126
+ 3. When packing a `StoreTemplate`, merge `library` into `schema`.
127
+ 4. Flutter insert = copy-on-drop, not `type: "reference"` (no DB id).
@@ -0,0 +1,81 @@
1
+ # Template Pages — checkout, thanks, product PDP
2
+
3
+ > Theme package copy — paths assume this theme folder is the project root.
4
+
5
+ Canonical routes and the **single-`main`-section** pattern.
6
+
7
+ ## Page matrix
8
+
9
+ | Page id | Path | Sections | Notes |
10
+ |---|---|---|---|
11
+ | `product` | `/p/:id` **and** `/products/:id` | **`main` only** | Both URLs render the same PDP. Prefer `/p/` for links; `/products/:id` is an alias. |
12
+ | `checkout` | `/checkout` | **`main` only** | Cart checkout. Prefer registry `cart_order_form` in a form slot. |
13
+ | `thank_you` | `/thanks` | **`main` only** | Optional post-order page. Query params carry order context. Legacy `/thank-you` redirects. |
14
+ | `not_found` | Next.js not-found | **`main` only** | Editable 404 (`type: "not_found"` or custom). |
15
+ | `products` | `/products` | multi | Collection / PLP (unchanged). |
16
+ | `home` / `contact` / … | … | multi | Unchanged. |
17
+
18
+ ## Pattern: one section, layout in slots
19
+
20
+ ```text
21
+ pages.<id>.sections.main.components[0] ← page shell (custom)
22
+ slots.header[]
23
+ slots.body[] | slots.form[]
24
+ slots.footer[]
25
+ ```
26
+
27
+ Next.js page components only render `sections.main`. They do **not** invent header/footer section keys.
28
+
29
+ Use a single `main` section; put chrome in slots.
30
+
31
+ ### Product runtime fallback
32
+
33
+ `ProductPageContent` prefers `main`. If `main` is empty, it still renders the legacy multi-section tree (`header` / `top` / `start` / `end` / `bottom` / `footer`) for older store blobs.
34
+
35
+ New themes (Dawn) ship `main` + slots only.
36
+
37
+ ## `/checkout`
38
+
39
+ - Route: `app/checkout/page.tsx`
40
+ - Dawn: `pages/checkout/…/dawn-checkout-shell` with slots `header` | `form` | `footer`
41
+ - Form slot: `type: "cart_order_form"` (multi-item cart). `product_order_form` also works if the cart’s current item is enough.
42
+ - Empty cart: shell shows empty state + link to `/products`.
43
+ - On success, `CartOrderFormWrapper` redirects to `/thanks?order=&name=&phone=&total=`.
44
+
45
+ ## `/thanks`
46
+
47
+ - Route: `app/thanks/page.tsx` → template key **`thank_you`**
48
+ - Query params (all optional):
49
+
50
+ | Param | Meaning |
51
+ |---|---|
52
+ | `order` / `id` | Order id |
53
+ | `name` / `customerName` | Customer name |
54
+ | `total` / `amount` | Display total |
55
+ | `phone` | Phone |
56
+
57
+ - Built-in registry type: `thank_you` (reads those params).
58
+ - Dawn: `dawn-thanks-shell` (`$ref` shared) with slots `header` | `body` | `footer`; body hosts the thank-you JSX (reads those params).
59
+ - After a successful COD submit, redirect e.g. `/thanks?order={id}&name={encodeURIComponent(name)}`.
60
+ - Cart drawer “Check out” must link to **`/checkout`**, not `/cart`.
61
+
62
+ ## `/p/[...id]` and `/products/[productId]`
63
+
64
+ - Routes: `app/p/[...id]/page.tsx` **and** `app/products/[productId]/page.tsx` (shared loader — **both fully work**, no redirect)
65
+ - Loads product by slug; sets current cart item + shipping bootstrap
66
+ - Dawn: `dawn-product-shell` with slots `header` | `body` | `footer`
67
+ - Prefer `/p/` for links (Dawn, OG, schema); `/products/:id` stays as a working alias
68
+
69
+ ## Authoring checklist
70
+
71
+ 1. Add / edit under `pages/<page>/sections/main/…`
72
+ 2. Keep a single root (or a short stack) in `main`; put chrome in slots
73
+ 3. `npm run build`
74
+ 4. Preview with `npm run dev`
75
+ 5. Do **not** reintroduce multi-section keys on these three pages in new themes
76
+
77
+ ## Related
78
+
79
+ - Order / COD contract: [08-ORDER-FORM.md](./08-ORDER-FORM.md)
80
+ - Architecture pages list: [01-ARCHITECTURE.md](./01-ARCHITECTURE.md)
81
+ - Schema library (optional drag-drop blocks): [10-SCHEMA-LIBRARY.md](./10-SCHEMA-LIBRARY.md)
@@ -0,0 +1,164 @@
1
+ # Dark / Light Theme (on top of native Lithium theme)
2
+
3
+ > Theme package copy — paths assume this theme folder is the project root.
4
+
5
+ How Feeef storefront themes support **light**, **dark**, and **system** modes — and how custom / Dawn JSX must ride that stack instead of inventing a parallel palette.
6
+
7
+ ## Mental model (three layers)
8
+
9
+ ```text
10
+ 1. Template default template.data.props.theme.mode → "light" | "dark" | "system"
11
+ 2. Runtime class next-themes → <html class="dark"> (or not)
12
+ 3. CSS tokens :root / .dark variables → hsl(var(--…))
13
+ ```
14
+
15
+ | Layer | Who sets it | Effect |
16
+ |---|---|---|
17
+ | Template `props.theme.mode` | Merchant / theme author | Default mode when the store loads (`StyleWrapper` calls `setTheme`) |
18
+ | `useTheme()` / toggle | Visitor or custom JSX | Overrides to light/dark/system for the session |
19
+ | CSS variables | Lithium + Tailwind theme | Actual colors; **`.dark` swaps surfaces + brand** |
20
+
21
+ Native registry components (order form, navbar, shadcn UI) already follow these tokens. Custom JSX must do the same.
22
+
23
+ ## Sources of truth
24
+
25
+ ## Template default mode
26
+
27
+ In template root props (kit: `props.json` or `template.data.props`):
28
+
29
+ ```json
30
+ {
31
+ "theme": {
32
+ "mode": "light",
33
+ "rounded": 100,
34
+ "font": "expoArabic"
35
+ }
36
+ }
37
+ ```
38
+
39
+ Options: `"light"` | `"dark"` | `"system"`.
40
+
41
+ `StyleWrapper` reads `template.data.props.theme.mode` and, when it is not `"light"`, calls `setTheme(themeMode)` so the store opens in the authored default.
42
+
43
+ ## What happens in dark mode
44
+
45
+ 1. `next-themes` puts `class="dark"` on the document root (`attribute="class"`).
46
+ 2. **Surfaces** flip via `globals.css` `.dark { --background: …; --foreground: …; … }`.
47
+ 3. **Brand** flips via `StyleWrapper`: store `decoration.primary` / `secondary` (and on*) are converted to HSL, then `hslToDark(...)` writes `.dark { --primary: … }` etc.
48
+
49
+ So in custom CSS you never hardcode “the brand hex for dark”. You write:
50
+
51
+ ```css
52
+ color: hsl(var(--foreground));
53
+ background: hsl(var(--background));
54
+ border-color: hsl(var(--border));
55
+ background: hsl(var(--primary));
56
+ color: hsl(var(--primary-foreground));
57
+ ```
58
+
59
+ When the visitor (or template) switches mode, tokens update automatically.
60
+
61
+ ## Token cheat-sheet (prefer these)
62
+
63
+ | Token | Use |
64
+ |---|---|
65
+ | `--background` / `--foreground` | Page / section surfaces and body text |
66
+ | `--card` / `--card-foreground` | Cards, panels |
67
+ | `--muted` / `--muted-foreground` | Secondary text, soft fills |
68
+ | `--border` / `--input` / `--ring` | Borders, inputs, focus |
69
+ | `--primary` / `--primary-foreground` | Brand CTAs (store decoration) |
70
+ | `--secondary` / `--secondary-foreground` | Secondary brand |
71
+ | `--destructive` / `--destructive-foreground` | Errors / danger |
72
+ | `--corners-card`, `--corners-fields`, `--corners-buttons-*` | Radii (mode-independent) |
73
+
74
+ Always wrap color channels with `hsl(...)`:
75
+
76
+ ```jsx
77
+ style={{ background: "hsl(var(--background))", color: "hsl(var(--foreground))" }}
78
+ ```
79
+
80
+ ```css
81
+ .my-block {
82
+ background: hsl(var(--card));
83
+ color: hsl(var(--card-foreground));
84
+ border: 1px solid hsl(var(--border));
85
+ border-radius: var(--corners-card);
86
+ }
87
+ ```
88
+
89
+ Optional softer borders that still track mode:
90
+
91
+ ```css
92
+ border: 1px solid color-mix(in srgb, hsl(var(--foreground)) 12%, transparent);
93
+ ```
94
+
95
+ ## Custom JSX: light/dark toggle
96
+
97
+ `useTheme()` is in the react-live scope (no import):
98
+
99
+ ```jsx
100
+ function App() {
101
+ const { resolvedTheme, setTheme } = useTheme();
102
+ const isDark = resolvedTheme === "dark";
103
+
104
+ return (
105
+ <button
106
+ type="button"
107
+ aria-label="Toggle theme"
108
+ onClick={() => setTheme(isDark ? "light" : "dark")}
109
+ style={{
110
+ color: "hsl(var(--foreground))",
111
+ background: "hsl(var(--muted))",
112
+ borderRadius: "var(--corners-buttons-medium)",
113
+ }}
114
+ >
115
+ {isDark ? "Light" : "Dark"}
116
+ </button>
117
+ );
118
+ }
119
+ ```
120
+
121
+ | Field | Meaning |
122
+ |---|---|
123
+ | `theme` | Stored preference: `light` / `dark` / `system` |
124
+ | `resolvedTheme` | What is actually painted: `light` or `dark` |
125
+ | `setTheme(mode)` | Set preference |
126
+ | `systemTheme` | OS preference when `theme === "system"` |
127
+
128
+ Prefer **`resolvedTheme`** for conditional UI (icons, “is dark?”). Prefer **CSS variables** over branching colors when possible.
129
+
130
+ ## Tailwind / native components
131
+
132
+ Registry / shadcn components use semantic classes (`bg-background`, `text-foreground`, `border-border`, `bg-primary`) which map to the same CSS variables. `dark:` variants work because of the `dark` class on `<html>`.
133
+
134
+ In **custom react-live JSX**, Tailwind `dark:` utilities are unreliable (string CSS / limited class pipeline). Prefer CSS variables or a small `<style>` block that uses `hsl(var(--…))`.
135
+
136
+ ## Anti-patterns
137
+
138
+ | Don’t | Do |
139
+ |---|---|
140
+ | `color: store.decoration.primary` (Flutter int) | `hsl(var(--primary))` |
141
+ | Hardcoded `#fff` / `#000` for body chrome | `hsl(var(--background))` / `hsl(var(--foreground))` |
142
+ | Duplicate “Dawn dark palette” hex tables | Ride Lithium tokens |
143
+ | `if (dark) color = '#eee'` for brand CTAs | Keep `--primary`; only branch when an asset needs it |
144
+ | Assume template mode alone updates mid-session | Use `useTheme().setTheme` for toggles |
145
+ | Put mode under a page/component `props` | Root `template.data.props.theme.mode` |
146
+
147
+ Hardcoded black/white is OK only for **reference craft** accents that intentionally ignore brand (rare). Prefer tokens for anything that should follow the store theme.
148
+
149
+ ## Authoring checklist
150
+
151
+ - [ ] Surfaces and text use `--background` / `--foreground` (or card/muted)
152
+ - [ ] Brand CTAs use `--primary` / `--primary-foreground`
153
+ - [ ] Borders use `--border` or `color-mix` on `--foreground`
154
+ - [ ] Radii use `--corners-*`
155
+ - [ ] Toggle (if any) uses `useTheme()` + `resolvedTheme`
156
+ - [ ] Template default set via `props.theme.mode` when the theme should open dark/system
157
+ - [ ] Preview both modes: set mode in editor / `setTheme('dark')` and hard-refresh `template:dev`
158
+
159
+ ## Related
160
+
161
+ - Custom styling rules: [03-CUSTOM-COMPONENTS.md](./03-CUSTOM-COMPONENTS.md)
162
+ - Scope API (`useTheme`, CSS vars): `docs/03-CUSTOM-COMPONENTS.md`
163
+ - Architecture theme note: [01-ARCHITECTURE.md](./01-ARCHITECTURE.md)
164
+ - Anti-patterns: [05-ANTI-PATTERNS.md](./05-ANTI-PATTERNS.md)