@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,433 @@
1
+ # Filterator & Product / Category Lists
2
+
3
+ > Theme package copy — paths assume this theme folder is the project root.
4
+
5
+ Canonical contract for **structured queries** and **list APIs** so AI can build creative PLPs, related products, search, carousels, and admin-grade filters.
6
+
7
+ ---
8
+
9
+ ## 1. Mental model
10
+
11
+ Two ways to query products:
12
+
13
+ ```text
14
+ A) Flat query params → ProductFilter methods (q, category_id, price_min, in_stock, …)
15
+ B) Filterator JSON → nested AND/OR tree + ordering + paging (?filterator=<json>)
16
+ ```
17
+
18
+ Categories use **flat params only** (no filterator today).
19
+
20
+ In **custom / Dawn JSX** (react-live):
21
+
22
+ ```jsx
23
+ const ff = useFeeef();
24
+ const storeCtx = useStore();
25
+ const store = storeCtx?.store;
26
+ if (!ff || !store) return null;
27
+
28
+ const res = await ff.products.list({
29
+ params: {
30
+ store_id: store.id,
31
+ limit: 24,
32
+ page: 1,
33
+ // flat filters and/or:
34
+ filterator: JSON.stringify({ filtering: { … }, ordering: […], paging: {…} }),
35
+ },
36
+ });
37
+ const products = res?.data || res || [];
38
+ ```
39
+
40
+ Always pass **`store_id`**. Prefer flat params for simple UIs; use **filterator** for nested OR/AND, `inList`, null checks, multi-field sorts.
41
+
42
+ ---
43
+
44
+ ## 2. Filterator query shape
45
+
46
+ ```ts
47
+ type ProductFilteratorQuery = {
48
+ filtering?: {
49
+ condition: 'and' | 'or'
50
+ filters?: FilteratorFilter[]
51
+ groups?: FilteratorFilteringGroup[] // nested trees
52
+ }
53
+ ordering?: Array<{ field: string; dir: 'asc' | 'desc' }>
54
+ paging?: { limit?: number; offset?: number; cursor?: string }
55
+ }
56
+
57
+ type FilteratorFilter = {
58
+ field: string // camelCase → snake_case DB column
59
+ operation: QueryOperation
60
+ value?: unknown // single-value ops
61
+ values?: unknown[] // inList / notIn
62
+ }
63
+ ```
64
+
65
+ Send as query param:
66
+
67
+ ```
68
+ GET /products?store_id=…&filterator=<URL-encoded JSON string>
69
+ ```
70
+
71
+ Via SDK:
72
+
73
+ ```js
74
+ ff.products.list({
75
+ params: {
76
+ store_id: store.id,
77
+ filterator: JSON.stringify(query),
78
+ },
79
+ })
80
+ ```
81
+
82
+ ### Operators (prefer long names in AI output)
83
+
84
+ | Operation | Aliases | SQL | Notes |
85
+ |---|---|---|---|
86
+ | `equals` | `eq` | `=` | Prefer for exact match |
87
+ | `notEquals` | `neq` | `!=` | |
88
+ | `contains` | — | `LIKE %v%` | Text |
89
+ | `startsWith` | — | `LIKE v%` | |
90
+ | `endsWith` | — | `LIKE %v` | |
91
+ | `greaterThan` | `gt` | `>` | Numbers / dates |
92
+ | `greaterOrEqual` / `greaterThanOrEqual` | `gte` | `>=` | |
93
+ | `lessThan` | `lt` | `<` | |
94
+ | `lessOrEqual` / `lessThanOrEqual` | `lte` | `<=` | |
95
+ | `inList` | `in` | `IN (…)`, use **`values`** | |
96
+ | `notIn` | — | `NOT IN (…)`, use **`values`** | |
97
+ | `isNull` | — | `IS NULL` | No value needed |
98
+ | `isNotNull` | — | `IS NOT NULL` | |
99
+
100
+ **Products filterator** (`ProductFilter.filterator`) accepts short + long aliases.
101
+ **Generic `filterator_apply.ts`** prefers **long** names — when in doubt use `equals`, `greaterThan`, `inList`.
102
+
103
+ ### Field mapping
104
+
105
+ - `categoryId` → `category_id`, `createdAt` → `created_at`, etc. (`string.snakeCase`)
106
+ - Exception: `type` / `productType` → column **`type`** (not `product_type`)
107
+
108
+ Useful product fields: `id`, `slug`, `name`, `sku`, `price`, `cost`, `discount`, `stock`, `sold`, `views`, `likes`, `status`, `type`, `categoryId`, `storeId`, `createdAt`, `updatedAt`, `photoUrl`, …
109
+
110
+ ### Nested AND / OR
111
+
112
+ ```json
113
+ {
114
+ "filtering": {
115
+ "condition": "or",
116
+ "filters": [
117
+ { "field": "name", "operation": "contains", "value": "shirt" }
118
+ ],
119
+ "groups": [
120
+ {
121
+ "condition": "and",
122
+ "filters": [
123
+ { "field": "price", "operation": "greaterThan", "value": 1000 },
124
+ { "field": "sold", "operation": "greaterThan", "value": 10 }
125
+ ]
126
+ }
127
+ ]
128
+ },
129
+ "ordering": [{ "field": "sold", "dir": "desc" }],
130
+ "paging": { "limit": 24, "offset": 0 }
131
+ }
132
+ ```
133
+
134
+ Meaning: *(name contains shirt) **OR** (price > 1000 **AND** sold > 10)*, sorted by sold desc.
135
+
136
+ ---
137
+
138
+ ## 3. Products — flat query params
139
+
140
+ `GET /products` — implemented in `ProductFilter`. camelCase / snake_case aliases exist for most.
141
+
142
+ | Param | Also | Meaning |
143
+ |---|---|---|
144
+ | `store_id` | `storeId`, `store_ids` | **Required** for storefront |
145
+ | `q` | `search` | Search name, slug, id, description, sku |
146
+ | `status` / `statuses` | | `published` / `draft` / `archived` / `deleted`; `!published` excludes |
147
+ | `category_id` | `categoryId` | Category id (preferred) |
148
+ | `category` | | id **or** embedded slug (compat) |
149
+ | `category_slug` | `categorySlug` | Embedded slug only |
150
+ | `price_min` / `price_max` | `priceMin` / `priceMax` | Price range |
151
+ | `stock_min` / `stock_max` | | Stock range |
152
+ | `in_stock` | `inStock` | `true` → stock > 0; `!true` → out |
153
+ | `has_media` / `has_photo` | | Media / cover present |
154
+ | `variant` | `variantPresent` | Has variants |
155
+ | `sku` / `barcode` | | Partial match |
156
+ | `sold_min` | | Min sold (or `true` → sold > 0) |
157
+ | `created_after` / `before` | | ISO dates |
158
+ | `updated_after` / `before` | | |
159
+ | `ids` | `products` | Whitelist ids |
160
+ | `published` | | Exclude archived |
161
+ | `random` | | `ORDER BY RANDOM()` (+ media) |
162
+ | `order_by` | `orderBy` | `field:dir` e.g. `price:desc` |
163
+ | `page` / `limit` | | Pagination (limit often default 20) |
164
+ | `filterator` | | JSON string — §2 |
165
+
166
+ ### SDK helpers (`feeef.js`)
167
+
168
+ ```ts
169
+ ff.products.list({
170
+ storeId, categoryId, status, q,
171
+ minPrice, maxPrice, inStock, // maps to min_price / max_price — prefer params.price_min for backend certainty
172
+ sortBy: 'price' | 'createdAt' | 'sold' | 'views',
173
+ sortOrder: 'asc' | 'desc',
174
+ page, limit,
175
+ params: { /* escape hatch — price_min, filterator, … */ },
176
+ })
177
+
178
+ ff.products.random(12)
179
+ ff.products.find({ id: slug, by: 'slug' })
180
+ ```
181
+
182
+ **Gotcha:** SDK `minPrice` → `min_price`, but Lucid filter methods are `price_min` / `priceMax`. In custom JSX prefer:
183
+
184
+ ```js
185
+ params: { store_id, price_min: 1000, price_max: 5000, in_stock: true }
186
+ ```
187
+
188
+ ### Response
189
+
190
+ ```ts
191
+ { data: ProductEntity[], meta?: { total, currentPage, perPage, … } }
192
+ // or ProductEntity[] depending on client path — always normalize:
193
+ const list = Array.isArray(res) ? res : (res?.data ?? [])
194
+ ```
195
+
196
+ ### ProductEntity (UI-critical)
197
+
198
+ | Field | Type | Notes |
199
+ |---|---|---|
200
+ | `id` / `slug` | string | Links: `/products/${slug \|\| id}` |
201
+ | `name` / `title` | string\|null | |
202
+ | `photoUrl` | string\|null | Cover |
203
+ | `media` | **`string[]`** | URL strings — **not** `{url}` objects |
204
+ | `price` / `discount` / `stock` / `sold` | number | |
205
+ | `categoryId` / `category` | | |
206
+ | `offers` / `variant` / `addons` | | Upsell / COD |
207
+ | `status` | | Guests usually only see published |
208
+ | `body` | | Markdown only |
209
+
210
+ ---
211
+
212
+ ## 4. Categories — list request
213
+
214
+ ```
215
+ GET /categories?store_id=…&parent_id=…&page&limit
216
+ GET /categories/tree?store_id=…
217
+ ```
218
+
219
+ | Param | Meaning |
220
+ |---|---|
221
+ | `store_id` | **Required** |
222
+ | `parent_id` | Parent id; `null` / `'null'` → roots |
223
+ | `q` | Search (SDK) |
224
+ | `page` / `limit` | Pagination (default limit often 50) |
225
+
226
+ **No `filterator` on categories.**
227
+
228
+ SDK:
229
+
230
+ ```js
231
+ ff.categories.list({ storeId: store.id, parentId: null, limit: 50 })
232
+ ff.categories.listRootCategories(store.id)
233
+ ff.categories.listChildren(store.id, parentId)
234
+ ```
235
+
236
+ Entity: `id`, `name`, `photoUrl`, `parentId`, `metadata`, children/parent when tree/show.
237
+
238
+ ---
239
+
240
+ ## 5. Creative recipes (AI playbook)
241
+
242
+ ### A. Products page (PLP) with URL filters
243
+
244
+ ```jsx
245
+ const searchParams = useSearchParams();
246
+ const categoryId = searchParams?.get('category_id') || '';
247
+ const q = searchParams?.get('q') || '';
248
+ const page = Number(searchParams?.get('page') || 1);
249
+
250
+ ff.products.list({
251
+ params: {
252
+ store_id: store.id,
253
+ limit: 24,
254
+ page,
255
+ category_id: categoryId || undefined,
256
+ q: q || undefined,
257
+ search: q || undefined, // dual-send for compat
258
+ in_stock: true, // server-side availability
259
+ price_min: priceFrom || undefined,
260
+ price_max: priceTo || undefined,
261
+ order_by: 'sold:desc',
262
+ },
263
+ });
264
+ ```
265
+
266
+ Prefer **server** `in_stock` / `price_min` / `price_max` over filtering the array in memory (Dawn grid historically did client-side — avoid for large catalogs).
267
+
268
+ ### B. Related products (same category)
269
+
270
+ There is **no** `/related` endpoint. Pattern:
271
+
272
+ ```jsx
273
+ const { product } = useCurrentProduct();
274
+ const cat = product?.categoryId;
275
+ const res = await ff.products.list({
276
+ params: {
277
+ store_id: store.id,
278
+ category_id: cat,
279
+ limit: 8,
280
+ in_stock: true,
281
+ },
282
+ });
283
+ const related = (res?.data || []).filter((p) => p.id !== product.id);
284
+ ```
285
+
286
+ Fallbacks: `ff.products.random(8)` or bestsellers `order_by: 'sold:desc'`.
287
+
288
+ ### C. “Bestsellers under 5000”
289
+
290
+ ```js
291
+ filterator: JSON.stringify({
292
+ filtering: {
293
+ condition: 'and',
294
+ filters: [
295
+ { field: 'price', operation: 'lessOrEqual', value: 5000 },
296
+ { field: 'stock', operation: 'greaterThan', value: 0 },
297
+ { field: 'status', operation: 'equals', value: 'published' },
298
+ ],
299
+ },
300
+ ordering: [{ field: 'sold', dir: 'desc' }],
301
+ paging: { limit: 12 },
302
+ })
303
+ ```
304
+
305
+ ### D. Multi-category spotlight (`inList`)
306
+
307
+ ```js
308
+ {
309
+ field: 'categoryId',
310
+ operation: 'inList',
311
+ values: [idA, idB, idC],
312
+ }
313
+ ```
314
+
315
+ ### E. New arrivals with media
316
+
317
+ ```js
318
+ params: {
319
+ store_id: store.id,
320
+ has_media: true,
321
+ order_by: 'created_at:desc',
322
+ limit: 12,
323
+ }
324
+ ```
325
+
326
+ ### F. Category nav + product grid
327
+
328
+ 1. `ff.categories.listRootCategories(store.id)` → chips
329
+ 2. On select → set `?category_id=` via `router.push` / `useSearchParams`
330
+ 3. Reload products with `category_id`
331
+
332
+ ### G. Search as you type
333
+
334
+ Debounce `q` (≥300ms), keep `limit` modest (12–24), show loading skeleton. Send both `q` and `search`.
335
+
336
+ ### H. Infinite scroll
337
+
338
+ Use `page` + `meta.lastPage` / `meta.total`, or TanStack `useInfiniteProductsQuery` in native TSX. In react-live, track `page` in `React.useState` and append `data`.
339
+
340
+ ---
341
+
342
+ ## 6. Custom JSX rules
343
+
344
+ | Do | Don’t |
345
+ |---|---|
346
+ | Null-check `ff` / `store` | Assume hooks always return data |
347
+ | `store_id: store.id` on every list | Omit store scope |
348
+ | `product.media` as `string[]` | `product.media[0].url` |
349
+ | `RouterNav` / `Link` to `/p/${slug}` | Hardcode `/products/${slug}` for PDP (alias only; prefer `/p/`) |
350
+ | Server filters (`in_stock`, `price_min`, filterator) | Fetch 200 and filter in JS for PLP |
351
+ | `React.useEffect` for fetches + cancel/ignore flag | Fire list on every render without deps |
352
+
353
+ Fetch skeleton:
354
+
355
+ ```jsx
356
+ function App() {
357
+ const ff = useFeeef();
358
+ const storeCtx = useStore();
359
+ const store = storeCtx?.store;
360
+ const [items, setItems] = React.useState([]);
361
+ const [loading, setLoading] = React.useState(true);
362
+
363
+ React.useEffect(() => {
364
+ let alive = true;
365
+ if (!ff || !store?.id) { setLoading(false); return; }
366
+ setLoading(true);
367
+ ff.products.list({
368
+ params: { store_id: store.id, limit: 12, in_stock: true, order_by: 'sold:desc' },
369
+ }).then((res) => {
370
+ if (!alive) return;
371
+ setItems(Array.isArray(res) ? res : (res?.data ?? []));
372
+ setLoading(false);
373
+ }).catch(() => { if (alive) setLoading(false); });
374
+ return () => { alive = false; };
375
+ }, [ff, store?.id]);
376
+
377
+ if (loading) return <div>…</div>;
378
+ return (
379
+ <div>
380
+ {items.map((p) => (
381
+ <RouterNav key={p.id} href={'/products/' + (p.slug || p.id)}>
382
+ <img src={p.photoUrl || (p.media && p.media[0]) || ''} alt={p.name || ''} />
383
+ <span>{p.name}</span>
384
+ </RouterNav>
385
+ ))}
386
+ </div>
387
+ );
388
+ }
389
+ ```
390
+
391
+ ---
392
+
393
+ ## 7. Guest visibility & security
394
+
395
+ - Unauthenticated storefront typically **hides archived** products.
396
+ - Never expose owner-only draft tooling in public custom JSX.
397
+ - Filterator is a query language, not auth — still scoped by `store_id` and backend policies.
398
+
399
+ ---
400
+
401
+ ## 8. Anti-patterns
402
+
403
+ | Anti-pattern | Fix |
404
+ |---|---|
405
+ | Client-only availability/price on full catalog | `in_stock`, `price_min`/`price_max`, or filterator |
406
+ | `min_price` from SDK without verifying | Use `params.price_min` |
407
+ | Related = copy Shopify recommendations API | Same-category list / random / sold |
408
+ | `product.media[i].url` | `product.media[i]` string |
409
+ | Missing `store_id` | Always set |
410
+ | Huge `limit` (500+) for decorative carousels | Cap 8–24; use random/bestsellers |
411
+ | Short ops only on shared apply helper | Prefer long op names |
412
+
413
+ ---
414
+
415
+ ## 9. Quick reference — when to use what
416
+
417
+ | Goal | Tool |
418
+ |---|---|
419
+ | Simple category + search PLP | Flat `category_id` + `q` + `page`/`limit` |
420
+ | In stock / price band | `in_stock`, `price_min`, `price_max` |
421
+ | Nested logic / multi-id / null checks | **filterator** |
422
+ | Related on PDP | Same `category_id`, exclude current id |
423
+ | Surprise grid | `random=true` or `ff.products.random(n)` |
424
+ | Category menu | `ff.categories.listRootCategories` / tree |
425
+ | Featured / offers | List then `p.offers?.length` **or** filterator on known fields |
426
+
427
+ ---
428
+
429
+ ## Related
430
+
431
+ - Scope API: `docs/03-CUSTOM-COMPONENTS.md`
432
+ - Product PDP pages: [11-PAGES-CHECKOUT-THANKS-PRODUCT.md](./11-PAGES-CHECKOUT-THANKS-PRODUCT.md)
433
+ - Theme porting PLP notes: [09-THEME-PORTING.md](./09-THEME-PORTING.md)
@@ -0,0 +1,73 @@
1
+ # Authoring with TypeScript & imports
2
+
3
+ > Theme package copy — paths assume this theme folder is the project root.
4
+
5
+ Theme components can use **TypeScript** and **ESM imports**. At `feeef template build`
6
+ (or `npm run build`), the kit compiles them into a react-live `code` string that still
7
+ exposes top-level `function App()` — the same contract as plain JSX.
8
+
9
+ ## What you write
10
+
11
+ ```
12
+ # Preferred (page-level → sections.main)
13
+ pages/home/components/hero.tsx
14
+
15
+ # Folder when you need colocated helpers or slots/children
16
+ pages/home/components/hero/
17
+ component.tsx
18
+ labels.ts
19
+
20
+ # Legacy multi-section (still supported)
21
+ pages/home/sections/main/components/hero.tsx
22
+ ```
23
+
24
+ ```tsx
25
+ import clsx from "clsx";
26
+ import { bannerLabel } from "./labels";
27
+
28
+ function App(): JSX.Element {
29
+ const text: string = bannerLabel("Feeef");
30
+ return <div className={clsx("hero")}>{text}</div>;
31
+ }
32
+ ```
33
+
34
+ ## What the storefront receives
35
+
36
+ Build emits bundled / transpiled JS with:
37
+
38
+ - No top-level `import` / `export` (react-live cannot resolve modules)
39
+ - Top-level `function App()` (wrapper around the bundled module when imports exist)
40
+ - `react` / `react-dom` **external** — CustomLive injects `React` in scope
41
+
42
+ Do **not** `import React from "react"` for JSX; use the live scope (or rely on the
43
+ classic `React.createElement` transform from the bundler).
44
+
45
+ ## npm dependencies
46
+
47
+ 1. Theme folder is a Node package (`package.json` from `feeef template init`).
48
+ 2. Install deps at the **theme root**:
49
+
50
+ ```bash
51
+ cd my-theme
52
+ npm install clsx # example
53
+ ```
54
+
55
+ 3. Import normally in the component file. The compiler walks up to `feeef.template.json`
56
+ and resolves from that theme’s `node_modules`.
57
+
58
+ `react` stays provided by the storefront — add it only if you need types
59
+ (`npm i -D @types/react`), not as a runtime bundle target.
60
+
61
+ ## Plain JSX (legacy)
62
+
63
+ `component.jsx` with **no** imports is copied into `code` unchanged (fast path).
64
+
65
+ ## Init
66
+
67
+ This package came from `feeef template init`. Re-read `AGENTS.md` after upgrading the CLI.
68
+
69
+ ## Do not
70
+
71
+ - Expect browser-side bundling of theme `node_modules` — only build-time inlining
72
+ - Bundle `react` into component `code`
73
+ - Rely on Node builtins or server-only packages inside components
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://feeef.org/schemas/template.schema.json",
3
+ "name": "my-theme",
4
+ "version": "0.1.0",
5
+ "defaultLocale": "en",
6
+ "pages": ["home"]
7
+ }
@@ -0,0 +1,4 @@
1
+ # Theme locales
2
+
3
+ One JSON file per language. Built into `data.i18n` / uploaded to `store_template_locales`.
4
+ Use `t('home.heroHeading')` in component JSX.
@@ -0,0 +1,10 @@
1
+ {
2
+ "home": {
3
+ "heroHeading": "مرحبا",
4
+ "heroText": "ثيم Feeef الخاص بك",
5
+ "shopNow": "تسوق الآن"
6
+ },
7
+ "common": {
8
+ "loading": "جاري التحميل…"
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "home": {
3
+ "heroHeading": "Welcome",
4
+ "heroText": "Your Feeef theme",
5
+ "shopNow": "Shop now"
6
+ },
7
+ "common": {
8
+ "loading": "Loading…"
9
+ }
10
+ }