@barefootjs/jinja 0.31.8 → 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
@@ -3077,6 +3077,34 @@ var SVG_ROOT_TAGS = new Set([
3077
3077
  "animateTransform",
3078
3078
  "animateMotion"
3079
3079
  ]);
3080
+ var MATHML_ROOT_TAGS = new Set([
3081
+ "math",
3082
+ "mrow",
3083
+ "mfrac",
3084
+ "msup",
3085
+ "msub",
3086
+ "msubsup",
3087
+ "mn",
3088
+ "mi",
3089
+ "mo",
3090
+ "mtext",
3091
+ "munder",
3092
+ "mover",
3093
+ "munderover",
3094
+ "mtable",
3095
+ "mtr",
3096
+ "mtd",
3097
+ "msqrt",
3098
+ "mroot",
3099
+ "mstyle",
3100
+ "merror",
3101
+ "mpadded",
3102
+ "mphantom",
3103
+ "menclose",
3104
+ "semantics",
3105
+ "annotation",
3106
+ "annotation-xml"
3107
+ ]);
3080
3108
 
3081
3109
  // ../jsx/src/ir-to-client-js/collect-elements.ts
3082
3110
  var EMPTY_RENDER_EXPRS = new Set(["null", "undefined", "false", "''", '""', "``"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/jinja",
3
- "version": "0.31.8",
3
+ "version": "0.31.9",
4
4
  "description": "Jinja2 adapter for BarefootJS — compiles IR to .jinja templates and ships the Python BarefootJS rendering runtime; runs under any Python web framework (Flask, etc.)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,7 +53,7 @@
53
53
  "directory": "packages/adapter-jinja"
54
54
  },
55
55
  "dependencies": {
56
- "@barefootjs/shared": "0.31.8"
56
+ "@barefootjs/shared": "0.31.9"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@barefootjs/jsx": ">=0.2.0",
@@ -70,9 +70,9 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@barefootjs/adapter-tests": "0.1.0",
73
- "@barefootjs/jsx": "0.31.8",
74
- "@barefootjs/vite": "0.31.8",
75
- "@barefootjs/client": "0.31.8",
73
+ "@barefootjs/jsx": "0.31.9",
74
+ "@barefootjs/vite": "0.31.9",
75
+ "@barefootjs/client": "0.31.9",
76
76
  "typescript": "^5.0.0",
77
77
  "vite": "^6.0.0"
78
78
  }
@@ -612,11 +612,24 @@ class BarefootJS:
612
612
  props = self._props()
613
613
  if not props:
614
614
  return ""
615
+ # Exclude the internal `scope_id` key from the client hydration
616
+ # payload: it is a server-side render detail (already carried by
617
+ # `_scope_id` for bf-s/bf-h/bf-m emission) with zero client runtime
618
+ # consumers -- the only bf-p parser
619
+ # (packages/client/src/runtime/hydrate.ts's parseProps -> runInit)
620
+ # never reads it back out. Filtered here, the single marshal
621
+ # boundary for bf-p, so it's excluded no matter how a `scope_id`
622
+ # key ends up in the props dict (found via the bf-p semantic-
623
+ # comparison audit against the Hono reference adapter, which never
624
+ # emits it).
625
+ client_props = {k: v for k, v in props.items() if str(k) != "scope_id"}
626
+ if not client_props:
627
+ return ""
615
628
  # The JSON must be attribute-escaped: a raw `'` inside a string value
616
629
  # (e.g. a blog paragraph) terminates the single-quoted attribute and
617
630
  # truncates the hydration payload. The browser entity-decodes the
618
631
  # attribute value, so the client's JSON.parse sees the original text.
619
- j = _html_escape(self.backend.encode_json(props))
632
+ j = _html_escape(self.backend.encode_json(client_props))
620
633
  return f" bf-p='{j}'"
621
634
 
622
635
  # -----------------------------------------------------------------
@@ -235,7 +235,7 @@ export async function renderJinjaComponent(options: RenderOptions): Promise<stri
235
235
  }
236
236
 
237
237
  // Build props dict for Python.
238
- const propsPy = buildPythonProps(componentName, props, ir)
238
+ const { propsPy, userPropsPy } = buildPythonProps(componentName, props, ir)
239
239
 
240
240
  // Honour `__instanceId` from props for the root scope id so
241
241
  // shared-component fixtures (which pin `<ComponentName>_test`) match
@@ -262,6 +262,18 @@ bf = BarefootJS(None, {'backend': backend})
262
262
  bf._scope_id(${pyStr(rootScopeIdRaw)})
263
263
 
264
264
  props = ${propsPy}
265
+ # Mirrors production's \`props_attr\` contract (see runtime.py's \`_props\`
266
+ # dual-accessor and its Ruby/Perl siblings): the caller is expected to
267
+ # seed \`_props\` before rendering the root component, so this harness
268
+ # must call it explicitly.
269
+ #
270
+ # \`userPropsPy\` — NOT \`props\` above — is what \`_props\` gets: \`props\` is
271
+ # this harness's internal vars hash (scope_id, signal/memo seed values,
272
+ # and — when the component imports searchParams — a non-JSON-serializable
273
+ # SearchParams reader object), none of which a real Flask route handler
274
+ # has or passes. Production's bf-p carries none of those, only the
275
+ # caller-facing props.
276
+ bf._props(${userPropsPy})
265
277
 
