@barefootjs/cli 0.31.5 → 0.31.6

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.
@@ -24,6 +24,8 @@ error[BF001]: 'use client' directive required for components with createSignal
24
24
 
25
25
  ## Directive Errors (BF001–BF003)
26
26
 
27
+ <a id="bf001"></a>
28
+
27
29
  ### BF001 — Missing `"use client"` Directive
28
30
 
29
31
  **Trigger:** Reactive APIs used without `"use client"`.
@@ -46,6 +48,8 @@ import { createSignal } from '@barefootjs/client'
46
48
  export function Counter() { ... }
47
49
  ```
48
50
 
51
+ <a id="bf003"></a>
52
+
49
53
  ### BF003 — Client Component Importing Server Component
50
54
 
51
55
  **Trigger:** Client component imports from a file without `"use client"`.
@@ -56,6 +60,8 @@ export function Counter() { ... }
56
60
 
57
61
  ## Signal Errors (BF011)
58
62
 
63
+ <a id="bf011"></a>
64
+
59
65
  ### BF011 — Module-Level Reactive Declaration
60
66
 
61
67
  **Trigger:** A `createSignal` or `createMemo` call at module scope without a leading `/* @client */` directive.
@@ -100,6 +106,8 @@ export function Counter() {
100
106
 
101
107
  ## JSX Errors (BF021–BF023)
102
108
 
109
+ <a id="bf021"></a>
110
+
103
111
  ### BF021 — Unsupported JSX Pattern
104
112
 
105
113
  **Trigger:** Array method chain before `.map()` cannot compile to SSR template.
@@ -201,6 +209,8 @@ that a component-body local (`const iso = createdAt.toISOString()`) is NOT a
201
209
  workaround: it lowers to a template variable whose value the template
202
210
  backend cannot compute, and dies at render time the same way.
203
211
 
212
+ <a id="bf023"></a>
213
+
204
214
  ### BF023 — Missing Key in List
205
215
 
206
216
  **Trigger:** `.map()` loop without `key` prop.
@@ -219,8 +229,56 @@ backend cannot compute, and dies at render time the same way.
219
229
 
220
230
  ---
221
231
 
232
+ ## Template Adapter Errors (BF101)
233
+
234
+ <a id="bf101"></a>
235
+
236
+ ### BF101 — No Template-Language Lowering
237
+
238
+ **Trigger:** An expression that a JS-runtime adapter (Hono, CSR) executes verbatim has no lowering on a non-JS template adapter (Go, Mojo, Xslate, Twig, ERB, Blade, Jinja, MiniJinja). Two shapes are permanent known limitations rather than subset widenings:
239
+
240
+ **A nested `.some()` / `.find()` inside a filter predicate** ([#2320](https://github.com/piconic-ai/barefootjs/issues/2320)) — `find`-family methods return an element, not a boolean, so degrading them to their receiver would silently change predicate semantics:
241
+
242
+ ```tsx
243
+ // ❌ BF101 on Go/Mojo/Xslate/Twig/ERB/Blade/Jinja/MiniJinja
244
+ {items().filter(t => picked().some(p => p.id === t.id)).map(t => <li key={t.id}>{t.name}</li>)}
245
+ ```
246
+
247
+ **A `.map()` loop array bound to a component-scope `const` with a computed initializer** ([#2321](https://github.com/piconic-ai/barefootjs/issues/2321)) — no template adapter binds an arbitrary computed local, only a prop/param it passes straight through:
248
+
249
+ ```tsx
250
+ // ❌ BF101 on Go/Mojo/Xslate/Twig/ERB/Blade/Jinja/MiniJinja
251
+ function ReactionBar(props: { reactions: Record<string, string[]> }) {
252
+ const entries = Object.entries(props.reactions).filter(([, users]) => users.length > 0)
253
+ return <div>{entries.map(([emoji, users]) => <span key={emoji}>{emoji}</span>)}</div>
254
+ }
255
+ ```
256
+
257
+ **Escapes** — each verified by a conformance twin that compiles clean on the refusing adapter, listed best-SSR-first:
258
+
259
+ - **Pass the computed result as a prop** (`prop-precompute`) — available for the loop-source shape, wherever the array is already computable server-side. **Full server render**: the rendered result is present in the server HTML.
260
+ - **`/* @client */`** (`client-directive`) — available for both shapes, and compiles clean on every adapter. **Client-render**: the region is *empty in server HTML until hydration*. That trade is the cost of the escape, not a bug — the twin fixtures pin the empty region in their own committed `expectedHtml`.
261
+
262
+ ```tsx
263
+ // ✅ Best for the loop-source shape: pass the computed array as a prop
264
+ function ReactionBar({ entries }: { entries: [string, string[]][] }) {
265
+ return <div>{entries.map(([emoji, users]) => <span key={emoji}>{emoji}</span>)}</div>
266
+ }
267
+
268
+ // ✅ Either shape: defer to the client
269
+ {/* @client */ items().filter(t => picked().some(p => p.id === t.id)).map(t => (
270
+ <li key={t.id}>{t.name}</li>
271
+ ))}
272
+ ```
273
+
274
+ See [JSX Compatibility](../rendering/jsx-compatibility.md) for the full worked examples.
275
+
276
+ ---
277
+
222
278
  ## Component Errors (BF043–BF044)
223
279
 
280
+ <a id="bf043"></a>
281
+
224
282
  ### BF043 — Props Destructuring (Warning)
225
283
 
226
284
  **Trigger:** Props destructured in function parameter.
@@ -257,6 +315,8 @@ function Child({ initialCount }: Props) {
257
315
  }
258
316
  ```
