@barefootjs/erb 0.31.7 → 0.31.9

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.
package/dist/vite.js CHANGED
@@ -3025,6 +3025,34 @@ var SVG_ROOT_TAGS = new Set([
3025
3025
  "animateTransform",
3026
3026
  "animateMotion"
3027
3027
  ]);
3028
+ var MATHML_ROOT_TAGS = new Set([
3029
+ "math",
3030
+ "mrow",
3031
+ "mfrac",
3032
+ "msup",
3033
+ "msub",
3034
+ "msubsup",
3035
+ "mn",
3036
+ "mi",
3037
+ "mo",
3038
+ "mtext",
3039
+ "munder",
3040
+ "mover",
3041
+ "munderover",
3042
+ "mtable",
3043
+ "mtr",
3044
+ "mtd",
3045
+ "msqrt",
3046
+ "mroot",
3047
+ "mstyle",
3048
+ "merror",
3049
+ "mpadded",
3050
+ "mphantom",
3051
+ "menclose",
3052
+ "semantics",
3053
+ "annotation",
3054
+ "annotation-xml"
3055
+ ]);
3028
3056
 
3029
3057
  // ../jsx/src/ir-to-client-js/collect-elements.ts
3030
3058
  var EMPTY_RENDER_EXPRS = new Set(["null", "undefined", "false", "''", '""', "``"]);
@@ -125,11 +125,23 @@ module BarefootJS
125
125
  props = _props
126
126
  return '' unless props && !props.empty?
127
127
 
128
+ # Exclude the internal `scope_id` key from the client hydration
129
+ # payload: it is a server-side render detail (already carried by
130
+ # `_scope_id` for bf-s/bf-h/bf-m emission) with zero client runtime
131
+ # consumers -- the only bf-p parser (packages/client/src/runtime/
132
+ # hydrate.ts's parseProps -> runInit) never reads it back out.
133
+ # Filtered here, the single marshal boundary for bf-p, so it's
134
+ # excluded no matter how a `scope_id` key ends up in the props Hash
135
+ # (found via the bf-p semantic-comparison audit against the Hono
136
+ # reference adapter, which never emits it).
137
+ client_props = props.reject { |k, _| k.to_s == 'scope_id' }
138
+ return '' if client_props.empty?
139
+
128
140
  # The JSON must be attribute-escaped: a raw `'` inside a string value
129
141
  # (e.g. a blog paragraph) terminates the single-quoted attribute and
130
142
  # truncates the hydration payload. The browser entity-decodes the
131
143
  # attribute value, so the client's JSON.parse sees the original text.
132
- json = html_escape(backend.encode_json(props))
144
+ json = html_escape(backend.encode_json(client_props))
133
145
  %( bf-p='#{json}')
134
146
  end
135
147
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/erb",
3
- "version": "0.31.7",
3
+ "version": "0.31.9",
4
4
  "description": "ERB (Embedded Ruby) adapter for BarefootJS — compiles IR to .erb templates and ships the Ruby rendering backend; runs under any Rack app (Sinatra, Rails)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "directory": "packages/adapter-erb"
55
55
  },
56
56
  "dependencies": {
57
- "@barefootjs/shared": "0.31.7"
57
+ "@barefootjs/shared": "0.31.9"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@barefootjs/jsx": ">=0.2.0",
@@ -71,9 +71,9 @@
71
71
  },
72
72
  "devDependencies": {
73
73
  "@barefootjs/adapter-tests": "0.1.0",
74
- "@barefootjs/jsx": "0.31.7",
75
- "@barefootjs/vite": "0.31.7",
76
- "@barefootjs/client": "0.31.7",
74
+ "@barefootjs/jsx": "0.31.9",
75
+ "@barefootjs/vite": "0.31.9",
76
+ "@barefootjs/client": "0.31.9",
77
77
  "typescript": "^5.0.0",
78
78
  "vite": "^6.0.0"
79
79
  }
@@ -479,11 +479,15 @@ export function TodoList(props: { initialTodos?: Todo[] }) {
479
479
  // as a thrown "ruby render failed" error, so a `NoMethodError` string
480
480
  // anywhere in a caught error would fail this test outright rather than
481
481
  // reaching these assertions. Post-fix: only the not-done todo (id 2)
482
- // survives the 'active' filter.
483
- expect(html).not.toContain('Eat breakfast')
484
- expect(html).toContain('Write tests')
485
- expect(html).toContain('data-key="2"')
486
- expect(html).not.toContain('data-key="1"')
482
+ // survives the 'active' filter. The bf-p hydration payload legitimately
483
+ // carries the full unfiltered `initialTodos` (both quote styles,
484
+ // mirroring `normalizeHTML`), so the assertions target the rendered
485
+ // list only.
486
+ const rendered = html.replace(/\s*bf-p=(?:"[^"]*"|'[^']*')/g, '')
487
+ expect(rendered).not.toContain('Eat breakfast')
488
+ expect(rendered).toContain('Write tests')
489
+ expect(rendered).toContain('data-key="2"')
490
+ expect(rendered).not.toContain('data-key="1"')
487
491
  })
488
492
  })
489
493
 
@@ -264,8 +264,13 @@ export async function renderErbComponent(options: RenderOptions): Promise<string
264
264
  // value domain is JSON-shaped symbol-keyed Hashes throughout, so
265
265
  // `JSON.parse(..., symbolize_names: true)` on the Ruby side is the
266
266
  // whole marshalling story; no hand-built Ruby literals.
267
- const { obj: rootProps, needsSearchParams } = buildRubyProps(props, ir)
267
+ const { obj: rootProps, needsSearchParams, userProps } = buildRubyProps(props, ir)
268
268
  await Bun.write(resolve(tempDir, 'props.json'), JSON.stringify(rootProps))