266
278
  ${childRenderers}
267
279
  html = backend.render_named(${pyStr(toSnakeCase(componentName))}, bf, props)
@@ -480,17 +492,26 @@ function toSnakeCase(name: string): string {
480
492
 
481
493
  /**
482
494
  * Build a Python dict literal from props (+ signal / memo seeds).
495
+ *
496
+ * Returns the full internal vars-hash Python literal (`props`, what the
497
+ * compiled template renders against) AND `userPropsPy` — the exact raw
498
+ * fixture props (no defaults, no null-fill, no signal/memo seeding),
499
+ * mirroring production's route-handler call `bf._props(props)` verbatim.
500
+ * `userPropsPy` is for `bf._props(...)` (bf-p hydration payload) only —
501
+ * these must NOT be the same value, and `userPropsPy` must NOT be
502
+ * derived from `entries` or the defaulted stash: production's own bf-p
503
+ * carries only what the caller actually passed in, unmodified.
483
504
  */
484
505
  function buildPythonProps(
485
506
  _componentName: string,
486
507
  props: Record<string, unknown> | undefined,
487
508
  ir: ComponentIR,
488
- ): string {
509
+ ): { propsPy: string; userPropsPy: string } {
510
+ // The full internal vars hash (local-template-var-name keyed, e.g.
511
+ // `count` for `{ n: count }`) that the compiled template renders
512
+ // against.
489
513
  const entries: string[] = []
490
514
 
491
- const explicitScope = typeof props?.__instanceId === 'string' ? props.__instanceId : 'test'
492
- entries.push(`${pyStr('scope_id')}: ${pyStr(explicitScope)}`)
493
-
494
515
  // Prop params with defaults (before signals, so signals can reference them).
495
516
  // Seeded through the shared `deriveStashFromDefaults` (the TS twin of the
496
517
  // production `_derive_stash_from_defaults` this harness ALSO calls at
@@ -555,6 +576,25 @@ function buildPythonProps(
555
576
  }
556
577
  }
557
578
 
579
+ // `bf._props(...)` payload (bf-p hydration): mirrors production's
580
+ // route-handler call `bf._props(props)` verbatim — the caller's raw
581
+ // props dict, unmodified. No default-filling, no signal/memo seeding,
582
+ // no rest-bag nesting: a real Flask route handler never rearranges
583
+ // the dict it received before handing it to `_props`. Excludes
584
+ // internal harness-only keys (`__instanceId` etc.), which are never a
585
+ // real caller-facing prop.
586
+ const userEntries: string[] = []
587
+ if (props) {
588
+ for (const [key, value] of Object.entries(props)) {
589
+ if (key.startsWith('__')) continue
590
+ userEntries.push(`${pyStr(key)}: ${toPyLiteral(value)}`)
591
+ }
592
+ }
593
+ const userPropsPy = `{${userEntries.join(', ')}}`
594
+
595
+ const explicitScope = typeof props?.__instanceId === 'string' ? props.__instanceId : 'test'
596
+ const fullEntries = [...entries, `${pyStr('scope_id')}: ${pyStr(explicitScope)}`]
597
+
558
598
  // Signal values evaluated from props (after user props).
559
599
  for (const signal of ir.metadata.signals) {
560
600
  // Env signals (#2057) are bound below via `SearchParams('')`, not from a
@@ -562,7 +602,7 @@ function buildPythonProps(
562
602
  if (signal.envReader) continue
563
603
  const value = evaluateSignalInit(signal.initialValue.trim(), props)
564
604
  if (value !== null) {
565
- entries.push(`${pyStr(signal.getter)}: ${toPyLiteral(value)}`)
605
+ fullEntries.push(`${pyStr(signal.getter)}: ${toPyLiteral(value)}`)
566
606
  }
567
607
  }
568
608
 
@@ -571,7 +611,7 @@ function buildPythonProps(
571
611
  for (const memo of ir.metadata.memos) {
572
612
  const entry = rootSsrDefaults[memo.name]
573
613
  const value = entry ? entry.value : 0
574
- entries.push(`${pyStr(memo.name)}: ${toPyLiteral(value ?? 0)}`)
614
+ fullEntries.push(`${pyStr(memo.name)}: ${toPyLiteral(value ?? 0)}`)
575
615
  }
576
616
 
577
617
  // (#1922) Request-scoped `searchParams()`: bind `searchParams` to an
@@ -581,10 +621,10 @@ function buildPythonProps(
581
621
  // `.get(k)` resolves to `None` and the author's `?? default` renders. Only
582
622
  // when the component imports `searchParams`.
583
623
  if (importsSearchParams(ir.metadata)) {
584
- entries.push(`${pyStr('searchParams')}: SearchParams('')`)
624
+ fullEntries.push(`${pyStr('searchParams')}: SearchParams('')`)
585
625
  }
586
626
 
587
- return `{${entries.join(', ')}}`
627
+ return { propsPy: `{${fullEntries.join(', ')}}`, userPropsPy }
588
628
  }
589
629
 
590
630