259
317
 
318
+ <a id="bf044"></a>
319
+
260
320
  ### BF044 — Signal/Memo Getter Not Called
261
321
 
262
322
  **Trigger:** Signal/memo getter passed without calling it.
@@ -273,6 +333,8 @@ function Child({ initialCount }: Props) {
273
333
  <Child count={count()} />
274
334
  ```
275
335
 
336
+ <a id="bf054"></a>
337
+
276
338
  ### BF054 — Built-in `<Async>` / `<Region>` Used Without Import
277
339
 
278
340
  **Trigger:** A bare `<Async>` or `<Region>` tag is used without importing it
@@ -125,6 +125,7 @@ Some JavaScript expressions cannot be translated into marked template syntax. Wh
125
125
  | JSX-returning `.flatMap()` with a statement body (early `return`, `const` before the projection) | works (runs as JS) | **BF021** |
126
126
  | Nested `.filter()` / `.map()` in a filter predicate (`x => x.tags.filter(...).length > 0`) | works | works |
127
127
  | Nested `.some()` / `.find()` / `.reduce()` in a filter predicate | works | **BF101** |
128
+ | `.map()` loop array bound to a component-scope `const` with a computed initializer (e.g. `Object.entries(props.x).filter(...)`) | works | **BF101** |
128
129
  | Sort comparator that's a multi-statement block body or `localeCompare(b, locale, opts)` | works (runs as JS) | **BF021** |
129
130
  | Sort comparator that's a function reference to an imported/prop identifier, or an alias chain (`const c2 = c1`) | works (runs as JS) | **BF021** |
130
131
  | `typeof` in a filter predicate | works (runs as JS) | **BF021** |
@@ -182,6 +183,39 @@ A nested `.some()` / `.find()` / `.reduce()` still has no faithful Go/Mojo lower
182
183
  {items().filter(x => x.done)}
183
184
  ```
184
185
 
186
+ **Computed loop array (`const` with a runtime initializer):**
187
+
188
+ A `.map()` loop whose array is a bare identifier works when that identifier is a prop or a signal read, but not when it's a component-scope `const` computed from one at render time (`Object.entries(...)`, `.filter(...)`, …) — no template adapter has a binding for an arbitrary computed local, only for a prop/param it can pass straight through:
189
+
190
+ ```tsx
191
+ // ❌ BF101 on Go/Mojo/Xslate/Twig/ERB/Blade/Jinja/MiniJinja; works on Hono
192
+ type Props = { reactions: Record<string, string[]> }
193
+ function ReactionBar(props: Props) {
194
+ const entries = Object.entries(props.reactions).filter(([, users]) => users.length > 0)
195
+ return <div>{entries.map(([emoji, users]) => (
196
+ <span key={emoji}>{emoji}: {String(users.length)}</span>
197
+ ))}</div>
198
+ }
199
+
200
+ // ✅ Best: precompute the array in the parent/route handler and pass it as a prop
201
+ type Entry = [string, string[]]
202
+ function ReactionBarByProp({ entries }: { entries: Entry[] }) {
203
+ return <div>{entries.map(([emoji, users]) => (
204
+ <span key={emoji}>{emoji}: {String(users.length)}</span>
205
+ ))}</div>
206
+ }
207
+
208
+ // ✅ Or defer to the client with /* @client */
209
+ function ReactionBarClientOnly(props: Props) {
210
+ const entries = Object.entries(props.reactions).filter(([, users]) => users.length > 0)
211
+ return <div>{/* @client */ entries.map(([emoji, users]) => (
212
+ <span key={emoji}>{emoji}: {String(users.length)}</span>
213
+ ))}</div>
214
+ }
215
+ ```
216
+
217
+ The prop-passing form is the better fix — it keeps full SSR output, since the template adapters bind a plain prop array directly. `/* @client */` compiles clean too, but renders **nothing** for that region at SSR — no content until hydration/mount runs the loop client-side.
218
+
185
219
  ### Sort comparators that error on Go / Mojo
186
220
 
187
221
  **Unsupported sort comparators** (imperative block bodies, unresolved function references) — a JS-runtime adapter (Hono, CSR) runs any of these verbatim; only non-JS template backends refuse them:
package/dist/index.js CHANGED
@@ -4963,6 +4963,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4963
4963
  case "expression":
4964
4964
  if (node.expr === "null" || node.expr === "undefined") return "";
4965
4965
  if (node.clientOnly && node.slotId) {
4966
+ if (node.markerless) return "";
4966
4967
  return `<!--bf:${node.slotId}--><!--/-->`;
4967
4968
  }
4968
4969
  {
@@ -9328,7 +9329,7 @@ function pickAttrMetaFromIR(src) {
9328
9329
  ...src.freeIdentifiers !== void 0 && { freeIdentifiers: src.freeIdentifiers }
9329
9330
  };
9330
9331
  }
9331
- var SCOPE_FORBIDDEN, REACTIVE_BINDING_KINDS, AttrValueOf;
9332
+ var SCOPE_FORBIDDEN, REACTIVE_BINDING_KINDS, AttrValueOf, ESCAPE_SSR_COST;
9332
9333
  var init_types = __esm({
9333
9334
  "../jsx/src/types.ts"() {
9334
9335
  "use strict";
@@ -9400,6 +9401,14 @@ var init_types = __esm({
9400
9401
  return { kind: "jsx-children", children: children2 };
9401
9402
  }
9402
9403
  };
9404
+ ESCAPE_SSR_COST = {
9405
+ // `/* @client */` — compiles and hydrates correctly, renders nothing at SSR.
9406
+ "client-directive": "client-render",
9407
+ // The refused computation moves to an already-computed prop.
9408
+ "prop-precompute": "none",
9409
+ // The source is restructured into an equivalent in-subset shape.
9410
+ rewrite: "none"
9411
+ };
9403
9412
  }
9404
9413
  });
9405
9414
 
@@ -29240,6 +29249,7 @@ __export(src_exports, {
29240
29249
  BindingScope: () => BindingScope,
29241
29250
  CALLBACK_METHODS: () => CALLBACK_METHODS,
29242
29251
  ENV_SIGNAL_READERS: () => ENV_SIGNAL_READERS,
29252
+ ESCAPE_SSR_COST: () => ESCAPE_SSR_COST,
29243
29253
  ErrorCodes: () => ErrorCodes,
29244
29254
  JsxAdapter: () => JsxAdapter,
29245
29255
  PARSED_EXPR_KINDS: () => PARSED_EXPR_KINDS,
@@ -29430,6 +29440,7 @@ var init_src2 = __esm({
29430
29440
  init_source_map();
29431
29441
  init_combine_client_js();
29432
29442
  init_types();
29443
+ init_types();
29433
29444
  init_css_layer_prefixer();
29434
29445
  init_instrumentation();
29435
29446
  init_errors();
package/dist/tokens.json CHANGED
@@ -49,6 +49,7 @@
49
49
  { "name": "card-foreground", "value": "oklch(0.145 0 0)", "dark": "oklch(0.985 0 0)" },
50
50
  { "name": "primary", "value": "oklch(0.205 0 0)", "dark": "oklch(0.35 0 0)" },
51
51
  { "name": "primary-foreground", "value": "oklch(0.985 0 0)", "dark": "oklch(0.985 0 0)" },
52
+ { "name": "link", "value": "oklch(0.205 0 0)", "dark": "oklch(0.75 0 0)" },
52
53
  { "name": "secondary", "value": "oklch(0.97 0 0)", "dark": "oklch(0.269 0 0)" },
53
54
  { "name": "secondary-foreground", "value": "oklch(0.205 0 0)", "dark": "oklch(0.985 0 0)" },
54
55
  { "name": "muted", "value": "oklch(0.97 0 0)", "dark": "oklch(0.269 0 0)" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/cli",
3
- "version": "0.31.5",
3
+ "version": "0.31.6",
4
4
  "description": "CLI for agent-driven UI component discovery and scaffolding",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,12 +30,12 @@
30
30
  "esbuild": "^0.25.0",
31
31
  "typescript": "^5.0.0",
32
32
  "vite": "^6.0.0",
33
- "@barefootjs/client": "0.31.5",
34
- "@barefootjs/shared": "0.31.5"
33
+ "@barefootjs/client": "0.31.6",
34
+ "@barefootjs/shared": "0.31.6"
35
35
  },
36
36
  "devDependencies": {
37
- "@barefootjs/jsx": "0.31.5",
38
- "@barefootjs/vite": "0.31.5",
37
+ "@barefootjs/jsx": "0.31.6",
38
+ "@barefootjs/vite": "0.31.6",
39
39
  "@happy-dom/global-registrator": "^20.0.11",
40
40
  "@types/node": "^22.0.0",
41
41
  "happy-dom": "^20.0.11"