269
+ // Separate from `props.json`: the caller-facing subset only (no
270
+ // scope_id / signal / memo bookkeeping) — what `bf._props(...)` below
271
+ // feeds `props_attr`'s bf-p payload, matching what a production
272
+ // Sinatra route handler passes, not this harness's internal vars hash.
273
+ await Bun.write(resolve(tempDir, 'user_props.json'), JSON.stringify(userProps))
269
274
 
270
275
  // Honour `__instanceId` from props for the root scope id so
271
276
  // shared-component fixtures (which pin `<ComponentName>_test`) match
@@ -310,6 +315,16 @@ bf = BarefootJS::Context.new(backend)
310
315
  bf._scope_id(${rootScopeId})
311
316
 
312
317
  props = JSON.parse(File.read(File.join(__dir__, 'props.json')), symbolize_names: true)
318
+ # Mirrors production's \`props_attr\` contract (see barefoot_js.rb and
319
+ # props_attr_test.rb): the caller is expected to seed \`_props\` before
320
+ # rendering the root component, so this harness must call it explicitly.
321
+ #
322
+ # \`user_props.json\` — NOT \`props\` above — is what \`_props\` gets: \`props\`
323
+ # is this harness's internal vars hash (scope_id, signal/memo seed
324
+ # values), which a real Sinatra route handler never has and never passes.
325
+ # Production's bf-p carries neither scope_id nor signal state, only the
326
+ # caller-facing props.
327
+ bf._props(JSON.parse(File.read(File.join(__dir__, 'user_props.json')), symbolize_names: true))
313
328
  ${needsSearchParams ? "# (#1922) Request-scoped searchParams() env signal: bind the reserved\n# `search_params` vars key to an empty-query reader. Only when the\n# component imports `searchParams`.\nprops[:search_params] = bf.search_params('')\n" : ''}
314
329
  ${childRenderers}
315
330
  html = backend.render_named(${rubyStringLiteral(toSnakeCase(componentName))}, bf, props)
@@ -491,12 +506,20 @@ function toSnakeCase(name: string): string {
491
506
 
492
507
  /**
493
508
  * Build the root props object (later JSON-serialised) + whether the
494
- * component imports `searchParams`.
509
+ * component imports `searchParams`, PLUS `userProps` — the exact raw
510
+ * fixture props (no defaults, no null-fill, no signal/memo seeding),
511
+ * mirroring production's route-handler call `bf._props(props)` verbatim.
512
+ * `obj` stays the full internal vars hash the compiled template renders
513
+ * against (local-var-keyed, defaulted, signal/memo-seeded); `userProps`
514
+ * is for `bf._props(...)` (bf-p hydration payload) only — these must NOT
515
+ * be the same object, and `userProps` must NOT be derived from `obj` or
516
+ * from the defaulted stash: production's own bf-p carries only what the
517
+ * caller actually passed in, unmodified.
495
518
  */
496
519
  function buildRubyProps(
497
520
  props: Record<string, unknown> | undefined,
498
521
  ir: ComponentIR,
499
- ): { obj: Record<string, unknown>; needsSearchParams: boolean } {
522
+ ): { obj: Record<string, unknown>; needsSearchParams: boolean; userProps: Record<string, unknown> } {
500
523
  const obj: Record<string, unknown> = {}
501
524
 
502
525
  const explicitScope = typeof props?.__instanceId === 'string' ? props.__instanceId : 'test'
@@ -509,6 +532,10 @@ function buildRubyProps(
509
532
  // (`propName`, e.g. `n` for `{ n: count }`) is honoured, not just the
510
533
  // local template var name (#2524 SSR half). `props` is keyed by the
511
534
  // caller-facing name, exactly what `propName` resolves against.
535
+ //
536
+ // This defaulted/derived stash feeds ONLY `obj` (the template-rendering
537
+ // vars hash) below — never `userProps` (the bf-p payload), which is
538
+ // built separately, straight off the raw `props` argument.
512
539
  const rootSsrDefaults = extractSsrDefaults(ir.metadata) ?? {}
513
540
  const derivedProps = deriveStashFromDefaults(rootSsrDefaults, props ?? {})
514
541
  for (const param of ir.metadata.propsParams) {
@@ -558,6 +585,21 @@ function buildRubyProps(
558
585
  }
559
586
  }
560
587
 
588
+ // `bf._props(...)` payload (bf-p hydration): mirrors production's
589
+ // route-handler call `bf._props(props)` verbatim — the caller's raw
590
+ // props dict, unmodified. No default-filling, no signal/memo seeding,
591
+ // no rest-bag nesting: a real Sinatra/Rack handler never rearranges
592
+ // the dict it received before handing it to `_props`. Excludes
593
+ // internal harness-only keys (`__instanceId` etc.), which are never a
594
+ // real caller-facing prop.
595
+ const userProps: Record<string, unknown> = {}
596
+ if (props) {
597
+ for (const [key, value] of Object.entries(props)) {
598
+ if (key.startsWith('__')) continue
599
+ userProps[key] = value
600
+ }
601
+ }
602
+
561
603
  // Signal values evaluated from props (after user props).
562
604
  for (const signal of ir.metadata.signals) {
563
605
  // Env signals (#2057 / #1922) are bound below via `search_params('')`,
@@ -577,6 +619,6 @@ function buildRubyProps(
577
619
 
578
620
  const needsSearchParams = importsSearchParams(ir.metadata)
579
621
 
580
- return { obj, needsSearchParams }
622
+ return { obj, needsSearchParams, userProps }
581
623
  }
582
624