@barefootjs/cli 0.9.1 → 0.9.3
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/index.js +23 -5
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -132,10 +132,11 @@ function commandsFor(pm) {
|
|
|
132
132
|
// package.json scripts surface as Deno tasks, so `deno task
|
|
133
133
|
// <script>` mirrors `bun run <script>` / `pnpm <script>`.
|
|
134
134
|
run: (s) => `deno task ${s}`,
|
|
135
|
-
// One-shot binaries run straight from npm via
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
135
|
+
// One-shot binaries run straight from npm via `deno x` (Deno
|
|
136
|
+
// 2.6+, the `npx` equivalent) with the `npm:` specifier. `deno x`
|
|
137
|
+
// defaults to `--allow-all`, so no explicit `-A` is needed for
|
|
138
|
+
// the filesystem/network access the CLI uses to write components.
|
|
139
|
+
exec: (c) => `deno x npm:${c}`,
|
|
139
140
|
// The scaffold's `test` task forwards extra args without a `--`
|
|
140
141
|
// separator (like bun/pnpm/yarn), so the targeted form just
|
|
141
142
|
// appends the path.
|
|
@@ -5189,6 +5190,22 @@ var init_types = __esm({
|
|
|
5189
5190
|
|
|
5190
5191
|
// ../jsx/src/expression-parser.ts
|
|
5191
5192
|
import ts8 from "typescript";
|
|
5193
|
+
function extractArrowBodyExpression(source) {
|
|
5194
|
+
const sf = ts8.createSourceFile(
|
|
5195
|
+
"__arrow__.ts",
|
|
5196
|
+
source,
|
|
5197
|
+
ts8.ScriptTarget.Latest,
|
|
5198
|
+
/* setParentNodes */
|
|
5199
|
+
true
|
|
5200
|
+
);
|
|
5201
|
+
const stmt = sf.statements[0];
|
|
5202
|
+
if (!stmt || !ts8.isExpressionStatement(stmt) || sf.statements.length !== 1) return null;
|
|
5203
|
+
let expr = stmt.expression;
|
|
5204
|
+
while (ts8.isParenthesizedExpression(expr)) expr = expr.expression;
|
|
5205
|
+
if (!ts8.isArrowFunction(expr)) return null;
|
|
5206
|
+
if (ts8.isBlock(expr.body)) return null;
|
|
5207
|
+
return expr.body.getText(sf).trim();
|
|
5208
|
+
}
|
|
5192
5209
|
function parseExpression(expr) {
|
|
5193
5210
|
const trimmed = expr.trim();
|
|
5194
5211
|
if (!trimmed) {
|
|
@@ -19681,6 +19698,7 @@ __export(src_exports, {
|
|
|
19681
19698
|
enableCompilerInstrumentation: () => enableCompilerInstrumentation,
|
|
19682
19699
|
evalStringArrayJoin: () => evalStringArrayJoin,
|
|
19683
19700
|
exprToString: () => exprToString,
|
|
19701
|
+
extractArrowBodyExpression: () => extractArrowBodyExpression,
|
|
19684
19702
|
extractFunctionParams: () => extractFunctionParams,
|
|
19685
19703
|
extractSsrDefaults: () => extractSsrDefaults,
|
|
19686
19704
|
findReachableNames: () => findReachableNames,
|
|
@@ -23546,7 +23564,7 @@ func StreamingFuncMap() template.FuncMap {
|
|
|
23546
23564
|
}
|
|
23547
23565
|
`;
|
|
23548
23566
|
bfdevGoSource = '// Package bfdev provides a dev-only browser auto-reload handler.\n//\n// It watches `<distDir>/.dev/build-id` (produced by `bf build --watch`\n// in the @barefootjs/cli package) and streams SSE `event: reload` whenever\n// the sentinel changes. Combined with the inline client snippet returned by\n// Snippet, editing a .tsx component triggers a browser reload automatically.\n//\n// The handler is framework-agnostic (net/http.Handler). Echo users can mount\n// it via echo.WrapHandler; other routers use it as-is.\n//\n// Example (Echo):\n//\n// if bfdev.IsDevDefault() {\n// e.GET("/_bf/reload", echo.WrapHandler(bfdev.NewReloadHandler(bfdev.Config{\n// DistDir: "./dist",\n// })))\n// }\n//\n// Example (net/http):\n//\n// http.Handle("/_bf/reload", bfdev.NewReloadHandler(bfdev.Config{DistDir: "./dist"}))\npackage bfdev\n\nimport (\n "fmt"\n "html/template"\n "net/http"\n "os"\n "path/filepath"\n "strings"\n "time"\n)\n\n// Sentinel path contract with `@barefootjs/cli`\n// (`packages/cli/src/lib/build.ts`, DEV_SENTINEL_SUBDIR / DEV_SENTINEL_FILENAME).\n// Duplicated here so the Go runtime avoids a dependency on the CLI. If the\n// CLI changes these values, update this package in the same PR.\nconst (\n devSubdir = ".dev"\n buildIDFile = "build-id"\n scrollStorageKey = "__bf_devreload_scroll"\n\n // heartbeatInterval keeps the SSE stream under the framework\'s idle\n // timeout (Bun.serve defaults to 10s; Go/Echo defaults are more forgiving\n // but middleware-level timeouts exist in the wild). 5s leaves comfortable\n // headroom.\n heartbeatInterval = 5 * time.Second\n\n // pollInterval is how often the handler checks `.dev/build-id`. Uses\n // polling instead of fsnotify to keep the runtime dependency-free \u2014 dev\n // latency of ~500ms is imperceptible next to the browser\'s reload time.\n pollInterval = 500 * time.Millisecond\n)\n\n// Config configures a dev reload handler or snippet.\ntype Config struct {\n // DistDir is the directory that `bf build` writes output into\n // (contains `.dev/build-id`). Required for the handler; ignored by\n // Snippet.\n DistDir string\n\n // Endpoint is the public SSE URL the client will connect to. Used only by\n // Snippet to populate the EventSource URL. Defaults to "/_bf/reload" when\n // empty.\n Endpoint string\n\n // Disabled, when true, makes NewReloadHandler return a 404 handler and\n // Snippet return an empty fragment. Intended for production builds.\n Disabled bool\n}\n\n// IsDevDefault reports whether the process is running in a development\n// environment using the common Go convention of APP_ENV=development.\n// Callers can use this to populate Config.Disabled:\n//\n// cfg := bfdev.Config{DistDir: "./dist", Disabled: !bfdev.IsDevDefault()}\nfunc IsDevDefault() bool {\n return os.Getenv("APP_ENV") == "development"\n}\n\n// NewReloadHandler returns an http.Handler that streams Server-Sent Events\n// and emits `event: reload` whenever `<DistDir>/.dev/build-id` changes. When\n// cfg.Disabled is true, the handler responds 404 and never opens a stream.\nfunc NewReloadHandler(cfg Config) http.Handler {\n if cfg.Disabled {\n return http.HandlerFunc(http.NotFound)\n }\n devDir := filepath.Join(cfg.DistDir, devSubdir)\n buildIDPath := filepath.Join(devDir, buildIDFile)\n // Ensure the directory exists so the first read does not race with the\n // initial build. Ignore the error: subsequent reads simply return "".\n _ = os.MkdirAll(devDir, 0o755)\n\n return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n flusher, ok := w.(http.Flusher)\n if !ok {\n http.Error(w, "streaming unsupported", http.StatusInternalServerError)\n return\n }\n h := w.Header()\n h.Set("Content-Type", "text/event-stream")\n h.Set("Cache-Control", "no-cache, no-transform")\n h.Set("Connection", "keep-alive")\n h.Set("X-Accel-Buffering", "no")\n\n send := func(chunk string) bool {\n if _, err := fmt.Fprint(w, chunk); err != nil {\n return false\n }\n flusher.Flush()\n return true\n }\n\n if !send("retry: 1000\\n\\n") {\n return\n }\n\n lastEventID := strings.TrimSpace(r.Header.Get("Last-Event-ID"))\n initialID := readBuildID(buildIDPath)\n lastSent := ""\n if initialID != "" {\n lastSent = initialID\n // When the client reconnects with a stale Last-Event-ID, a build\n // happened during its disconnected window \u2014 fire `reload`\n // immediately so the missed rebuild does not silently stay\n // unpainted until the next change.\n event := "hello"\n if lastEventID != "" && lastEventID != initialID {\n event = "reload"\n }\n if !send(fmt.Sprintf("event: %s\\nid: %s\\ndata: %s\\n\\n", event, initialID, initialID)) {\n return\n }\n }\n\n ctx := r.Context()\n hbTicker := time.NewTicker(heartbeatInterval)\n defer hbTicker.Stop()\n pollTicker := time.NewTicker(pollInterval)\n defer pollTicker.Stop()\n\n for {\n select {\n case <-ctx.Done():\n return\n case <-hbTicker.C:\n if !send(": hb\\n\\n") {\n return\n }\n case <-pollTicker.C:\n id := readBuildID(buildIDPath)\n if id == "" || id == lastSent {\n continue\n }\n lastSent = id\n if !send(fmt.Sprintf("event: reload\\nid: %s\\ndata: %s\\n\\n", id, id)) {\n return\n }\n }\n }\n })\n}\n\nfunc readBuildID(path string) string {\n b, err := os.ReadFile(path)\n if err != nil {\n return ""\n }\n return strings.TrimSpace(string(b))\n}\n\n// Snippet returns an inline <script> that subscribes to the SSE endpoint,\n// reloads on `reload`, and preserves window.scrollY across reloads via\n// sessionStorage. Returns an empty fragment when cfg.Disabled is true.\n//\n// Place it before </body>, typically just after the rendered scripts.\nfunc Snippet(cfg Config) template.HTML {\n if cfg.Disabled {\n return ""\n }\n endpoint := cfg.Endpoint\n if endpoint == "" {\n endpoint = "/_bf/reload"\n }\n // Small IIFE: EventSource subscriber + scrollY preservation. Idempotent\n // across duplicate mounts (guarded by window.__bfDevReload).\n js := fmt.Sprintf(\n `(function(){if(window.__bfDevReload)return;window.__bfDevReload=1;`+\n `try{var s=sessionStorage.getItem(%q);if(s){sessionStorage.removeItem(%q);`+\n `var y=parseInt(s,10);if(!isNaN(y)){var restore=function(){window.scrollTo(0,y)};`+\n `if(document.readyState===\'loading\'){addEventListener(\'DOMContentLoaded\',restore,{once:true})}else{restore()}}}}catch(e){}`+\n `var es=new EventSource(%q);`+\n `es.addEventListener(\'reload\',function(){try{sessionStorage.setItem(%q,String(window.scrollY))}catch(e){}location.reload()});`+\n `es.addEventListener(\'error\',function(){})})();`,\n scrollStorageKey, scrollStorageKey, endpoint, scrollStorageKey,\n )\n // Safe: `js` is assembled from package-internal literals plus `endpoint`\n // escaped by %q (Go-syntax quoting == valid JS string literal for the\n // ASCII endpoint paths this accepts).\n return template.HTML("<script>" + js + "</script>") //nolint:gosec\n}\n';
|
|
23549
|
-
barefootPmSource = "package BarefootJS;\nour $VERSION = \"0.8.0\";\nuse strict;\nuse warnings;\nuse utf8;\nuse feature 'signatures';\nno warnings 'experimental::signatures';\n\nuse POSIX ();\nuse Scalar::Util qw(looks_like_number weaken);\n\n# NOTE: This runtime is template-engine-agnostic AND framework-agnostic by\n# design, so it can ship as a standalone CPAN distribution. It depends only on\n# core Perl (subroutine signatures + the hand-rolled minimal accessor base\n# below \u2014 no Mojo::Base, no Class::Tiny). Every operation that depends on *how*\n# a template is rendered \u2014 JSON marshalling, raw-string marking, JSX-children\n# materialisation, and named-template rendering \u2014 is delegated to a pluggable\n# `backend` (see BarefootJS::Backend::Mojo for the reference Mojolicious\n# implementation), which is the only component that pulls in the Mojo\n# distribution, and only when it is actually used.\n\n# ---------------------------------------------------------------------------\n# Minimal accessor base (no Mojo::Base / Class::Tiny dependency)\n# ---------------------------------------------------------------------------\n#\n# Generates read/write accessors with optional lazy defaults so the runtime\n# stays free of any non-core OO base. Semantics mirror the Mojo::Base `has`\n# this class used to inherit: a getter returns the stored value (building it\n# from the default on first access if unset); a setter stores the value and\n# returns $self for chaining. A default is either a plain scalar or a coderef\n# invoked as `$default->($self)` (for per-instance refs like `[]` / `{}` and\n# the lazily-required Mojo backend).\nmy %ATTR_DEFAULT = (\n _scripts => sub { [] },\n _script_seen => sub { {} },\n _child_renderers => sub { {} },\n _is_child => 0,\n # Lazily fall back to the Mojo reference backend so a bare-blessed\n # instance (the pure-function unit tests) and the historical\n # `BarefootJS->new($c, ...)` callers keep working unchanged. A non-Mojo\n # host injects its own backend via `BarefootJS->new($c, { backend => $b })`\n # and never triggers this require \u2014 keeping the core load Mojo-free.\n backend => sub {\n require BarefootJS::Backend::Mojo;\n return BarefootJS::Backend::Mojo->new;\n },\n);\n\n# c \u2014 Mojolicious controller (kept for back-compat accessors)\n# config \u2014 plugin / instance config\n# backend \u2014 the template-engine seam (#engine-abstraction)\n# _scope_id \u2014 addressable scope id\n# _bf_parent / _bf_mount \u2014 slot identity when this scope is slot-attached\n# _props \u2014 props serialised into bf-p / the scope comment\nfor my $attr (qw(\n c config backend\n _scripts _script_seen _scope_id _is_child _bf_parent _bf_mount _props\n _child_renderers\n)) {\n no strict 'refs';\n *{\"BarefootJS::$attr\"} = sub {\n my $self = shift;\n if (@_) { $self->{$attr} = shift; return $self; }\n if (!exists $self->{$attr} && exists $ATTR_DEFAULT{$attr}) {\n my $d = $ATTR_DEFAULT{$attr};\n $self->{$attr} = ref($d) eq 'CODE' ? $d->($self) : $d;\n }\n return $self->{$attr};\n };\n}\n\nsub new ($class, $c, $config = {}) {\n # Build (or accept an injected) rendering backend. The default Mojo\n # backend wraps the controller and honours an optional `json_encoder`\n # override so a host can swap in a faster XS JSON implementation\n # without subclassing. A caller targeting another template engine\n # passes its own backend via `$config->{backend}`.\n my $backend = $config->{backend};\n unless ($backend) {\n require BarefootJS::Backend::Mojo;\n $backend = BarefootJS::Backend::Mojo->new(\n c => $c,\n ($config->{json_encoder}\n ? (json_encoder => $config->{json_encoder})\n : ()),\n );\n }\n my $self = bless {\n c => $c,\n config => $config,\n backend => $backend,\n }, $class;\n # Hold the controller weakly. Mojolicious stashes this bf instance under\n # `$c->stash->{'bf.instance'}`, so a strong bf -> controller back-reference\n # closes a per-request cycle ($c -> stash -> bf -> $c) that Perl's\n # refcount GC cannot reclaim, leaking one controller + bf + child-renderer\n # closures per request. The controller owns (outlives) the per-request bf,\n # so the weak ref stays valid for the whole render. Callers that need the\n # controller to outlive the bf instance independently must keep their own\n # strong reference (the normal Mojo request scope already does).\n weaken($self->{c}) if defined $c;\n return $self;\n}\n\n# ---------------------------------------------------------------------------\n# Scope & Props\n# ---------------------------------------------------------------------------\n\nsub scope_attr ($self) {\n # bf-s is the addressable scope id only (#1249).\n return $self->_scope_id // '';\n}\n\n# Emits `bf-h=\"<host>\" bf-m=\"<slot>\" bf-r=\"\"` conditionally.\n# See spec/compiler.md \"Slot identity\".\nsub hydration_attrs ($self) {\n my @parts;\n my $host = $self->_bf_parent;\n my $mount = $self->_bf_mount;\n if (defined $host && length $host) {\n my $h = $host =~ s/\"/"/gr;\n push @parts, qq{bf-h=\"$h\"};\n }\n if (defined $mount && length $mount) {\n my $m = $mount =~ s/\"/"/gr;\n push @parts, qq{bf-m=\"$m\"};\n }\n unless ($self->_is_child) {\n push @parts, q{bf-r=\"\"};\n }\n return join(' ', @parts);\n}\n\nsub props_attr ($self) {\n my $props = $self->_props;\n return '' unless $props && %$props;\n # encode_json returns a character string (not bytes) for safe embedding\n # in templates (the Mojo backend uses Mojo::JSON::to_json).\n my $json = $self->backend->encode_json($props);\n return qq{ bf-p='$json'};\n}\n\n# ---------------------------------------------------------------------------\n# Comment Markers\n# ---------------------------------------------------------------------------\n\nsub comment ($self, $text) {\n return \"<!--bf-$text-->\";\n}\n\n# ---------------------------------------------------------------------------\n# JS-equivalent value stringification\n# ---------------------------------------------------------------------------\n\n# Map a Perl boolean-shaped value to the JS `String(bool)` form.\n# Used by the Mojo adapter when emitting reactive attribute bindings\n# whose JS source `isBooleanResultExpr` classified as boolean \u2014\n# a comparison (`count() > 0`), a logical negation (`!ok()`), or a\n# literal `true` / `false`. Perl's auto-stringification of those\n# expressions yields `''` / `1`; Hono and Go emit `'false'` / `'true'`.\n# Centralising the bool \u2192 string mapping here keeps the contract\n# testable and the template-emit syntax tidy\n# (`<%= bf->bool_str(...) %>` vs an inline ternary).\n#\n# Contract is boolean-only: callers must have classified the\n# expression as boolean-result before routing through this helper.\n# Non-boolean values reaching here will be Perl-truthy-coerced to\n# 'true' / 'false', which is generally wrong \u2014 non-boolean attribute\n# bindings stay on the plain `<%= expr %>` emit path and never reach\n# this function.\nsub bool_str ($self, $value) {\n return $value ? 'true' : 'false';\n}\n\nsub text_start ($self, $slot_id) {\n return \"<!--bf:$slot_id-->\";\n}\n\nsub text_end ($self) {\n return \"<!--/-->\";\n}\n\n# See spec/compiler.md \"Slot identity\" for the comment-scope wire format.\nsub scope_comment ($self) {\n my $scope_id = $self->_scope_id // '';\n my $host_segment = '';\n my $host = $self->_bf_parent;\n my $mount = $self->_bf_mount;\n if (defined $host && length $host) {\n $host_segment = \"|h=$host|m=\" . ($mount // '');\n }\n my $props_json = '';\n if ($self->_props && %{$self->_props}) {\n $props_json = '|' . $self->backend->encode_json($self->_props);\n }\n return \"<!--bf-scope:$scope_id$host_segment$props_json-->\";\n}\n\n# ---------------------------------------------------------------------------\n# Script Registration\n# ---------------------------------------------------------------------------\n\nsub register_script ($self, $path) {\n return if $self->_script_seen->{$path};\n $self->_script_seen->{$path} = 1;\n push @{$self->_scripts}, $path;\n}\n\n# ---------------------------------------------------------------------------\n# Child Component Rendering\n# ---------------------------------------------------------------------------\n# (`_child_renderers` accessor is generated by the minimal accessor base above.)\n\nsub register_child_renderer ($self, $name, $renderer) {\n $self->_child_renderers->{$name} = $renderer;\n}\n\nsub render_child ($self, $name, @args) {\n my $renderer = $self->_child_renderers->{$name};\n die \"No renderer registered for child component '$name'\" unless $renderer;\n # Accept both the Mojo list form \u2014 `bf->render_child($name, k => v, ...)`\n # \u2014 and the single-hashref form \u2014 `$bf.render_child($name, { k => v })`.\n # Template languages whose method calls can't splat a hash into positional\n # args (Text::Xslate Kolon, Template Toolkit) pass one hashref instead.\n my %props = (@args == 1 && ref $args[0] eq 'HASH') ? %{ $args[0] } : @args;\n # JSX children come in via the engine's children-capture mechanism\n # (Mojo's `begin %>...<% end`, which produces a CODE ref returning a\n # Mojo::ByteStream). Materialize it through the backend before handing\n # the props to the child renderer so the child template sees\n # `$children` as already-rendered HTML. Guard on `exists` so a\n # childless invocation (`bf->render_child('counter')`) doesn't gain a\n # spurious `children => undef` key \u2014 preserving the historical \"only\n # touch children when present\" behaviour.\n $props{children} = $self->backend->materialize($props{children})\n if exists $props{children};\n return $renderer->(\\%props);\n}\n\n# ---------------------------------------------------------------------------\n# Bulk registration from build manifest\n# ---------------------------------------------------------------------------\n#\n# `bf build` emits dist/templates/manifest.json describing every\n# component the page might invoke (Counter, ui/button/index, ...).\n# This helper walks that manifest and registers one child renderer per\n# UI registry entry \u2014 the path shape `ui/<name>/index` maps to the\n# `<name>` slot key Counter.html.ep and friends use via\n# `<%= bf->render_child('<name>', ...) %>`.\n#\n# Each manifest entry carries an `ssrDefaults` hash derived statically\n# from the component's JSX (prop destructure defaults + signal /\n# memo initial values, see packages/jsx/src/ssr-defaults.ts). The\n# child renderer seeds every template variable from that hash,\n# preferring the caller's matching prop where one exists. This\n# replaces the per-component `signal_init` callback that every\n# scaffold's `app.pl` used to hand-roll for items 1/3 of issue #1416.\n#\n# `signal_init` remains as an opt-in override for cases the static\n# extractor can't see through (e.g. signal initial values that\n# reference imported helpers). When supplied for a given slot key\n# it takes precedence over the manifest's `ssrDefaults` for that\n# child, allowing callers to mix manual overrides with auto-derived\n# defaults for siblings.\nsub register_components_from_manifest ($self, $manifest, %opts) {\n my $signal_inits = $opts{signal_init} // {};\n my $parent_scope = $self->_scope_id;\n # Weaken the parent capture so the child-renderer closures stored on\n # `$self->_child_renderers` don't keep `$self` alive (the direct\n # closure <-> parent cycle). The controller is reached through `$parent`\n # at call time rather than captured strongly here, so the closures hold\n # no strong reference to `$c` either \u2014 see the controller-cycle note in\n # `new`. `$parent` is always live whenever a closure runs (the closure is\n # stored on `$parent`, so `$parent` outlives every invocation).\n weaken(my $parent = $self);\n\n for my $entry_name (keys %$manifest) {\n # `__barefoot__` is the runtime entry, not a component.\n next if $entry_name eq '__barefoot__';\n # Only UI registry components (path shape `ui/<name>/index`)\n # become child renderers; top-level page components are the\n # render target rather than a child.\n next unless $entry_name =~ m{^ui/([^/]+)/index$};\n my $slot_key = $1;\n my $marked = $manifest->{$entry_name}{markedTemplate} // '';\n next unless $marked;\n # `templates/ui/button/index.html.ep` \u2192 `ui/button/index`\n my $template_name = $marked;\n $template_name =~ s{^templates/}{};\n $template_name =~ s{\\.html\\.ep$}{};\n\n my $signal_init = $signal_inits->{$slot_key};\n my $manifest_defaults = $manifest->{$entry_name}{ssrDefaults};\n $self->register_child_renderer($slot_key, sub {\n my ($props) = @_;\n # Child shares the parent's backend so nested renders go\n # through the same engine + controller (and inherit any\n # injected json_encoder). The controller is fetched via the weak\n # `$parent` at call time \u2014 never captured strongly \u2014 so the\n # closure adds no edge to the per-request reference cycle.\n my $child_bf = BarefootJS->new($parent->c, { backend => $parent->backend });\n my $slot_id = delete $props->{_bf_slot};\n $child_bf->_scope_id(\n $slot_id ? $parent_scope . '_' . $slot_id\n : $template_name . '_' . substr(rand() =~ s/^0\\.//r, 0, 6)\n );\n $child_bf->_is_child(1);\n # (#1249) Slot identity: host scope + slot id. Emitted as\n # bf-h / bf-m attributes by hydration_attrs.\n if ($slot_id) {\n $child_bf->_bf_parent($parent_scope);\n $child_bf->_bf_mount($slot_id);\n }\n $child_bf->_scripts($parent->_scripts);\n $child_bf->_script_seen($parent->_script_seen);\n\n my %extra;\n if ($signal_init) {\n %extra = $signal_init->($props);\n } elsif ($manifest_defaults) {\n %extra = _derive_stash_from_defaults($manifest_defaults, $props);\n }\n\n # Render the child template with $child_bf bound as the active\n # instance for the nested render. The backend owns the\n # engine-specific binding + restore (stash juggle for Mojo).\n my $html = $parent->backend->render_named(\n $template_name, $child_bf, { %$props, %extra },\n );\n chomp $html;\n return $html;\n });\n }\n}\n\n# Derive template-stash kvs from a manifest entry's `ssrDefaults`\n# section. Each entry shape:\n# { value => <static-fallback>, propName => <prop>, isRestProps => bool }\n# For `isRestProps`, the rest bag passes through unchanged (or the\n# static `{}` if the caller didn't supply one). For ordinary entries\n# the caller's `$props->{propName}` wins when defined, otherwise the\n# static `value` does. `propName`-less entries (signal / memo locals)\n# always use the static value \u2014 the caller cannot override them.\nsub _derive_stash_from_defaults ($defaults, $props) {\n my %extra;\n for my $name (keys %$defaults) {\n my $d = $defaults->{$name};\n if (ref($d) ne 'HASH') {\n $extra{$name} = $d;\n next;\n }\n if ($d->{isRestProps}) {\n $extra{$name} = exists $props->{$name} ? $props->{$name} : $d->{value};\n next;\n }\n my $prop_name = $d->{propName};\n if (defined $prop_name && exists $props->{$prop_name} && defined $props->{$prop_name}) {\n $extra{$name} = $props->{$prop_name};\n } else {\n $extra{$name} = $d->{value};\n }\n }\n return %extra;\n}\n\n# ---------------------------------------------------------------------------\n# Script Output\n# ---------------------------------------------------------------------------\n\nsub scripts ($self) {\n my @tags;\n for my $path (@{$self->_scripts}) {\n push @tags, qq{<script type=\"module\" src=\"$path\"></script>};\n }\n return join(\"\\n\", @tags);\n}\n\n# ---------------------------------------------------------------------------\n# Streaming SSR (Out-of-Order)\n# ---------------------------------------------------------------------------\n\nsub streaming_bootstrap ($self) {\n return q{<script>(function(){function s(id){var a=document.querySelector('[bf-async=\"'+id+'\"]');var t=document.querySelector('template[bf-async-resolve=\"'+id+'\"]');if(!a||!t)return;a.replaceChildren(t.content.cloneNode(true));a.removeAttribute('bf-async');t.remove();requestAnimationFrame(function(){if(window.__bf_hydrate)window.__bf_hydrate()})};window.__bf_swap=s})()</script>};\n}\n\nsub async_boundary ($self, $id, $fallback_html) {\n # The fallback comes in via Mojo `begin %>...<% end` capture (see\n # MojoAdapter::renderAsync), which produces a CODE ref returning a\n # Mojo::ByteStream. Materialize it through the backend so the rendered\n # HTML embeds in the placeholder rather than the CODE ref's\n # stringification.\n $fallback_html = $self->backend->materialize($fallback_html);\n return qq{<div bf-async=\"$id\">$fallback_html</div>};\n}\n\nsub async_resolve ($self, $id, $content_html) {\n return qq{<template bf-async-resolve=\"$id\">$content_html</template><script>__bf_swap(\"$id\")</script>};\n}\n\n# ---------------------------------------------------------------------------\n# JS-compat callees (#1189) \u2014 invoked from generated Mojo templates as\n# <%= bf->json($val) %>, <%= bf->floor($val) %>, etc. The MojoAdapter's\n# `templatePrimitives` registry emits these helper calls in place of the\n# corresponding JS callees (`JSON.stringify`, `Math.floor`, \u2026) so the SSR\n# template can render value-equivalent output without a JS engine.\n#\n# Failure policy mirrors the Go adapter (#1188): user-data marshalling\n# (json) bubbles errors so Mojolicious aborts loudly on cycles /\n# unsupported values rather than silently producing an empty payload.\n# Numeric coercion follows JS semantics (NaN propagates as the special\n# string 'NaN'; non-numeric input returns 'NaN' rather than 0). Strings\n# always coerce to a string representation.\n# ---------------------------------------------------------------------------\n\nsub json ($self, $value) {\n # Mojo::JSON::to_json returns a character string (not bytes), suitable\n # for embedding in HTML output via Mojo::ByteStream / `<%==`.\n #\n # Documented divergence from JS: JS distinguishes `null` (renders as\n # \"null\") from `undefined` (`JSON.stringify(undefined)` returns the\n # JS value `undefined`, not a string). Perl has no such distinction\n # \u2014 both map to `undef`. We choose the `null` rendering for SSR\n # ergonomics: an unset prop becomes the string \"null\" rather than\n # the literal text \"undefined\" or an empty attribute. Matches the\n # `null` case of JS exactly; diverges from the `undefined` case.\n return $self->backend->encode_json($value);\n}\n\nsub string ($self, $value) {\n # JS `String(v)` mirror. `undef` renders as the empty string here so\n # an unset prop doesn't surface as a literal \"undefined\" / \"null\"\n # in user-facing HTML \u2014 same divergence the Go adapter documents\n # for `bf_string`.\n return defined $value ? \"$value\" : '';\n}\n\nsub number ($self, $value) {\n # JS `Number(v)` mirror. Numeric coerces via Perl's implicit\n # numeric context; non-numeric / undef yield real numeric NaN\n # (`'nan' + 0`) so downstream arithmetic propagates correctly\n # (`Math.floor(NaN) === NaN`). Returning the literal string\n # \"NaN\" would conflate the user-passing-the-string-\"NaN\" case\n # with the parse-failure case, and break NaN detection in\n # downstream helpers.\n return 0 + 'nan' unless defined $value;\n return $value + 0 if looks_like_number($value);\n return 0 + 'nan';\n}\n\n# NaN is the only float for which `$x != $x` holds. Used as the\n# portable sentinel check in floor/ceil/round.\nsub _is_nan { my $n = shift; return $n != $n }\n\nsub floor ($self, $value) {\n my $n = $self->number($value);\n return $n if _is_nan($n);\n return POSIX::floor($n);\n}\n\nsub ceil ($self, $value) {\n my $n = $self->number($value);\n return $n if _is_nan($n);\n return POSIX::ceil($n);\n}\n\nsub round ($self, $value) {\n my $n = $self->number($value);\n return $n if _is_nan($n);\n # POSIX has no `round`. JS `Math.round` rounds half toward\n # +Infinity (so `Math.round(-1.5) === -1`, not -2). `floor(n\n # + 0.5)` reproduces that for both signs.\n return POSIX::floor($n + 0.5);\n}\n\n# ---------------------------------------------------------------------------\n# Array / String method helpers (#1448 Tier A)\n# ---------------------------------------------------------------------------\n#\n# `Array.prototype.includes(x)` and `String.prototype.includes(sub)`\n# share a method name in JS; the JSX parser can't tell the two\n# receiver shapes apart without TS type inference, so both lower to\n# the same IR node (`array-method` / method `includes`). This helper\n# dispatches at the Perl level via `ref()`:\n# - ARRAY ref: scan elements with `eq`; one defined-vs-undef\n# hop matches JS's `===` for null/undefined.\n# - scalar: `index($recv, $sub) != -1`, with both args\n# coerced through `// ''` so an undef receiver /\n# needle doesn't trip Perl's substr warning.\n# Anything else (HASH ref, code ref) returns false \u2014 matches the\n# JS semantic where `.includes` is only defined on Array /\n# TypedArray / String.\n\nsub includes ($self, $recv, $elem) {\n if (ref($recv) eq 'ARRAY') {\n for my $item (@$recv) {\n if (!defined $item) {\n return 1 if !defined $elem;\n next;\n }\n return 1 if defined $elem && $item eq $elem;\n }\n return 0;\n }\n return 0 if ref($recv);\n return index($recv // '', $elem // '') != -1 ? 1 : 0;\n}\n\n# `Array.prototype.filter(fn)` / `.every(fn)` / `.some(fn)`. The Xslate adapter\n# lowers a JS arrow predicate to a Kolon lambda (`-> $x { ... }`), which is\n# callable from Perl as a code ref, and emits `$bf.filter($arr, <lambda>)`.\n# `filter` returns a new arrayref; `every` / `some` return 1/0. Non-array /\n# empty receivers follow JS (`filter` \u2192 [], `every` \u2192 true, `some` \u2192 false).\n# (The Mojo adapter lowers these shapes inline and never reaches these methods.)\nsub filter ($self, $recv, $pred) {\n return [] unless ref($recv) eq 'ARRAY';\n return [ grep { $pred->($_) } @$recv ];\n}\n\nsub every ($self, $recv, $pred) {\n return 1 unless ref($recv) eq 'ARRAY';\n for my $item (@$recv) { return 0 unless $pred->($item) }\n return 1;\n}\n\nsub some ($self, $recv, $pred) {\n return 0 unless ref($recv) eq 'ARRAY';\n for my $item (@$recv) { return 1 if $pred->($item) }\n return 0;\n}\n\n# `Array.prototype.find(fn)` / `.findIndex(fn)` / `.findLast(fn)` /\n# `.findLastIndex(fn)` \u2014 same Kolon-lambda predicate mechanism as filter. The\n# camelCase JS names lower to these snake_case methods (like index_of /\n# last_index_of). `find` / `find_last` return the matching element (or undef \u2192\n# JS `undefined`); the index forms return the 0-based position (or -1).\nsub find ($self, $recv, $pred) {\n return undef unless ref($recv) eq 'ARRAY';\n for my $item (@$recv) { return $item if $pred->($item) }\n return undef;\n}\n\nsub find_index ($self, $recv, $pred) {\n return -1 unless ref($recv) eq 'ARRAY';\n for my $i (0 .. $#$recv) { return $i if $pred->($recv->[$i]) }\n return -1;\n}\n\nsub find_last ($self, $recv, $pred) {\n return undef unless ref($recv) eq 'ARRAY';\n for my $i (reverse 0 .. $#$recv) { return $recv->[$i] if $pred->($recv->[$i]) }\n return undef;\n}\n\nsub find_last_index ($self, $recv, $pred) {\n return -1 unless ref($recv) eq 'ARRAY';\n for my $i (reverse 0 .. $#$recv) { return $i if $pred->($recv->[$i]) }\n return -1;\n}\n\n# `String.prototype.toLowerCase()` / `.toUpperCase()`. Kolon has a builtin\n# `.join` array method (so the adapter uses that directly) but no builtin\n# `lc` / `uc`, so these live on the runtime object. `CORE::` avoids recursing\n# into these methods.\nsub lc ($self, $s) { return defined $s ? CORE::lc($s) : '' }\nsub uc ($self, $s) { return defined $s ? CORE::uc($s) : '' }\n\n# `Array.prototype.join(sep)` with JS semantics: separator defaults to \",\",\n# and undefined / null elements render as empty (`[1,,2].join(\",\")` \u2192 \"1,,2\").\n# Kolon has a builtin `.join`, but routing through the runtime keeps the\n# JS-compat element handling in one place. `CORE::join` avoids recursing.\nsub join ($self, $recv, $sep = undef) {\n return '' unless ref($recv) eq 'ARRAY';\n $sep //= ',';\n return CORE::join($sep, map { defined $_ ? $_ : '' } @$recv);\n}\n\n# `.length` \u2014 JS works on BOTH arrays (element count) and strings (character\n# count); Kolon's builtin `.size()` is array-only and faults on a string. So\n# dispatch on ref type here. `CORE::length` avoids recursing into this method.\nsub length ($self, $recv) {\n return scalar @$recv if ref($recv) eq 'ARRAY';\n return 0 if ref($recv);\n return CORE::length($recv // '');\n}\n\n# `Array.prototype.indexOf(x)` / `Array.prototype.lastIndexOf(x)`\n# value-equality search (#1448 Tier A). Returns the 0-based position\n# of the first / last matching element, or -1 if not found.\n# Non-array receivers return -1 \u2014 matches the JS semantic that\n# `.indexOf` / `.lastIndexOf` are only defined on Array / TypedArray.\n# (The string-position `indexOf` form isn't in Tier A; if it lands\n# later the helper can grow a ref()-dispatch branch like `includes`.)\n\nsub _array_index_of ($recv, $elem, $reverse) {\n return -1 unless ref($recv) eq 'ARRAY';\n my @indices = $reverse ? (reverse 0 .. $#{$recv}) : (0 .. $#{$recv});\n for my $i (@indices) {\n my $item = $recv->[$i];\n if (!defined $item) {\n return $i if !defined $elem;\n next;\n }\n return $i if defined $elem && $item eq $elem;\n }\n return -1;\n}\n\nsub index_of ($self, $recv, $elem) {\n return _array_index_of($recv, $elem, 0);\n}\n\nsub last_index_of ($self, $recv, $elem) {\n return _array_index_of($recv, $elem, 1);\n}\n\n# `Array.prototype.at(i)` \u2014 supports negative indices (`.at(-1)` is\n# the last element); out-of-bounds returns undef (which Mojo's\n# auto-escape renders as the empty string, matching JS's `undefined`).\n# Non-array receivers return undef. Matches the Go `bf_at` arithmetic\n# (`length + i` for i < 0) so adapter output stays symmetric.\n\nsub at ($self, $recv, $i) {\n return undef unless ref($recv) eq 'ARRAY';\n return undef if !defined $i;\n my $len = scalar @$recv;\n return undef if $len == 0;\n my $idx = $i < 0 ? $len + $i : $i;\n return undef if $idx < 0 || $idx >= $len;\n return $recv->[$idx];\n}\n\n# `Array.prototype.concat(other)` \u2014 merges two arrays in order\n# into a new ARRAY ref. Non-array operands collapse to empty\n# (matches the Go `bf_concat` semantic so cross-adapter output\n# stays symmetric; differs from JS where a non-Array argument\n# with `Symbol.isConcatSpreadable` would be spread, a behaviour\n# the template-language path never observes).\n\nsub concat ($self, $a, $b) {\n my @out;\n push @out, @$a if ref($a) eq 'ARRAY';\n push @out, @$b if ref($b) eq 'ARRAY';\n return \\@out;\n}\n\n# `Array.prototype.slice(start, end?)` \u2014 carves out a sub-range\n# into a new ARRAY ref. Mirrors the Go `bf_slice` arithmetic so\n# adapter output stays symmetric:\n# - start < 0 \u2192 length + start (e.g. -1 = last index)\n# - end < 0 \u2192 length + end\n# - start < 0 after clamp \u2192 0\n# - end > length \u2192 length\n# - start >= end \u2192 empty\n# - end undef \u2192 \"to length\"\n# Non-array receivers return an empty ARRAY ref.\n\nsub slice ($self, $recv, $start, $end) {\n return [] unless ref($recv) eq 'ARRAY';\n my $len = scalar @$recv;\n return [] if $len == 0;\n\n my $s = $start // 0;\n $s = $len + $s if $s < 0;\n $s = 0 if $s < 0;\n $s = $len if $s > $len;\n\n my $e = defined $end ? $end : $len;\n $e = $len + $e if $e < 0;\n $e = 0 if $e < 0;\n $e = $len if $e > $len;\n\n return [] if $s >= $e;\n return [ @{$recv}[$s .. $e - 1] ];\n}\n\n# `Array.prototype.reverse()` / `Array.prototype.toReversed()` \u2014\n# both shapes share this lowering. SSR templates render a snapshot\n# of state, so JS's mutate-receiver (`reverse`) vs\n# return-new-array (`toReversed`) distinction has no template-\n# level meaning. Always returns a new ARRAY ref to keep callers\n# safe from accidental aliasing. Non-array receivers return an\n# empty ARRAY ref.\n\nsub reverse ($self, $recv) {\n return [] unless ref($recv) eq 'ARRAY';\n return [ reverse @$recv ];\n}\n\n# `Array.prototype.flat(depth?)` (#1448 Tier C) \u2014 flatten nested ARRAY\n# refs `$depth` levels deep. A `$depth` of -1 is the `Infinity` sentinel\n# (flatten fully); 0 returns a shallow copy. Non-ARRAY elements are kept\n# as-is (JS only flattens nested arrays). Non-ARRAY receiver \u2192 [].\nsub flat ($self, $recv, $depth = 1) {\n return [] unless ref($recv) eq 'ARRAY';\n my @out;\n for my $el (@$recv) {\n if ($depth != 0 && ref($el) eq 'ARRAY') {\n my $next = $depth > 0 ? $depth - 1 : $depth;\n push @out, @{ $self->flat($el, $next) };\n }\n else {\n push @out, $el;\n }\n }\n return \\@out;\n}\n\n# `Array.prototype.flatMap(fn)` value-returning field projection\n# (#1448 Tier C) \u2014 map each element through a self / field projection,\n# then flatten one level. `field` reads a HASH-ref key (the raw JS prop\n# name, as `bf->reduce` does); a projected non-ARRAY value is kept as-is\n# (flatMap = map + flat(1)). Non-ARRAY receiver \u2192 [].\nsub flat_map ($self, $recv, $key_kind, $key) {\n return [] unless ref($recv) eq 'ARRAY';\n my @projected;\n for my $el (@$recv) {\n if ($key_kind eq 'field') {\n # JS `i => i.field` on a non-object yields `undefined`, not the\n # element itself \u2014 push `undef` so a scalar element doesn't leak\n # into the output (matches Go's `getFieldValue` returning nil).\n push @projected, ref($el) eq 'HASH' ? $el->{$key} : undef;\n }\n else {\n push @projected, $el;\n }\n }\n return $self->flat(\\@projected, 1);\n}\n\n# `Array.prototype.flatMap(i => [i.a, i.b])` \u2014 array-literal tuple\n# projection (#1448 Tier C). Each `@specs` entry is a [kind, key] arrayref\n# (['self', ''] or ['field', 'a']). For each element, every leaf's value\n# is appended in order. flat(1) removes only the literal wrapper, so an\n# array-valued leaf is appended verbatim (no spread) \u2014 i.e. just append\n# each leaf. A non-HASH element under a `field` leaf yields undef (JS\n# `i.field` on a non-object). Non-ARRAY receiver \u2192 [].\nsub flat_map_tuple ($self, $recv, @specs) {\n return [] unless ref($recv) eq 'ARRAY';\n my @out;\n for my $el (@$recv) {\n for my $spec (@specs) {\n my ($kind, $key) = @$spec;\n if ($kind eq 'field') {\n push @out, ref($el) eq 'HASH' ? $el->{$key} : undef;\n }\n else {\n push @out, $el;\n }\n }\n }\n return \\@out;\n}\n\n# `String.prototype.trim()` \u2014 strip leading + trailing whitespace.\n# JS's `String.prototype.trim` matches `\\s` in the Unicode sense\n# (any whitespace including non-breaking space U+00A0); Perl's `\\s`\n# inside a regex with `/u` flag is the same. Undef receivers return\n# the empty string (matches JS's `String(undefined).trim()` which\n# would be \"undefined\" \u2192 \"undefined\", but in our template context\n# undef commonly means \"missing prop\"; rendering the empty string\n# is the safer choice and mirrors the JS-compat divergence we\n# already document for `bf->string(undef) === \"\"`).\n\nsub trim ($self, $recv) {\n return '' unless defined $recv;\n return '' if ref($recv);\n my $s = \"$recv\";\n $s =~ s/^\\s+|\\s+$//gu;\n return $s;\n}\n\n# `String.prototype.split(sep)` (#1448 Tier B) \u2014 string \u2192 ARRAY ref.\n#\n# Two JS-parity wrinkles drive the helper (a bare `split` emit would\n# diverge from both JS and Go):\n#\n# * Perl's `split` treats its first argument as a *regex*, so a\n# separator like '.' or '|' would match far too much. We\n# `quotemeta` it to force literal-string matching, mirroring JS's\n# string-separator semantics (the regex-separator form stays\n# refused upstream \u2014 see the parser arm).\n# * Perl's `split` drops trailing empty fields by default; JS keeps\n# them (`\"a,\".split(\",\")` is `[\"a\", \"\"]`). Passing the `-1` limit\n# preserves them, matching JS and Go's `strings.Split`.\n#\n# An empty separator splits into individual characters (JS + Go agree).\n# Undef receiver renders as the single-element `['']` \u2014 the same\n# \"missing prop \u2192 empty string\" convention `bf->trim` uses.\n\nsub split ($self, $recv, $sep = undef, $limit = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n\n my @parts;\n if (!defined $sep) {\n # No separator \u2192 the whole string in a single-element array\n # (matches JS `\"x\".split()` / `.split(undefined)`).\n @parts = ($s);\n }\n elsif (\"$sep\" eq '') {\n # Empty separator \u2192 individual characters. No `-1` limit here:\n # on an empty pattern Perl's `split` with `-1` appends a spurious\n # trailing empty field (\"abc\" \u2192 'a','b','c',''), which JS/Go don't.\n @parts = split //, $s;\n }\n elsif ($s eq '') {\n # Empty input with a non-empty separator: JS `\"\".split(\",\")` is\n # `[\"\"]` and Go's `strings.Split(\"\", \",\")` is `[\"\"]`, but Perl's\n # `split /,/, ''` returns the empty list \u2014 special-case for parity.\n @parts = ('');\n }\n else {\n # `quotemeta` forces literal-string matching (JS string-separator\n # semantics); the `-1` keeps trailing empty fields (JS keeps them,\n # Perl's bare `split` drops them).\n my $q = quotemeta(\"$sep\");\n @parts = split /$q/, $s, -1;\n }\n\n # Optional `limit` caps the number of pieces (JS `split(sep, limit)`).\n # 0 \u2192 empty; a negative limit keeps all (JS ToUint32 wrap makes it\n # effectively unbounded) \u2014 both match Go's `bf_split`.\n if (defined $limit) {\n my $n = int($limit);\n if ($n == 0) { @parts = () }\n elsif ($n > 0 && $n < scalar @parts) { @parts = @parts[0 .. $n - 1] }\n }\n\n return [@parts];\n}\n\n# `String.prototype.startsWith(prefix, position?)` (#1448 Tier B) \u2014\n# string \u2192 boolean (1 / 0). `substr`-anchored literal comparison mirrors\n# Go's `strings.HasPrefix`. An empty prefix is always true (JS parity);\n# undef / non-string receivers coerce to the empty string first. The\n# optional `position` re-anchors the test (clamped to `[0, length]`),\n# matching JS `\"abc\".startsWith(\"b\", 1)`.\n\nsub starts_with ($self, $recv, $prefix, $position = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $p = defined $prefix ? \"$prefix\" : '';\n if (defined $position) {\n my $n = int($position);\n $n = 0 if $n < 0;\n $n = CORE::length($s) if $n > CORE::length($s);\n $s = substr($s, $n);\n }\n return substr($s, 0, CORE::length $p) eq $p ? 1 : 0;\n}\n\n# `String.prototype.endsWith(suffix, endPosition?)` (#1448 Tier B) \u2014\n# string \u2192 boolean (1 / 0). Mirrors Go's `strings.HasSuffix`. An empty\n# suffix is always true (JS parity); a suffix longer than the string is\n# false. `substr($s, -length $x)` would mis-read the whole string when\n# `length $x == 0`, so that case short-circuits. The optional\n# `endPosition` treats the string as if it were only that many chars\n# long (clamped to `[0, length]`), matching JS `\"abc\".endsWith(\"b\", 2)`.\n\nsub ends_with ($self, $recv, $suffix, $end_position = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $x = defined $suffix ? \"$suffix\" : '';\n if (defined $end_position) {\n my $e = int($end_position);\n $e = 0 if $e < 0;\n $e = CORE::length($s) if $e > CORE::length($s);\n $s = substr($s, 0, $e);\n }\n return 1 if $x eq '';\n return 0 if CORE::length($s) < CORE::length($x);\n return substr($s, -CORE::length $x) eq $x ? 1 : 0;\n}\n\n# `String.prototype.replace(pattern, replacement)` \u2014 string-pattern\n# form only (#1448 Tier B), replacing the FIRST occurrence (JS string-\n# pattern semantics). Spliced via index/substr rather than `s///` so\n# BOTH the pattern and the replacement are literal: no Perl regex\n# metacharacters in the pattern and no `$1` / `$&` interpolation in the\n# replacement. Go's `bf_replace` (strings.Replace, n=1) treats the\n# replacement literally too, so the two adapters stay byte-equal \u2014 this\n# diverges from JS only for replacement strings containing `$`-patterns\n# (rare in template position). An empty pattern inserts the replacement\n# at the front (`\"abc\".replace(\"\", \"X\")` \u2192 \"Xabc\"), matching JS + Go.\n\nsub replace ($self, $recv, $pattern, $replacement) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $o = defined $pattern ? \"$pattern\" : '';\n my $n = defined $replacement ? \"$replacement\" : '';\n return $n . $s if $o eq '';\n my $i = index($s, $o);\n return $s if $i < 0;\n return substr($s, 0, $i) . $n . substr($s, $i + CORE::length($o));\n}\n\n# `String.prototype.repeat(n)` \u2014 the receiver concatenated n times\n# (#1448 Tier B), via Perl's `x` operator. JS throws RangeError for a\n# negative count, but SSR templates degrade to the empty string rather\n# than dying mid-render, so a count <= 0 returns \"\" (Go's `bf_repeat`\n# applies the same clamp). The count is truncated toward zero\n# (`int`), matching JS's ToIntegerOrInfinity on `\"a\".repeat(3.7)`.\n\nsub repeat ($self, $recv, $count) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $n = defined $count ? int($count) : 0;\n return $n <= 0 ? '' : $s x $n;\n}\n\n# `String.prototype.padStart` / `padEnd` (#1448 Tier B) \u2014 pad the\n# receiver to `$target` characters with `$pad` (default a single space)\n# repeated and truncated to fill, prepended or appended. Length is\n# measured in characters (Perl `length`), matching Go's rune-based\n# `bf_pad_*` \u2014 diverges from JS's UTF-16-unit length only for\n# astral-plane input. An empty pad, or a receiver already >= `$target`,\n# returns the receiver unchanged (JS parity). The `$target` is\n# truncated toward zero (JS ToLength on the first arg).\n\nsub _pad ($s, $target, $pad, $at_start) {\n $pad = ' ' unless defined $pad;\n $pad = \"$pad\";\n return $s if $pad eq '';\n my $len = CORE::length $s;\n my $t = int($target // 0);\n return $s if $len >= $t;\n my $need = $t - $len;\n # Repeat enough copies to cover $need, then trim to exactly $need.\n my $fill = substr($pad x (int($need / CORE::length($pad)) + 1), 0, $need);\n return $at_start ? $fill . $s : $s . $fill;\n}\n\nsub pad_start ($self, $recv, $target, $pad = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n return _pad($s, $target, $pad, 1);\n}\n\nsub pad_end ($self, $recv, $target, $pad = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n return _pad($s, $target, $pad, 0);\n}\n\n# `Array.prototype.sort(cmp)` / `Array.prototype.toSorted(cmp)`\n# lowering (#1448 Tier B). Non-mutating \u2014 JS's mutate-vs-new\n# distinction is moot in SSR template context.\n#\n# Opts hash-ref. The compiler emits a `keys` list of per-key hashes\n# in priority order; each hash carries:\n#\n# key_kind => 'self' | 'field'\n# key => '' when key_kind eq 'self'; field name verbatim\n# from the comparator AST (e.g. 'price', 'createdAt')\n# when key_kind eq 'field' \u2014 no case normalisation\n# applied. Perl hash lookups are case-sensitive so\n# the key here must match the actual hash key the\n# user populated.\n# compare_type => 'numeric' | 'string' | 'auto'\n# direction => 'asc' | 'desc'\n#\n# Accepted comparator catalogue (gated upstream at parse time \u2014\n# anything outside refuses with BF101 before reaching this helper):\n#\n# (a,b) => a.f - b.f \u2192 field, numeric\n# (a,b) => a - b \u2192 self, numeric\n# (a,b) => a[.f].localeCompare(b[.f]) \u2192 field|self, string\n# (a,b) => a.f > b.f ? 1 : -1 \u2192 field|self, auto\n# any of the above ||-chained \u2192 multi-key tie-breaks\n# (and reversed-operand variants for `desc`).\n#\n# `auto` (relational-ternary lowering) compares numerically when both\n# keys `looks_like_number`, else lexically \u2014 Go's `bf_sort` applies the\n# same rule so the two template adapters stay byte-equal.\n#\n# A future `nulls => 'first' | 'last'` knob can land per key without\n# churn \u2014 the opts hash is the right place to grow.\n\nsub sort ($self, $recv, $opts = {}) {\n return [] unless ref($recv) eq 'ARRAY';\n\n # Normalise the per-key specs (priority order, length >= 1).\n my @spec = map {\n {\n key_kind => $_->{key_kind} // 'self',\n key => $_->{key} // '',\n compare_type => $_->{compare_type} // 'numeric',\n direction => $_->{direction} // 'asc',\n }\n } @{ $opts->{keys} // [] };\n return [ @$recv ] unless @spec;\n\n # Schwartzian transform: project each item to all its sort keys\n # once, then compare projected keys. Cheaper than re-resolving the\n # field accessors inside every comparison for non-trivial arrays.\n my @keyed = map {\n my $item = $_;\n my @ks = map {\n $_->{key_kind} eq 'field' && ref($item) eq 'HASH' ? $item->{ $_->{key} } : $item;\n } @spec;\n [ \\@ks, $item ];\n } @$recv;\n\n my $cmp = sub {\n for my $i (0 .. $#spec) {\n my $sp = $spec[$i];\n my $c = _compare_sort_key($a->[0][$i], $b->[0][$i], $sp->{compare_type});\n next if $c == 0; # tie on this key \u2014 try the next\n return $sp->{direction} eq 'desc' ? -$c : $c;\n }\n return 0;\n };\n\n my @sorted = sort $cmp @keyed;\n return [ map { $_->[1] } @sorted ];\n}\n\n# Compare two projected keys, ascending orientation (-1 / 0 / 1); the\n# caller negates for 'desc'. 'auto' compares numerically when both\n# keys look like numbers, else lexically (matches Go's `bf_sort`).\n# undef coalesces to '' / 0 so the order stays total without warnings.\nsub _compare_sort_key ($av, $bv, $compare_type) {\n if ($compare_type eq 'string') {\n return ($av // '') cmp ($bv // '');\n }\n if ($compare_type eq 'auto') {\n if (looks_like_number($av // '') && looks_like_number($bv // '')) {\n return ($av // 0) <=> ($bv // 0);\n }\n return ($av // '') cmp ($bv // '');\n }\n return ($av // 0) <=> ($bv // 0); # numeric\n}\n\n# Fold an array into a scalar via the arithmetic-fold catalogue\n# (#1448 Tier C). Mirrors Go's `bf_reduce` and JS `reduce(fn, init)` /\n# `reduceRight(fn, init)` for the shapes `(acc, x) => acc <op> x` /\n# `(acc, x) => acc <op> x.field`:\n#\n# bf->reduce($recv, {\n# op => '+' | '*',\n# key_kind => 'self' | 'field',\n# key => '<field>', # when key_kind eq 'field'\n# type => 'numeric' | 'string',\n# init => <seed>, # number, or string for concat\n# direction => 'left' | 'right', # 'right' = reduceRight (default 'left')\n# })\n#\n# Numeric folds accumulate with `+` / `*` (non-numeric keys coalesce to\n# 0); string folds concatenate via `bf->string` (undef \u2192 ''). The init\n# seeds the accumulator, so an empty array returns it unchanged \u2014 exactly\n# like JS. `direction => 'right'` folds right-to-left (reduceRight); only\n# observable for string concat, since numeric sum / product commute.\n# Float stringification can diverge from Go's for inexact binary\n# fractions (e.g. 0.1 + 0.2); integer sums \u2014 the common case \u2014 agree.\nsub reduce ($self, $recv, $opts = {}) {\n my $op = $opts->{op} // '+';\n my $key_kind = $opts->{key_kind} // 'self';\n my $key = $opts->{key} // '';\n my $type = $opts->{type} // 'numeric';\n my $direction = $opts->{direction} // 'left';\n\n my @items = ref($recv) eq 'ARRAY' ? @$recv : ();\n # reduceRight folds right-to-left; reversing the snapshot keeps the\n # single forward loop below. Only observable for string concat \u2014\n # numeric sum / product commute. Qualify as CORE::reverse \u2014 this\n # package defines `sub reverse` (the `.reverse()` helper), so a bare\n # `reverse` is ambiguous under `use warnings`.\n @items = CORE::reverse(@items) if $direction eq 'right';\n my $project = sub ($item) {\n $key_kind eq 'field' && ref($item) eq 'HASH' ? $item->{$key} : $item;\n };\n\n if ($type eq 'string') {\n my $acc = $opts->{init} // '';\n $acc .= $self->string($project->($_)) for @items;\n return $acc;\n }\n\n my $acc = $opts->{init} // 0;\n for my $item (@items) {\n my $n = $project->($item);\n # Guard `defined` before `looks_like_number` so a missing field\n # (undef) folds as 0 without an \"uninitialized value\" warning\n # under `use warnings` \u2014 matching the `$av // ''` style `sort` uses.\n $n = 0 unless defined $n && looks_like_number($n);\n $op eq '*' ? ($acc *= $n) : ($acc += $n);\n }\n return $acc;\n}\n\n# ---------------------------------------------------------------------------\n# JSX intrinsic-element spread (#1407)\n# ---------------------------------------------------------------------------\n#\n# Mirrors the JS `spreadAttrs` runtime\n# (`packages/client/src/runtime/spread-attrs.ts`) and the Go adapter's\n# `bf.SpreadAttrs` so SSR output stays byte-equal across the three\n# adapters. Generated Mojo templates invoke this as\n# `<%== bf->spread_attrs($bag) %>`.\n#\n# Skip rules: nil/false values, event handlers (`on[A-Z]\u2026` shape\n# matching JS `key[2] === key[2].toUpperCase()` \u2014 true for any\n# character whose uppercase is itself, including digits and\n# underscore), `children`. `ref` is intentionally NOT filtered,\n# matching the JS reference.\n#\n# Key remap: className \u2192 class, htmlFor \u2192 for; SVG camelCase\n# attrs preserved (case-sensitive XML spec); other camelCase keys\n# lowered to kebab-case with a leading `-` for an initial\n# uppercase letter (mirrors JS `key.replace(/([A-Z])/g, '-$1')`).\n#\n# `style` is routed through `_style_to_css` so object literals\n# serialise to a real CSS string instead of Perl's default\n# `HASH(0x...)` form.\n#\n# Output is deterministic: keys are sorted alphabetically before\n# emission, matching the Go adapter's `sort.Strings(keys)` policy\n# and Mojo::JSON's marshal order.\n#\n# The return value is a Mojo::ByteStream so the calling template's\n# `<%==` raw-emit skips re-escaping (the helper has already\n# HTML-escaped each value).\n\nmy %SVG_CAMEL_CASE_ATTRS = map { $_ => 1 } qw(\n allowReorder attributeName attributeType autoReverse\n baseFrequency baseProfile calcMode clipPathUnits\n contentScriptType contentStyleType diffuseConstant edgeMode\n externalResourcesRequired filterRes filterUnits glyphRef\n gradientTransform gradientUnits kernelMatrix kernelUnitLength\n keyPoints keySplines keyTimes lengthAdjust limitingConeAngle\n markerHeight markerUnits markerWidth maskContentUnits\n maskUnits numOctaves pathLength patternContentUnits\n patternTransform patternUnits pointsAtX pointsAtY pointsAtZ\n preserveAlpha preserveAspectRatio primitiveUnits refX refY\n repeatCount repeatDur requiredExtensions requiredFeatures\n specularConstant specularExponent spreadMethod startOffset\n stdDeviation stitchTiles surfaceScale systemLanguage\n tableValues targetX targetY textLength viewBox viewTarget\n xChannelSelector yChannelSelector zoomAndPan\n);\n\nsub _to_attr_name ($key) {\n return 'class' if $key eq 'className';\n return 'for' if $key eq 'htmlFor';\n return $key if $SVG_CAMEL_CASE_ATTRS{$key};\n # camelCase \u2192 kebab-case, with a leading `-` for an initial\n # uppercase letter (JS-reference parity, even though that case\n # produces an HTML-invalid attribute name \u2014 same documented\n # behaviour as the Go adapter's `toAttrName`).\n my $out = $key;\n $out =~ s/([A-Z])/-\\L$1/g;\n return $out;\n}\n\nsub _html_escape ($value) {\n # HTML attribute-value escape for SSR string emission. The\n # spread bag's values reach the browser as part of a generated\n # `key=\"...\"` substring inside the rendered HTML, so the\n # escape set has to cover everything that could break either\n # the surrounding double-quoted attribute or the enclosing\n # tag: `&`, `<`, `>`, `\"`, and `'`. Matches Go's\n # `template.HTMLEscapeString` semantics byte-for-byte (using\n # `"` / `'` for quotes rather than the named entities)\n # so the SSR output is identical across the Go and Mojo\n # adapters (#1407, #1413 review). The CSR-side\n # `applyRestAttrs` calls `el.setAttribute(name, String(value))`\n # \u2014 which does its own DOM-level escaping in the browser \u2014\n # so JS doesn't need an explicit escape pass; Perl/Go emit a\n # string, so we do.\n my $s = defined $value ? \"$value\" : '';\n $s =~ s/&/&/g;\n $s =~ s/</</g;\n $s =~ s/>/>/g;\n $s =~ s/\"/"/g;\n $s =~ s/'/'/g;\n return $s;\n}\n\nsub _style_to_css ($value) {\n return undef unless defined $value;\n # Non-hashref values pass through stringified \u2014 matches the JS\n # `typeof value !== 'object'` branch in `styleToCss`.\n if (ref($value) ne 'HASH') {\n my $s = \"$value\";\n return CORE::length $s ? $s : undef;\n }\n my @parts;\n for my $key (sort keys %$value) {\n my $v = $value->{$key};\n next unless defined $v;\n my $prop = $key;\n $prop =~ s/([A-Z])/-\\L$1/g;\n push @parts, \"$prop:$v\";\n }\n return @parts ? CORE::join(';', @parts) : undef;\n}\n\nsub spread_attrs ($self, $bag) {\n return '' unless defined $bag && ref($bag) eq 'HASH';\n my @parts;\n for my $key (sort keys %$bag) {\n # Event handlers: skip when key starts `on` and the third\n # character is its own uppercase form (uppercase letter,\n # digit, underscore, \u2026). Mirrors the JS predicate.\n if (CORE::length($key) > 2 && substr($key, 0, 2) eq 'on') {\n my $c = substr($key, 2, 1);\n next if CORE::uc($c) eq $c;\n }\n next if $key eq 'children';\n my $val = $bag->{$key};\n # null / undef \u2192 drop.\n next unless defined $val;\n # Boolean values arrive as Mojo::JSON sentinel objects\n # (`Mojo::JSON::true` / `false`) \u2014 both from JSON-deserialised\n # props and from the test harness's `toPerlLiteral`\n # (which emits the sentinels rather than plain 0/1 to avoid\n # conflating booleans with numeric attribute values like\n # `tabindex=\"0\"`). The contract is: callers MUST use the\n # sentinels for boolean values; plain Perl scalars 0/1\n # render as numeric attribute values, matching how JS\n # `spreadAttrs` treats a `0`/`1` JS number.\n if (ref($val) eq 'JSON::PP::Boolean' || ref($val) eq 'Mojo::JSON::_Bool') {\n next unless $val;\n push @parts, _to_attr_name($key);\n next;\n }\n # `style` routes through `_style_to_css` so object literals\n # serialise to a real CSS string.\n if ($key eq 'style') {\n my $css = _style_to_css($val);\n next unless defined $css && CORE::length $css;\n push @parts, qq{style=\"} . _html_escape($css) . qq{\"};\n next;\n }\n my $name = _to_attr_name($key);\n push @parts, $name . qq{=\"} . _html_escape($val) . qq{\"};\n }\n return '' unless @parts;\n # Mark the result raw so the calling template's `<%==` raw-emit\n # doesn't re-escape the already-escaped values (the Mojo backend\n # returns a Mojo::ByteStream).\n return $self->backend->mark_raw(CORE::join(' ', @parts));\n}\n\n1;\n__END__\n\n=encoding utf8\n\n=head1 NAME\n\nBarefootJS - Engine- and framework-agnostic server runtime for BarefootJS marked templates\n\n=head1 SYNOPSIS\n\n use BarefootJS;\n\n # A host injects a rendering backend (see BarefootJS::Backend::Xslate or\n # Mojolicious::Plugin::BarefootJS for shipping backends).\n my $bf = BarefootJS->new($context, { backend => $backend });\n\n # The compiled marked template calls the runtime as a `bf` object:\n # <: $bf.scope_attr() :> <: $bf.json($data) :> <: $bf.spread_attrs($h) :>\n\n=head1 DESCRIPTION\n\nBarefootJS compiles JSX/TSX into a marked template plus client JS. This module\nis the server-side runtime the marked templates call into at render time. It is\ndeliberately template-engine- and web-framework-agnostic: every operation that\ndepends on I<how> a template is rendered \u2014 JSON marshalling, raw-string marking,\nJSX-children materialisation, and named-template rendering \u2014 is delegated to a\npluggable C<backend>.\n\nThat design lets the one runtime drive any backend. Shipping backends:\n\n=over 4\n\n=item * L<BarefootJS::Backend::Xslate> \u2014 Text::Xslate (Kolon); runs under any PSGI/Plack app.\n\n=item * L<BarefootJS::Backend::Mojo> \u2014 Mojolicious (via L<Mojolicious::Plugin::BarefootJS>).\n\n=back\n\nThe core itself pulls in only core Perl modules (C<POSIX>, C<Scalar::Util>);\nno template engine or web framework is loaded unless a backend that needs one\nis used.\n\n=head1 SEE ALSO\n\nL<BarefootJS::Backend::Xslate>, L<Mojolicious::Plugin::BarefootJS>,\nL<https://github.com/piconic-ai/barefootjs>\n\n=head1 AUTHOR\n\nkobaken E<lt>kentafly88@gmail.comE<gt>\n\n=head1 LICENSE\n\nCopyright (c) 2025-present BarefootJS Contributors.\n\nThis library is free software; you can redistribute it and/or modify it under\nthe MIT License. See the F<LICENSE> file in the distribution for the full text.\n\n=cut\n";
|
|
23567
|
+
barefootPmSource = "package BarefootJS;\nour $VERSION = \"0.8.0\";\nuse strict;\nuse warnings;\nuse utf8;\nuse feature 'signatures';\nno warnings 'experimental::signatures';\n\nuse POSIX ();\nuse Scalar::Util qw(looks_like_number weaken);\n\n# NOTE: This runtime is template-engine-agnostic AND framework-agnostic by\n# design, so it can ship as a standalone CPAN distribution. It depends only on\n# core Perl (subroutine signatures + the hand-rolled minimal accessor base\n# below \u2014 no Mojo::Base, no Class::Tiny). Every operation that depends on *how*\n# a template is rendered \u2014 JSON marshalling, raw-string marking, JSX-children\n# materialisation, and named-template rendering \u2014 is delegated to a pluggable\n# `backend` (see BarefootJS::Backend::Mojo for the reference Mojolicious\n# implementation), which is the only component that pulls in the Mojo\n# distribution, and only when it is actually used.\n\n# ---------------------------------------------------------------------------\n# Minimal accessor base (no Mojo::Base / Class::Tiny dependency)\n# ---------------------------------------------------------------------------\n#\n# Generates read/write accessors with optional lazy defaults so the runtime\n# stays free of any non-core OO base. Semantics mirror the Mojo::Base `has`\n# this class used to inherit: a getter returns the stored value (building it\n# from the default on first access if unset); a setter stores the value and\n# returns $self for chaining. A default is either a plain scalar or a coderef\n# invoked as `$default->($self)` (for per-instance refs like `[]` / `{}` and\n# the lazily-required Mojo backend).\nmy %ATTR_DEFAULT = (\n _scripts => sub { [] },\n _script_seen => sub { {} },\n _child_renderers => sub { {} },\n _is_child => 0,\n # Lazily fall back to the Mojo reference backend so a bare-blessed\n # instance (the pure-function unit tests) and the historical\n # `BarefootJS->new($c, ...)` callers keep working unchanged. A non-Mojo\n # host injects its own backend via `BarefootJS->new($c, { backend => $b })`\n # and never triggers this require \u2014 keeping the core load Mojo-free.\n backend => sub {\n require BarefootJS::Backend::Mojo;\n return BarefootJS::Backend::Mojo->new;\n },\n);\n\n# c \u2014 Mojolicious controller (kept for back-compat accessors)\n# config \u2014 plugin / instance config\n# backend \u2014 the template-engine seam (#engine-abstraction)\n# _scope_id \u2014 addressable scope id\n# _bf_parent / _bf_mount \u2014 slot identity when this scope is slot-attached\n# _props \u2014 props serialised into bf-p / the scope comment\n# _data_key \u2014 keyed-loop-item key, emitted as data-key on the scope root\nfor my $attr (qw(\n c config backend\n _scripts _script_seen _scope_id _is_child _bf_parent _bf_mount _props\n _data_key _child_renderers\n)) {\n no strict 'refs';\n *{\"BarefootJS::$attr\"} = sub {\n my $self = shift;\n if (@_) { $self->{$attr} = shift; return $self; }\n if (!exists $self->{$attr} && exists $ATTR_DEFAULT{$attr}) {\n my $d = $ATTR_DEFAULT{$attr};\n $self->{$attr} = ref($d) eq 'CODE' ? $d->($self) : $d;\n }\n return $self->{$attr};\n };\n}\n\nsub new ($class, $c, $config = {}) {\n # Build (or accept an injected) rendering backend. The default Mojo\n # backend wraps the controller and honours an optional `json_encoder`\n # override so a host can swap in a faster XS JSON implementation\n # without subclassing. A caller targeting another template engine\n # passes its own backend via `$config->{backend}`.\n my $backend = $config->{backend};\n unless ($backend) {\n require BarefootJS::Backend::Mojo;\n $backend = BarefootJS::Backend::Mojo->new(\n c => $c,\n ($config->{json_encoder}\n ? (json_encoder => $config->{json_encoder})\n : ()),\n );\n }\n my $self = bless {\n c => $c,\n config => $config,\n backend => $backend,\n }, $class;\n # Hold the controller weakly. Mojolicious stashes this bf instance under\n # `$c->stash->{'bf.instance'}`, so a strong bf -> controller back-reference\n # closes a per-request cycle ($c -> stash -> bf -> $c) that Perl's\n # refcount GC cannot reclaim, leaking one controller + bf + child-renderer\n # closures per request. The controller owns (outlives) the per-request bf,\n # so the weak ref stays valid for the whole render. Callers that need the\n # controller to outlive the bf instance independently must keep their own\n # strong reference (the normal Mojo request scope already does).\n weaken($self->{c}) if defined $c;\n return $self;\n}\n\n# ---------------------------------------------------------------------------\n# Scope & Props\n# ---------------------------------------------------------------------------\n\nsub scope_attr ($self) {\n # bf-s is the addressable scope id only (#1249).\n return $self->_scope_id // '';\n}\n\n# Emits `bf-h=\"<host>\" bf-m=\"<slot>\" bf-r=\"\"` conditionally.\n# See spec/compiler.md \"Slot identity\".\nsub hydration_attrs ($self) {\n my @parts;\n my $host = $self->_bf_parent;\n my $mount = $self->_bf_mount;\n if (defined $host && length $host) {\n my $h = $host =~ s/\"/"/gr;\n push @parts, qq{bf-h=\"$h\"};\n }\n if (defined $mount && length $mount) {\n my $m = $mount =~ s/\"/"/gr;\n push @parts, qq{bf-m=\"$m\"};\n }\n unless ($self->_is_child) {\n push @parts, q{bf-r=\"\"};\n }\n return join(' ', @parts);\n}\n\n# Emits ` data-key=\"<key>\"` for a keyed loop item, else ''. The client\n# runtime uses data-key for list reconciliation; SSR must match the Hono\n# reference, which stamps it on each loop item's scope root. The value is set\n# on the child instance by the child renderer (`register_child_renderer` /\n# `register_components_from_manifest`) from the JSX `key` prop \u2014 a reserved\n# prop, never a real template variable.\nsub data_key_attr ($self) {\n my $k = $self->_data_key;\n return '' unless defined $k;\n $k =~ s/&/&/g;\n $k =~ s/\"/"/g;\n return qq{ data-key=\"$k\"};\n}\n\nsub props_attr ($self) {\n my $props = $self->_props;\n return '' unless $props && %$props;\n # encode_json returns a character string (not bytes) for safe embedding\n # in templates (the Mojo backend uses Mojo::JSON::to_json).\n my $json = $self->backend->encode_json($props);\n return qq{ bf-p='$json'};\n}\n\n# ---------------------------------------------------------------------------\n# Context (SSR mirror of the client `provideContext` / `useContext`)\n# ---------------------------------------------------------------------------\n#\n# A `<Ctx.Provider value>` seeds a value that descendant `useContext(Ctx)`\n# consumers read during the same render. Dynamic scoping mirrors the client:\n# the provider pushes the value before rendering its children and pops it\n# after, and `use_context` reads the innermost active value (or the\n# `createContext` default when none is active).\n#\n# The value stacks live in a package-level store rather than per-instance or\n# on `$c->stash`: a parent template and the child templates it renders via\n# `render_child` are separate bf instances that don't reliably share a\n# controller (the Xslate backend runs with `c => undef`) nor a backend (the\n# Mojo path lazily builds one per instance). SSR rendering is synchronous \u2014\n# nothing awaits between a provider's push and its matching pop \u2014 and the\n# push/pop are perfectly balanced, so the per-name stack always unwinds to\n# empty at the end of each provider subtree, keeping concurrent root renders\n# isolated. provide/revoke return '' so they drop cleanly into an inline\n# `<: \u2026 :>` (Kolon) or `% \u2026 ;` (EP) emit.\n\nmy %CONTEXT_STACKS;\n\nsub provide_context ($self, $name, $value) {\n push @{ $CONTEXT_STACKS{$name} //= [] }, $value;\n return '';\n}\n\nsub revoke_context ($self, $name) {\n pop @{ $CONTEXT_STACKS{$name} } if $CONTEXT_STACKS{$name} && @{ $CONTEXT_STACKS{$name} };\n return '';\n}\n\nsub use_context ($self, $name, $default = undef) {\n my $stack = $CONTEXT_STACKS{$name};\n return $default unless $stack && @$stack;\n return $stack->[-1];\n}\n\n# ---------------------------------------------------------------------------\n# Comment Markers\n# ---------------------------------------------------------------------------\n\nsub comment ($self, $text) {\n return \"<!--bf-$text-->\";\n}\n\n# ---------------------------------------------------------------------------\n# JS-equivalent value stringification\n# ---------------------------------------------------------------------------\n\n# Map a Perl boolean-shaped value to the JS `String(bool)` form.\n# Used by the Mojo adapter when emitting reactive attribute bindings\n# whose JS source `isBooleanResultExpr` classified as boolean \u2014\n# a comparison (`count() > 0`), a logical negation (`!ok()`), or a\n# literal `true` / `false`. Perl's auto-stringification of those\n# expressions yields `''` / `1`; Hono and Go emit `'false'` / `'true'`.\n# Centralising the bool \u2192 string mapping here keeps the contract\n# testable and the template-emit syntax tidy\n# (`<%= bf->bool_str(...) %>` vs an inline ternary).\n#\n# Contract is boolean-only: callers must have classified the\n# expression as boolean-result before routing through this helper.\n# Non-boolean values reaching here will be Perl-truthy-coerced to\n# 'true' / 'false', which is generally wrong \u2014 non-boolean attribute\n# bindings stay on the plain `<%= expr %>` emit path and never reach\n# this function.\nsub bool_str ($self, $value) {\n return $value ? 'true' : 'false';\n}\n\nsub text_start ($self, $slot_id) {\n return \"<!--bf:$slot_id-->\";\n}\n\nsub text_end ($self) {\n return \"<!--/-->\";\n}\n\n# See spec/compiler.md \"Slot identity\" for the comment-scope wire format.\nsub scope_comment ($self) {\n my $scope_id = $self->_scope_id // '';\n my $host_segment = '';\n my $host = $self->_bf_parent;\n my $mount = $self->_bf_mount;\n if (defined $host && length $host) {\n $host_segment = \"|h=$host|m=\" . ($mount // '');\n }\n my $props_json = '';\n if ($self->_props && %{$self->_props}) {\n $props_json = '|' . $self->backend->encode_json($self->_props);\n }\n return \"<!--bf-scope:$scope_id$host_segment$props_json-->\";\n}\n\n# ---------------------------------------------------------------------------\n# Script Registration\n# ---------------------------------------------------------------------------\n\nsub register_script ($self, $path) {\n return if $self->_script_seen->{$path};\n $self->_script_seen->{$path} = 1;\n push @{$self->_scripts}, $path;\n}\n\n# ---------------------------------------------------------------------------\n# Child Component Rendering\n# ---------------------------------------------------------------------------\n# (`_child_renderers` accessor is generated by the minimal accessor base above.)\n\nsub register_child_renderer ($self, $name, $renderer) {\n $self->_child_renderers->{$name} = $renderer;\n}\n\nsub render_child ($self, $name, @args) {\n my $renderer = $self->_child_renderers->{$name};\n die \"No renderer registered for child component '$name'\" unless $renderer;\n # Accept both the Mojo list form \u2014 `bf->render_child($name, k => v, ...)`\n # \u2014 and the single-hashref form \u2014 `$bf.render_child($name, { k => v })`.\n # Template languages whose method calls can't splat a hash into positional\n # args (Text::Xslate Kolon, Template Toolkit) pass one hashref instead.\n my %props = (@args == 1 && ref $args[0] eq 'HASH') ? %{ $args[0] } : @args;\n # JSX children come in via the engine's children-capture mechanism\n # (Mojo's `begin %>...<% end`, which produces a CODE ref returning a\n # Mojo::ByteStream). Materialize it through the backend before handing\n # the props to the child renderer so the child template sees\n # `$children` as already-rendered HTML. Guard on `exists` so a\n # childless invocation (`bf->render_child('counter')`) doesn't gain a\n # spurious `children => undef` key \u2014 preserving the historical \"only\n # touch children when present\" behaviour.\n $props{children} = $self->backend->materialize($props{children})\n if exists $props{children};\n return $renderer->(\\%props);\n}\n\n# ---------------------------------------------------------------------------\n# Bulk registration from build manifest\n# ---------------------------------------------------------------------------\n#\n# `bf build` emits dist/templates/manifest.json describing every\n# component the page might invoke (Counter, ui/button/index, ...).\n# This helper walks that manifest and registers one child renderer per\n# UI registry entry \u2014 the path shape `ui/<name>/index` maps to the\n# `<name>` slot key Counter.html.ep and friends use via\n# `<%= bf->render_child('<name>', ...) %>`.\n#\n# Each manifest entry carries an `ssrDefaults` hash derived statically\n# from the component's JSX (prop destructure defaults + signal /\n# memo initial values, see packages/jsx/src/ssr-defaults.ts). The\n# child renderer seeds every template variable from that hash,\n# preferring the caller's matching prop where one exists. This\n# replaces the per-component `signal_init` callback that every\n# scaffold's `app.pl` used to hand-roll for items 1/3 of issue #1416.\n#\n# `signal_init` remains as an opt-in override for cases the static\n# extractor can't see through (e.g. signal initial values that\n# reference imported helpers). When supplied for a given slot key\n# it takes precedence over the manifest's `ssrDefaults` for that\n# child, allowing callers to mix manual overrides with auto-derived\n# defaults for siblings.\nsub register_components_from_manifest ($self, $manifest, %opts) {\n my $signal_inits = $opts{signal_init} // {};\n my $parent_scope = $self->_scope_id;\n # Weaken the parent capture so the child-renderer closures stored on\n # `$self->_child_renderers` don't keep `$self` alive (the direct\n # closure <-> parent cycle). The controller is reached through `$parent`\n # at call time rather than captured strongly here, so the closures hold\n # no strong reference to `$c` either \u2014 see the controller-cycle note in\n # `new`. `$parent` is always live whenever a closure runs (the closure is\n # stored on `$parent`, so `$parent` outlives every invocation).\n weaken(my $parent = $self);\n\n for my $entry_name (keys %$manifest) {\n # `__barefoot__` is the runtime entry, not a component.\n next if $entry_name eq '__barefoot__';\n # Only UI registry components (path shape `ui/<name>/index`)\n # become child renderers; top-level page components are the\n # render target rather than a child.\n next unless $entry_name =~ m{^ui/([^/]+)/index$};\n my $slot_key = $1;\n my $marked = $manifest->{$entry_name}{markedTemplate} // '';\n next unless $marked;\n # `templates/ui/button/index.html.ep` \u2192 `ui/button/index`\n my $template_name = $marked;\n $template_name =~ s{^templates/}{};\n $template_name =~ s{\\.html\\.ep$}{};\n\n my $signal_init = $signal_inits->{$slot_key};\n my $manifest_defaults = $manifest->{$entry_name}{ssrDefaults};\n $self->register_child_renderer($slot_key, sub {\n my ($props) = @_;\n # Child shares the parent's backend so nested renders go\n # through the same engine + controller (and inherit any\n # injected json_encoder). The controller is fetched via the weak\n # `$parent` at call time \u2014 never captured strongly \u2014 so the\n # closure adds no edge to the per-request reference cycle.\n my $child_bf = BarefootJS->new($parent->c, { backend => $parent->backend });\n my $slot_id = delete $props->{_bf_slot};\n # JSX `key` (a reserved prop) \u2192 data-key on the child's scope root\n # for keyed-loop reconciliation (see `data_key_attr`).\n my $data_key = delete $props->{key};\n $child_bf->_data_key($data_key) if defined $data_key;\n $child_bf->_scope_id(\n $slot_id ? $parent_scope . '_' . $slot_id\n : $template_name . '_' . substr(rand() =~ s/^0\\.//r, 0, 6)\n );\n $child_bf->_is_child(1);\n # (#1249) Slot identity: host scope + slot id. Emitted as\n # bf-h / bf-m attributes by hydration_attrs.\n if ($slot_id) {\n $child_bf->_bf_parent($parent_scope);\n $child_bf->_bf_mount($slot_id);\n }\n $child_bf->_scripts($parent->_scripts);\n $child_bf->_script_seen($parent->_script_seen);\n\n my %extra;\n if ($signal_init) {\n %extra = $signal_init->($props);\n } elsif ($manifest_defaults) {\n %extra = _derive_stash_from_defaults($manifest_defaults, $props);\n }\n\n # Render the child template with $child_bf bound as the active\n # instance for the nested render. The backend owns the\n # engine-specific binding + restore (stash juggle for Mojo).\n my $html = $parent->backend->render_named(\n $template_name, $child_bf, { %$props, %extra },\n );\n chomp $html;\n return $html;\n });\n }\n}\n\n# Derive template-stash kvs from a manifest entry's `ssrDefaults`\n# section. Each entry shape:\n# { value => <static-fallback>, propName => <prop>, isRestProps => bool }\n# For `isRestProps`, the rest bag passes through unchanged (or the\n# static `{}` if the caller didn't supply one). For ordinary entries\n# the caller's `$props->{propName}` wins when defined, otherwise the\n# static `value` does. `propName`-less entries (signal / memo locals)\n# always use the static value \u2014 the caller cannot override them.\nsub _derive_stash_from_defaults ($defaults, $props) {\n my %extra;\n for my $name (keys %$defaults) {\n my $d = $defaults->{$name};\n if (ref($d) ne 'HASH') {\n $extra{$name} = $d;\n next;\n }\n if ($d->{isRestProps}) {\n $extra{$name} = exists $props->{$name} ? $props->{$name} : $d->{value};\n next;\n }\n my $prop_name = $d->{propName};\n if (defined $prop_name && exists $props->{$prop_name} && defined $props->{$prop_name}) {\n $extra{$name} = $props->{$prop_name};\n } else {\n $extra{$name} = $d->{value};\n }\n }\n return %extra;\n}\n\n# ---------------------------------------------------------------------------\n# Script Output\n# ---------------------------------------------------------------------------\n\nsub scripts ($self) {\n my @tags;\n for my $path (@{$self->_scripts}) {\n push @tags, qq{<script type=\"module\" src=\"$path\"></script>};\n }\n return join(\"\\n\", @tags);\n}\n\n# ---------------------------------------------------------------------------\n# Streaming SSR (Out-of-Order)\n# ---------------------------------------------------------------------------\n\nsub streaming_bootstrap ($self) {\n return q{<script>(function(){function s(id){var a=document.querySelector('[bf-async=\"'+id+'\"]');var t=document.querySelector('template[bf-async-resolve=\"'+id+'\"]');if(!a||!t)return;a.replaceChildren(t.content.cloneNode(true));a.removeAttribute('bf-async');t.remove();requestAnimationFrame(function(){if(window.__bf_hydrate)window.__bf_hydrate()})};window.__bf_swap=s})()</script>};\n}\n\nsub async_boundary ($self, $id, $fallback_html) {\n # The fallback comes in via Mojo `begin %>...<% end` capture (see\n # MojoAdapter::renderAsync), which produces a CODE ref returning a\n # Mojo::ByteStream. Materialize it through the backend so the rendered\n # HTML embeds in the placeholder rather than the CODE ref's\n # stringification.\n $fallback_html = $self->backend->materialize($fallback_html);\n return qq{<div bf-async=\"$id\">$fallback_html</div>};\n}\n\nsub async_resolve ($self, $id, $content_html) {\n return qq{<template bf-async-resolve=\"$id\">$content_html</template><script>__bf_swap(\"$id\")</script>};\n}\n\n# ---------------------------------------------------------------------------\n# JS-compat callees (#1189) \u2014 invoked from generated Mojo templates as\n# <%= bf->json($val) %>, <%= bf->floor($val) %>, etc. The MojoAdapter's\n# `templatePrimitives` registry emits these helper calls in place of the\n# corresponding JS callees (`JSON.stringify`, `Math.floor`, \u2026) so the SSR\n# template can render value-equivalent output without a JS engine.\n#\n# Failure policy mirrors the Go adapter (#1188): user-data marshalling\n# (json) bubbles errors so Mojolicious aborts loudly on cycles /\n# unsupported values rather than silently producing an empty payload.\n# Numeric coercion follows JS semantics (NaN propagates as the special\n# string 'NaN'; non-numeric input returns 'NaN' rather than 0). Strings\n# always coerce to a string representation.\n# ---------------------------------------------------------------------------\n\nsub json ($self, $value) {\n # Mojo::JSON::to_json returns a character string (not bytes), suitable\n # for embedding in HTML output via Mojo::ByteStream / `<%==`.\n #\n # Documented divergence from JS: JS distinguishes `null` (renders as\n # \"null\") from `undefined` (`JSON.stringify(undefined)` returns the\n # JS value `undefined`, not a string). Perl has no such distinction\n # \u2014 both map to `undef`. We choose the `null` rendering for SSR\n # ergonomics: an unset prop becomes the string \"null\" rather than\n # the literal text \"undefined\" or an empty attribute. Matches the\n # `null` case of JS exactly; diverges from the `undefined` case.\n return $self->backend->encode_json($value);\n}\n\nsub string ($self, $value) {\n # JS `String(v)` mirror. `undef` renders as the empty string here so\n # an unset prop doesn't surface as a literal \"undefined\" / \"null\"\n # in user-facing HTML \u2014 same divergence the Go adapter documents\n # for `bf_string`.\n return defined $value ? \"$value\" : '';\n}\n\nsub number ($self, $value) {\n # JS `Number(v)` mirror. Numeric coerces via Perl's implicit\n # numeric context; non-numeric / undef yield real numeric NaN\n # (`'nan' + 0`) so downstream arithmetic propagates correctly\n # (`Math.floor(NaN) === NaN`). Returning the literal string\n # \"NaN\" would conflate the user-passing-the-string-\"NaN\" case\n # with the parse-failure case, and break NaN detection in\n # downstream helpers.\n return 0 + 'nan' unless defined $value;\n return $value + 0 if looks_like_number($value);\n return 0 + 'nan';\n}\n\n# NaN is the only float for which `$x != $x` holds. Used as the\n# portable sentinel check in floor/ceil/round.\nsub _is_nan { my $n = shift; return $n != $n }\n\nsub floor ($self, $value) {\n my $n = $self->number($value);\n return $n if _is_nan($n);\n return POSIX::floor($n);\n}\n\nsub ceil ($self, $value) {\n my $n = $self->number($value);\n return $n if _is_nan($n);\n return POSIX::ceil($n);\n}\n\nsub round ($self, $value) {\n my $n = $self->number($value);\n return $n if _is_nan($n);\n # POSIX has no `round`. JS `Math.round` rounds half toward\n # +Infinity (so `Math.round(-1.5) === -1`, not -2). `floor(n\n # + 0.5)` reproduces that for both signs.\n return POSIX::floor($n + 0.5);\n}\n\n# ---------------------------------------------------------------------------\n# Array / String method helpers (#1448 Tier A)\n# ---------------------------------------------------------------------------\n#\n# `Array.prototype.includes(x)` and `String.prototype.includes(sub)`\n# share a method name in JS; the JSX parser can't tell the two\n# receiver shapes apart without TS type inference, so both lower to\n# the same IR node (`array-method` / method `includes`). This helper\n# dispatches at the Perl level via `ref()`:\n# - ARRAY ref: scan elements with `eq`; one defined-vs-undef\n# hop matches JS's `===` for null/undefined.\n# - scalar: `index($recv, $sub) != -1`, with both args\n# coerced through `// ''` so an undef receiver /\n# needle doesn't trip Perl's substr warning.\n# Anything else (HASH ref, code ref) returns false \u2014 matches the\n# JS semantic where `.includes` is only defined on Array /\n# TypedArray / String.\n\nsub includes ($self, $recv, $elem) {\n if (ref($recv) eq 'ARRAY') {\n for my $item (@$recv) {\n if (!defined $item) {\n return 1 if !defined $elem;\n next;\n }\n return 1 if defined $elem && $item eq $elem;\n }\n return 0;\n }\n return 0 if ref($recv);\n return index($recv // '', $elem // '') != -1 ? 1 : 0;\n}\n\n# `Array.prototype.filter(fn)` / `.every(fn)` / `.some(fn)`. The Xslate adapter\n# lowers a JS arrow predicate to a Kolon lambda (`-> $x { ... }`), which is\n# callable from Perl as a code ref, and emits `$bf.filter($arr, <lambda>)`.\n# `filter` returns a new arrayref; `every` / `some` return 1/0. Non-array /\n# empty receivers follow JS (`filter` \u2192 [], `every` \u2192 true, `some` \u2192 false).\n# (The Mojo adapter lowers these shapes inline and never reaches these methods.)\nsub filter ($self, $recv, $pred) {\n return [] unless ref($recv) eq 'ARRAY';\n return [ grep { $pred->($_) } @$recv ];\n}\n\nsub every ($self, $recv, $pred) {\n return 1 unless ref($recv) eq 'ARRAY';\n for my $item (@$recv) { return 0 unless $pred->($item) }\n return 1;\n}\n\nsub some ($self, $recv, $pred) {\n return 0 unless ref($recv) eq 'ARRAY';\n for my $item (@$recv) { return 1 if $pred->($item) }\n return 0;\n}\n\n# `Array.prototype.find(fn)` / `.findIndex(fn)` / `.findLast(fn)` /\n# `.findLastIndex(fn)` \u2014 same Kolon-lambda predicate mechanism as filter. The\n# camelCase JS names lower to these snake_case methods (like index_of /\n# last_index_of). `find` / `find_last` return the matching element (or undef \u2192\n# JS `undefined`); the index forms return the 0-based position (or -1).\nsub find ($self, $recv, $pred) {\n return undef unless ref($recv) eq 'ARRAY';\n for my $item (@$recv) { return $item if $pred->($item) }\n return undef;\n}\n\nsub find_index ($self, $recv, $pred) {\n return -1 unless ref($recv) eq 'ARRAY';\n for my $i (0 .. $#$recv) { return $i if $pred->($recv->[$i]) }\n return -1;\n}\n\nsub find_last ($self, $recv, $pred) {\n return undef unless ref($recv) eq 'ARRAY';\n for my $i (reverse 0 .. $#$recv) { return $recv->[$i] if $pred->($recv->[$i]) }\n return undef;\n}\n\nsub find_last_index ($self, $recv, $pred) {\n return -1 unless ref($recv) eq 'ARRAY';\n for my $i (reverse 0 .. $#$recv) { return $i if $pred->($recv->[$i]) }\n return -1;\n}\n\n# `String.prototype.toLowerCase()` / `.toUpperCase()`. Kolon has a builtin\n# `.join` array method (so the adapter uses that directly) but no builtin\n# `lc` / `uc`, so these live on the runtime object. `CORE::` avoids recursing\n# into these methods.\nsub lc ($self, $s) { return defined $s ? CORE::lc($s) : '' }\nsub uc ($self, $s) { return defined $s ? CORE::uc($s) : '' }\n\n# `Array.prototype.join(sep)` with JS semantics: separator defaults to \",\",\n# and undefined / null elements render as empty (`[1,,2].join(\",\")` \u2192 \"1,,2\").\n# Kolon has a builtin `.join`, but routing through the runtime keeps the\n# JS-compat element handling in one place. `CORE::join` avoids recursing.\nsub join ($self, $recv, $sep = undef) {\n return '' unless ref($recv) eq 'ARRAY';\n $sep //= ',';\n return CORE::join($sep, map { defined $_ ? $_ : '' } @$recv);\n}\n\n# `.length` \u2014 JS works on BOTH arrays (element count) and strings (character\n# count); Kolon's builtin `.size()` is array-only and faults on a string. So\n# dispatch on ref type here. `CORE::length` avoids recursing into this method.\nsub length ($self, $recv) {\n return scalar @$recv if ref($recv) eq 'ARRAY';\n return 0 if ref($recv);\n return CORE::length($recv // '');\n}\n\n# `Array.prototype.indexOf(x)` / `Array.prototype.lastIndexOf(x)`\n# value-equality search (#1448 Tier A). Returns the 0-based position\n# of the first / last matching element, or -1 if not found.\n# Non-array receivers return -1 \u2014 matches the JS semantic that\n# `.indexOf` / `.lastIndexOf` are only defined on Array / TypedArray.\n# (The string-position `indexOf` form isn't in Tier A; if it lands\n# later the helper can grow a ref()-dispatch branch like `includes`.)\n\nsub _array_index_of ($recv, $elem, $reverse) {\n return -1 unless ref($recv) eq 'ARRAY';\n my @indices = $reverse ? (reverse 0 .. $#{$recv}) : (0 .. $#{$recv});\n for my $i (@indices) {\n my $item = $recv->[$i];\n if (!defined $item) {\n return $i if !defined $elem;\n next;\n }\n return $i if defined $elem && $item eq $elem;\n }\n return -1;\n}\n\nsub index_of ($self, $recv, $elem) {\n return _array_index_of($recv, $elem, 0);\n}\n\nsub last_index_of ($self, $recv, $elem) {\n return _array_index_of($recv, $elem, 1);\n}\n\n# `Array.prototype.at(i)` \u2014 supports negative indices (`.at(-1)` is\n# the last element); out-of-bounds returns undef (which Mojo's\n# auto-escape renders as the empty string, matching JS's `undefined`).\n# Non-array receivers return undef. Matches the Go `bf_at` arithmetic\n# (`length + i` for i < 0) so adapter output stays symmetric.\n\nsub at ($self, $recv, $i) {\n return undef unless ref($recv) eq 'ARRAY';\n return undef if !defined $i;\n my $len = scalar @$recv;\n return undef if $len == 0;\n my $idx = $i < 0 ? $len + $i : $i;\n return undef if $idx < 0 || $idx >= $len;\n return $recv->[$idx];\n}\n\n# `Array.prototype.concat(other)` \u2014 merges two arrays in order\n# into a new ARRAY ref. Non-array operands collapse to empty\n# (matches the Go `bf_concat` semantic so cross-adapter output\n# stays symmetric; differs from JS where a non-Array argument\n# with `Symbol.isConcatSpreadable` would be spread, a behaviour\n# the template-language path never observes).\n\nsub concat ($self, $a, $b) {\n my @out;\n push @out, @$a if ref($a) eq 'ARRAY';\n push @out, @$b if ref($b) eq 'ARRAY';\n return \\@out;\n}\n\n# `Array.prototype.slice(start, end?)` \u2014 carves out a sub-range\n# into a new ARRAY ref. Mirrors the Go `bf_slice` arithmetic so\n# adapter output stays symmetric:\n# - start < 0 \u2192 length + start (e.g. -1 = last index)\n# - end < 0 \u2192 length + end\n# - start < 0 after clamp \u2192 0\n# - end > length \u2192 length\n# - start >= end \u2192 empty\n# - end undef \u2192 \"to length\"\n# Non-array receivers return an empty ARRAY ref.\n\nsub slice ($self, $recv, $start, $end) {\n return [] unless ref($recv) eq 'ARRAY';\n my $len = scalar @$recv;\n return [] if $len == 0;\n\n my $s = $start // 0;\n $s = $len + $s if $s < 0;\n $s = 0 if $s < 0;\n $s = $len if $s > $len;\n\n my $e = defined $end ? $end : $len;\n $e = $len + $e if $e < 0;\n $e = 0 if $e < 0;\n $e = $len if $e > $len;\n\n return [] if $s >= $e;\n return [ @{$recv}[$s .. $e - 1] ];\n}\n\n# `Array.prototype.reverse()` / `Array.prototype.toReversed()` \u2014\n# both shapes share this lowering. SSR templates render a snapshot\n# of state, so JS's mutate-receiver (`reverse`) vs\n# return-new-array (`toReversed`) distinction has no template-\n# level meaning. Always returns a new ARRAY ref to keep callers\n# safe from accidental aliasing. Non-array receivers return an\n# empty ARRAY ref.\n\nsub reverse ($self, $recv) {\n return [] unless ref($recv) eq 'ARRAY';\n return [ reverse @$recv ];\n}\n\n# `Array.prototype.flat(depth?)` (#1448 Tier C) \u2014 flatten nested ARRAY\n# refs `$depth` levels deep. A `$depth` of -1 is the `Infinity` sentinel\n# (flatten fully); 0 returns a shallow copy. Non-ARRAY elements are kept\n# as-is (JS only flattens nested arrays). Non-ARRAY receiver \u2192 [].\nsub flat ($self, $recv, $depth = 1) {\n return [] unless ref($recv) eq 'ARRAY';\n my @out;\n for my $el (@$recv) {\n if ($depth != 0 && ref($el) eq 'ARRAY') {\n my $next = $depth > 0 ? $depth - 1 : $depth;\n push @out, @{ $self->flat($el, $next) };\n }\n else {\n push @out, $el;\n }\n }\n return \\@out;\n}\n\n# `Array.prototype.flatMap(fn)` value-returning field projection\n# (#1448 Tier C) \u2014 map each element through a self / field projection,\n# then flatten one level. `field` reads a HASH-ref key (the raw JS prop\n# name, as `bf->reduce` does); a projected non-ARRAY value is kept as-is\n# (flatMap = map + flat(1)). Non-ARRAY receiver \u2192 [].\nsub flat_map ($self, $recv, $key_kind, $key) {\n return [] unless ref($recv) eq 'ARRAY';\n my @projected;\n for my $el (@$recv) {\n if ($key_kind eq 'field') {\n # JS `i => i.field` on a non-object yields `undefined`, not the\n # element itself \u2014 push `undef` so a scalar element doesn't leak\n # into the output (matches Go's `getFieldValue` returning nil).\n push @projected, ref($el) eq 'HASH' ? $el->{$key} : undef;\n }\n else {\n push @projected, $el;\n }\n }\n return $self->flat(\\@projected, 1);\n}\n\n# `Array.prototype.flatMap(i => [i.a, i.b])` \u2014 array-literal tuple\n# projection (#1448 Tier C). Each `@specs` entry is a [kind, key] arrayref\n# (['self', ''] or ['field', 'a']). For each element, every leaf's value\n# is appended in order. flat(1) removes only the literal wrapper, so an\n# array-valued leaf is appended verbatim (no spread) \u2014 i.e. just append\n# each leaf. A non-HASH element under a `field` leaf yields undef (JS\n# `i.field` on a non-object). Non-ARRAY receiver \u2192 [].\nsub flat_map_tuple ($self, $recv, @specs) {\n return [] unless ref($recv) eq 'ARRAY';\n my @out;\n for my $el (@$recv) {\n for my $spec (@specs) {\n my ($kind, $key) = @$spec;\n if ($kind eq 'field') {\n push @out, ref($el) eq 'HASH' ? $el->{$key} : undef;\n }\n else {\n push @out, $el;\n }\n }\n }\n return \\@out;\n}\n\n# `String.prototype.trim()` \u2014 strip leading + trailing whitespace.\n# JS's `String.prototype.trim` matches `\\s` in the Unicode sense\n# (any whitespace including non-breaking space U+00A0); Perl's `\\s`\n# inside a regex with `/u` flag is the same. Undef receivers return\n# the empty string (matches JS's `String(undefined).trim()` which\n# would be \"undefined\" \u2192 \"undefined\", but in our template context\n# undef commonly means \"missing prop\"; rendering the empty string\n# is the safer choice and mirrors the JS-compat divergence we\n# already document for `bf->string(undef) === \"\"`).\n\nsub trim ($self, $recv) {\n return '' unless defined $recv;\n return '' if ref($recv);\n my $s = \"$recv\";\n $s =~ s/^\\s+|\\s+$//gu;\n return $s;\n}\n\n# `String.prototype.split(sep)` (#1448 Tier B) \u2014 string \u2192 ARRAY ref.\n#\n# Two JS-parity wrinkles drive the helper (a bare `split` emit would\n# diverge from both JS and Go):\n#\n# * Perl's `split` treats its first argument as a *regex*, so a\n# separator like '.' or '|' would match far too much. We\n# `quotemeta` it to force literal-string matching, mirroring JS's\n# string-separator semantics (the regex-separator form stays\n# refused upstream \u2014 see the parser arm).\n# * Perl's `split` drops trailing empty fields by default; JS keeps\n# them (`\"a,\".split(\",\")` is `[\"a\", \"\"]`). Passing the `-1` limit\n# preserves them, matching JS and Go's `strings.Split`.\n#\n# An empty separator splits into individual characters (JS + Go agree).\n# Undef receiver renders as the single-element `['']` \u2014 the same\n# \"missing prop \u2192 empty string\" convention `bf->trim` uses.\n\nsub split ($self, $recv, $sep = undef, $limit = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n\n my @parts;\n if (!defined $sep) {\n # No separator \u2192 the whole string in a single-element array\n # (matches JS `\"x\".split()` / `.split(undefined)`).\n @parts = ($s);\n }\n elsif (\"$sep\" eq '') {\n # Empty separator \u2192 individual characters. No `-1` limit here:\n # on an empty pattern Perl's `split` with `-1` appends a spurious\n # trailing empty field (\"abc\" \u2192 'a','b','c',''), which JS/Go don't.\n @parts = split //, $s;\n }\n elsif ($s eq '') {\n # Empty input with a non-empty separator: JS `\"\".split(\",\")` is\n # `[\"\"]` and Go's `strings.Split(\"\", \",\")` is `[\"\"]`, but Perl's\n # `split /,/, ''` returns the empty list \u2014 special-case for parity.\n @parts = ('');\n }\n else {\n # `quotemeta` forces literal-string matching (JS string-separator\n # semantics); the `-1` keeps trailing empty fields (JS keeps them,\n # Perl's bare `split` drops them).\n my $q = quotemeta(\"$sep\");\n @parts = split /$q/, $s, -1;\n }\n\n # Optional `limit` caps the number of pieces (JS `split(sep, limit)`).\n # 0 \u2192 empty; a negative limit keeps all (JS ToUint32 wrap makes it\n # effectively unbounded) \u2014 both match Go's `bf_split`.\n if (defined $limit) {\n my $n = int($limit);\n if ($n == 0) { @parts = () }\n elsif ($n > 0 && $n < scalar @parts) { @parts = @parts[0 .. $n - 1] }\n }\n\n return [@parts];\n}\n\n# `String.prototype.startsWith(prefix, position?)` (#1448 Tier B) \u2014\n# string \u2192 boolean (1 / 0). `substr`-anchored literal comparison mirrors\n# Go's `strings.HasPrefix`. An empty prefix is always true (JS parity);\n# undef / non-string receivers coerce to the empty string first. The\n# optional `position` re-anchors the test (clamped to `[0, length]`),\n# matching JS `\"abc\".startsWith(\"b\", 1)`.\n\nsub starts_with ($self, $recv, $prefix, $position = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $p = defined $prefix ? \"$prefix\" : '';\n if (defined $position) {\n my $n = int($position);\n $n = 0 if $n < 0;\n $n = CORE::length($s) if $n > CORE::length($s);\n $s = substr($s, $n);\n }\n return substr($s, 0, CORE::length $p) eq $p ? 1 : 0;\n}\n\n# `String.prototype.endsWith(suffix, endPosition?)` (#1448 Tier B) \u2014\n# string \u2192 boolean (1 / 0). Mirrors Go's `strings.HasSuffix`. An empty\n# suffix is always true (JS parity); a suffix longer than the string is\n# false. `substr($s, -length $x)` would mis-read the whole string when\n# `length $x == 0`, so that case short-circuits. The optional\n# `endPosition` treats the string as if it were only that many chars\n# long (clamped to `[0, length]`), matching JS `\"abc\".endsWith(\"b\", 2)`.\n\nsub ends_with ($self, $recv, $suffix, $end_position = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $x = defined $suffix ? \"$suffix\" : '';\n if (defined $end_position) {\n my $e = int($end_position);\n $e = 0 if $e < 0;\n $e = CORE::length($s) if $e > CORE::length($s);\n $s = substr($s, 0, $e);\n }\n return 1 if $x eq '';\n return 0 if CORE::length($s) < CORE::length($x);\n return substr($s, -CORE::length $x) eq $x ? 1 : 0;\n}\n\n# `String.prototype.replace(pattern, replacement)` \u2014 string-pattern\n# form only (#1448 Tier B), replacing the FIRST occurrence (JS string-\n# pattern semantics). Spliced via index/substr rather than `s///` so\n# BOTH the pattern and the replacement are literal: no Perl regex\n# metacharacters in the pattern and no `$1` / `$&` interpolation in the\n# replacement. Go's `bf_replace` (strings.Replace, n=1) treats the\n# replacement literally too, so the two adapters stay byte-equal \u2014 this\n# diverges from JS only for replacement strings containing `$`-patterns\n# (rare in template position). An empty pattern inserts the replacement\n# at the front (`\"abc\".replace(\"\", \"X\")` \u2192 \"Xabc\"), matching JS + Go.\n\nsub replace ($self, $recv, $pattern, $replacement) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $o = defined $pattern ? \"$pattern\" : '';\n my $n = defined $replacement ? \"$replacement\" : '';\n return $n . $s if $o eq '';\n my $i = index($s, $o);\n return $s if $i < 0;\n return substr($s, 0, $i) . $n . substr($s, $i + CORE::length($o));\n}\n\n# `String.prototype.repeat(n)` \u2014 the receiver concatenated n times\n# (#1448 Tier B), via Perl's `x` operator. JS throws RangeError for a\n# negative count, but SSR templates degrade to the empty string rather\n# than dying mid-render, so a count <= 0 returns \"\" (Go's `bf_repeat`\n# applies the same clamp). The count is truncated toward zero\n# (`int`), matching JS's ToIntegerOrInfinity on `\"a\".repeat(3.7)`.\n\nsub repeat ($self, $recv, $count) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n my $n = defined $count ? int($count) : 0;\n return $n <= 0 ? '' : $s x $n;\n}\n\n# `String.prototype.padStart` / `padEnd` (#1448 Tier B) \u2014 pad the\n# receiver to `$target` characters with `$pad` (default a single space)\n# repeated and truncated to fill, prepended or appended. Length is\n# measured in characters (Perl `length`), matching Go's rune-based\n# `bf_pad_*` \u2014 diverges from JS's UTF-16-unit length only for\n# astral-plane input. An empty pad, or a receiver already >= `$target`,\n# returns the receiver unchanged (JS parity). The `$target` is\n# truncated toward zero (JS ToLength on the first arg).\n\nsub _pad ($s, $target, $pad, $at_start) {\n $pad = ' ' unless defined $pad;\n $pad = \"$pad\";\n return $s if $pad eq '';\n my $len = CORE::length $s;\n my $t = int($target // 0);\n return $s if $len >= $t;\n my $need = $t - $len;\n # Repeat enough copies to cover $need, then trim to exactly $need.\n my $fill = substr($pad x (int($need / CORE::length($pad)) + 1), 0, $need);\n return $at_start ? $fill . $s : $s . $fill;\n}\n\nsub pad_start ($self, $recv, $target, $pad = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n return _pad($s, $target, $pad, 1);\n}\n\nsub pad_end ($self, $recv, $target, $pad = undef) {\n my $s = defined $recv && !ref($recv) ? \"$recv\" : '';\n return _pad($s, $target, $pad, 0);\n}\n\n# `Array.prototype.sort(cmp)` / `Array.prototype.toSorted(cmp)`\n# lowering (#1448 Tier B). Non-mutating \u2014 JS's mutate-vs-new\n# distinction is moot in SSR template context.\n#\n# Opts hash-ref. The compiler emits a `keys` list of per-key hashes\n# in priority order; each hash carries:\n#\n# key_kind => 'self' | 'field'\n# key => '' when key_kind eq 'self'; field name verbatim\n# from the comparator AST (e.g. 'price', 'createdAt')\n# when key_kind eq 'field' \u2014 no case normalisation\n# applied. Perl hash lookups are case-sensitive so\n# the key here must match the actual hash key the\n# user populated.\n# compare_type => 'numeric' | 'string' | 'auto'\n# direction => 'asc' | 'desc'\n#\n# Accepted comparator catalogue (gated upstream at parse time \u2014\n# anything outside refuses with BF101 before reaching this helper):\n#\n# (a,b) => a.f - b.f \u2192 field, numeric\n# (a,b) => a - b \u2192 self, numeric\n# (a,b) => a[.f].localeCompare(b[.f]) \u2192 field|self, string\n# (a,b) => a.f > b.f ? 1 : -1 \u2192 field|self, auto\n# any of the above ||-chained \u2192 multi-key tie-breaks\n# (and reversed-operand variants for `desc`).\n#\n# `auto` (relational-ternary lowering) compares numerically when both\n# keys `looks_like_number`, else lexically \u2014 Go's `bf_sort` applies the\n# same rule so the two template adapters stay byte-equal.\n#\n# A future `nulls => 'first' | 'last'` knob can land per key without\n# churn \u2014 the opts hash is the right place to grow.\n\nsub sort ($self, $recv, $opts = {}) {\n return [] unless ref($recv) eq 'ARRAY';\n\n # Normalise the per-key specs (priority order, length >= 1).\n my @spec = map {\n {\n key_kind => $_->{key_kind} // 'self',\n key => $_->{key} // '',\n compare_type => $_->{compare_type} // 'numeric',\n direction => $_->{direction} // 'asc',\n }\n } @{ $opts->{keys} // [] };\n return [ @$recv ] unless @spec;\n\n # Schwartzian transform: project each item to all its sort keys\n # once, then compare projected keys. Cheaper than re-resolving the\n # field accessors inside every comparison for non-trivial arrays.\n my @keyed = map {\n my $item = $_;\n my @ks = map {\n $_->{key_kind} eq 'field' && ref($item) eq 'HASH' ? $item->{ $_->{key} } : $item;\n } @spec;\n [ \\@ks, $item ];\n } @$recv;\n\n my $cmp = sub {\n for my $i (0 .. $#spec) {\n my $sp = $spec[$i];\n my $c = _compare_sort_key($a->[0][$i], $b->[0][$i], $sp->{compare_type});\n next if $c == 0; # tie on this key \u2014 try the next\n return $sp->{direction} eq 'desc' ? -$c : $c;\n }\n return 0;\n };\n\n my @sorted = sort $cmp @keyed;\n return [ map { $_->[1] } @sorted ];\n}\n\n# Compare two projected keys, ascending orientation (-1 / 0 / 1); the\n# caller negates for 'desc'. 'auto' compares numerically when both\n# keys look like numbers, else lexically (matches Go's `bf_sort`).\n# undef coalesces to '' / 0 so the order stays total without warnings.\nsub _compare_sort_key ($av, $bv, $compare_type) {\n if ($compare_type eq 'string') {\n return ($av // '') cmp ($bv // '');\n }\n if ($compare_type eq 'auto') {\n if (looks_like_number($av // '') && looks_like_number($bv // '')) {\n return ($av // 0) <=> ($bv // 0);\n }\n return ($av // '') cmp ($bv // '');\n }\n return ($av // 0) <=> ($bv // 0); # numeric\n}\n\n# Fold an array into a scalar via the arithmetic-fold catalogue\n# (#1448 Tier C). Mirrors Go's `bf_reduce` and JS `reduce(fn, init)` /\n# `reduceRight(fn, init)` for the shapes `(acc, x) => acc <op> x` /\n# `(acc, x) => acc <op> x.field`:\n#\n# bf->reduce($recv, {\n# op => '+' | '*',\n# key_kind => 'self' | 'field',\n# key => '<field>', # when key_kind eq 'field'\n# type => 'numeric' | 'string',\n# init => <seed>, # number, or string for concat\n# direction => 'left' | 'right', # 'right' = reduceRight (default 'left')\n# })\n#\n# Numeric folds accumulate with `+` / `*` (non-numeric keys coalesce to\n# 0); string folds concatenate via `bf->string` (undef \u2192 ''). The init\n# seeds the accumulator, so an empty array returns it unchanged \u2014 exactly\n# like JS. `direction => 'right'` folds right-to-left (reduceRight); only\n# observable for string concat, since numeric sum / product commute.\n# Float stringification can diverge from Go's for inexact binary\n# fractions (e.g. 0.1 + 0.2); integer sums \u2014 the common case \u2014 agree.\nsub reduce ($self, $recv, $opts = {}) {\n my $op = $opts->{op} // '+';\n my $key_kind = $opts->{key_kind} // 'self';\n my $key = $opts->{key} // '';\n my $type = $opts->{type} // 'numeric';\n my $direction = $opts->{direction} // 'left';\n\n my @items = ref($recv) eq 'ARRAY' ? @$recv : ();\n # reduceRight folds right-to-left; reversing the snapshot keeps the\n # single forward loop below. Only observable for string concat \u2014\n # numeric sum / product commute. Qualify as CORE::reverse \u2014 this\n # package defines `sub reverse` (the `.reverse()` helper), so a bare\n # `reverse` is ambiguous under `use warnings`.\n @items = CORE::reverse(@items) if $direction eq 'right';\n my $project = sub ($item) {\n $key_kind eq 'field' && ref($item) eq 'HASH' ? $item->{$key} : $item;\n };\n\n if ($type eq 'string') {\n my $acc = $opts->{init} // '';\n $acc .= $self->string($project->($_)) for @items;\n return $acc;\n }\n\n my $acc = $opts->{init} // 0;\n for my $item (@items) {\n my $n = $project->($item);\n # Guard `defined` before `looks_like_number` so a missing field\n # (undef) folds as 0 without an \"uninitialized value\" warning\n # under `use warnings` \u2014 matching the `$av // ''` style `sort` uses.\n $n = 0 unless defined $n && looks_like_number($n);\n $op eq '*' ? ($acc *= $n) : ($acc += $n);\n }\n return $acc;\n}\n\n# ---------------------------------------------------------------------------\n# JSX intrinsic-element spread (#1407)\n# ---------------------------------------------------------------------------\n#\n# Mirrors the JS `spreadAttrs` runtime\n# (`packages/client/src/runtime/spread-attrs.ts`) and the Go adapter's\n# `bf.SpreadAttrs` so SSR output stays byte-equal across the three\n# adapters. Generated Mojo templates invoke this as\n# `<%== bf->spread_attrs($bag) %>`.\n#\n# Skip rules: nil/false values, event handlers (`on[A-Z]\u2026` shape\n# matching JS `key[2] === key[2].toUpperCase()` \u2014 true for any\n# character whose uppercase is itself, including digits and\n# underscore), `children`. `ref` is intentionally NOT filtered,\n# matching the JS reference.\n#\n# Key remap: className \u2192 class, htmlFor \u2192 for; SVG camelCase\n# attrs preserved (case-sensitive XML spec); other camelCase keys\n# lowered to kebab-case with a leading `-` for an initial\n# uppercase letter (mirrors JS `key.replace(/([A-Z])/g, '-$1')`).\n#\n# `style` is routed through `_style_to_css` so object literals\n# serialise to a real CSS string instead of Perl's default\n# `HASH(0x...)` form.\n#\n# Output is deterministic: keys are sorted alphabetically before\n# emission, matching the Go adapter's `sort.Strings(keys)` policy\n# and Mojo::JSON's marshal order.\n#\n# The return value is a Mojo::ByteStream so the calling template's\n# `<%==` raw-emit skips re-escaping (the helper has already\n# HTML-escaped each value).\n\nmy %SVG_CAMEL_CASE_ATTRS = map { $_ => 1 } qw(\n allowReorder attributeName attributeType autoReverse\n baseFrequency baseProfile calcMode clipPathUnits\n contentScriptType contentStyleType diffuseConstant edgeMode\n externalResourcesRequired filterRes filterUnits glyphRef\n gradientTransform gradientUnits kernelMatrix kernelUnitLength\n keyPoints keySplines keyTimes lengthAdjust limitingConeAngle\n markerHeight markerUnits markerWidth maskContentUnits\n maskUnits numOctaves pathLength patternContentUnits\n patternTransform patternUnits pointsAtX pointsAtY pointsAtZ\n preserveAlpha preserveAspectRatio primitiveUnits refX refY\n repeatCount repeatDur requiredExtensions requiredFeatures\n specularConstant specularExponent spreadMethod startOffset\n stdDeviation stitchTiles surfaceScale systemLanguage\n tableValues targetX targetY textLength viewBox viewTarget\n xChannelSelector yChannelSelector zoomAndPan\n);\n\nsub _to_attr_name ($key) {\n return 'class' if $key eq 'className';\n return 'for' if $key eq 'htmlFor';\n return $key if $SVG_CAMEL_CASE_ATTRS{$key};\n # camelCase \u2192 kebab-case, with a leading `-` for an initial\n # uppercase letter (JS-reference parity, even though that case\n # produces an HTML-invalid attribute name \u2014 same documented\n # behaviour as the Go adapter's `toAttrName`).\n my $out = $key;\n $out =~ s/([A-Z])/-\\L$1/g;\n return $out;\n}\n\nsub _html_escape ($value) {\n # HTML attribute-value escape for SSR string emission. The\n # spread bag's values reach the browser as part of a generated\n # `key=\"...\"` substring inside the rendered HTML, so the\n # escape set has to cover everything that could break either\n # the surrounding double-quoted attribute or the enclosing\n # tag: `&`, `<`, `>`, `\"`, and `'`. Matches Go's\n # `template.HTMLEscapeString` semantics byte-for-byte (using\n # `"` / `'` for quotes rather than the named entities)\n # so the SSR output is identical across the Go and Mojo\n # adapters (#1407, #1413 review). The CSR-side\n # `applyRestAttrs` calls `el.setAttribute(name, String(value))`\n # \u2014 which does its own DOM-level escaping in the browser \u2014\n # so JS doesn't need an explicit escape pass; Perl/Go emit a\n # string, so we do.\n my $s = defined $value ? \"$value\" : '';\n $s =~ s/&/&/g;\n $s =~ s/</</g;\n $s =~ s/>/>/g;\n $s =~ s/\"/"/g;\n $s =~ s/'/'/g;\n return $s;\n}\n\nsub _style_to_css ($value) {\n return undef unless defined $value;\n # Non-hashref values pass through stringified \u2014 matches the JS\n # `typeof value !== 'object'` branch in `styleToCss`.\n if (ref($value) ne 'HASH') {\n my $s = \"$value\";\n return CORE::length $s ? $s : undef;\n }\n my @parts;\n for my $key (sort keys %$value) {\n my $v = $value->{$key};\n next unless defined $v;\n my $prop = $key;\n $prop =~ s/([A-Z])/-\\L$1/g;\n push @parts, \"$prop:$v\";\n }\n return @parts ? CORE::join(';', @parts) : undef;\n}\n\nsub spread_attrs ($self, $bag) {\n return '' unless defined $bag && ref($bag) eq 'HASH';\n my @parts;\n for my $key (sort keys %$bag) {\n # Event handlers: skip when key starts `on` and the third\n # character is its own uppercase form (uppercase letter,\n # digit, underscore, \u2026). Mirrors the JS predicate.\n if (CORE::length($key) > 2 && substr($key, 0, 2) eq 'on') {\n my $c = substr($key, 2, 1);\n next if CORE::uc($c) eq $c;\n }\n next if $key eq 'children';\n my $val = $bag->{$key};\n # null / undef \u2192 drop.\n next unless defined $val;\n # Boolean values arrive as Mojo::JSON sentinel objects\n # (`Mojo::JSON::true` / `false`) \u2014 both from JSON-deserialised\n # props and from the test harness's `toPerlLiteral`\n # (which emits the sentinels rather than plain 0/1 to avoid\n # conflating booleans with numeric attribute values like\n # `tabindex=\"0\"`). The contract is: callers MUST use the\n # sentinels for boolean values; plain Perl scalars 0/1\n # render as numeric attribute values, matching how JS\n # `spreadAttrs` treats a `0`/`1` JS number.\n if (ref($val) eq 'JSON::PP::Boolean' || ref($val) eq 'Mojo::JSON::_Bool') {\n next unless $val;\n push @parts, _to_attr_name($key);\n next;\n }\n # `style` routes through `_style_to_css` so object literals\n # serialise to a real CSS string.\n if ($key eq 'style') {\n my $css = _style_to_css($val);\n next unless defined $css && CORE::length $css;\n push @parts, qq{style=\"} . _html_escape($css) . qq{\"};\n next;\n }\n my $name = _to_attr_name($key);\n push @parts, $name . qq{=\"} . _html_escape($val) . qq{\"};\n }\n return '' unless @parts;\n # Mark the result raw so the calling template's `<%==` raw-emit\n # doesn't re-escape the already-escaped values (the Mojo backend\n # returns a Mojo::ByteStream).\n return $self->backend->mark_raw(CORE::join(' ', @parts));\n}\n\n1;\n__END__\n\n=encoding utf8\n\n=head1 NAME\n\nBarefootJS - Engine- and framework-agnostic server runtime for BarefootJS marked templates\n\n=head1 SYNOPSIS\n\n use BarefootJS;\n\n # A host injects a rendering backend (see BarefootJS::Backend::Xslate or\n # Mojolicious::Plugin::BarefootJS for shipping backends).\n my $bf = BarefootJS->new($context, { backend => $backend });\n\n # The compiled marked template calls the runtime as a `bf` object:\n # <: $bf.scope_attr() :> <: $bf.json($data) :> <: $bf.spread_attrs($h) :>\n\n=head1 DESCRIPTION\n\nBarefootJS compiles JSX/TSX into a marked template plus client JS. This module\nis the server-side runtime the marked templates call into at render time. It is\ndeliberately template-engine- and web-framework-agnostic: every operation that\ndepends on I<how> a template is rendered \u2014 JSON marshalling, raw-string marking,\nJSX-children materialisation, and named-template rendering \u2014 is delegated to a\npluggable C<backend>.\n\nThat design lets the one runtime drive any backend. Shipping backends:\n\n=over 4\n\n=item * L<BarefootJS::Backend::Xslate> \u2014 Text::Xslate (Kolon); runs under any PSGI/Plack app.\n\n=item * L<BarefootJS::Backend::Mojo> \u2014 Mojolicious (via L<Mojolicious::Plugin::BarefootJS>).\n\n=back\n\nThe core itself pulls in only core Perl modules (C<POSIX>, C<Scalar::Util>);\nno template engine or web framework is loaded unless a backend that needs one\nis used.\n\n=head1 SEE ALSO\n\nL<BarefootJS::Backend::Xslate>, L<Mojolicious::Plugin::BarefootJS>,\nL<https://github.com/piconic-ai/barefootjs>\n\n=head1 AUTHOR\n\nkobaken E<lt>kentafly88@gmail.comE<gt>\n\n=head1 LICENSE\n\nCopyright (c) 2025-present BarefootJS Contributors.\n\nThis library is free software; you can redistribute it and/or modify it under\nthe MIT License. See the F<LICENSE> file in the distribution for the full text.\n\n=cut\n";
|
|
23550
23568
|
barefootBackendMojoPmSource = "package BarefootJS::Backend::Mojo;\nour $VERSION = \"0.8.0\";\nuse Mojo::Base -base, -signatures;\n\nuse Mojo::ByteStream qw(b);\nuse Mojo::JSON qw(to_json);\nuse Scalar::Util qw(weaken);\n\n# ---------------------------------------------------------------------------\n# Reference rendering backend (Mojolicious / Mojo::Template).\n# ---------------------------------------------------------------------------\n#\n# BarefootJS.pm holds all the template-engine-agnostic logic (the JS-compat\n# value helpers, array/string methods, hydration markers). Everything that is\n# specific to *how a template is rendered* \u2014 JSON marshalling, raw-string\n# marking, JSX-children materialisation, and named-template rendering \u2014 lives\n# behind this backend object so the same runtime can drive a different Perl\n# template engine (Text::Xslate, Template Toolkit, \u2026) without rewriting the\n# helper surface.\n#\n# A backend MUST implement:\n# - encode_json($data) -> string\n# - mark_raw($str) -> value the engine emits without escaping\n# - materialize($value) -> string (resolve a captured-children ref)\n# - render_named($name, $bf, \\%vars) -> string\n#\n# This Mojo implementation is the reference. To target another engine, write a\n# sibling backend (BarefootJS::Backend::Xslate, \u2026) implementing the same four\n# methods and pass it via `BarefootJS->new($c, { backend => $b })`.\n\n# The Mojolicious controller. Optional: the value-marshalling helpers\n# (`encode_json` / `mark_raw` / `materialize`) work without it; only\n# `render_named` reaches into the controller's renderer + stash.\nhas 'c';\n\n# Pluggable JSON encoder (#engine-abstraction). Defaults to\n# `Mojo::JSON::to_json`, which returns a *character* string (not bytes)\n# suitable for embedding in HTML output via `<%==` / Mojo::ByteStream.\n#\n# Override with any `sub ($data) { ... }` to swap in a faster XS encoder \u2014\n# e.g. `json_encoder => sub { Cpanel::JSON::XS->new->canonical->encode($_[0]) }`.\n# The pure-Perl JSON::PP fallback Mojo::JSON uses can be a hot spot for large\n# props payloads; the seam lets a host pick its own implementation without\n# touching the runtime.\nhas 'json_encoder' => sub { \\&to_json };\n\n# Hold the controller weakly for the same reason BarefootJS does: the\n# controller owns the bf instance (which owns this backend) via its stash,\n# so a strong back-reference would close a per-request cycle the refcount GC\n# can't reclaim. `render_named` only touches `$self->c` mid-render, while the\n# controller is still alive on the request stack.\nsub new ($class, %args) {\n my $self = $class->SUPER::new(%args);\n weaken($self->{c}) if $self->{c};\n return $self;\n}\n\nsub encode_json ($self, $data) {\n return $self->json_encoder->($data);\n}\n\n# Mark a string as already-safe so the template engine emits it verbatim\n# (no re-escaping). In Mojo this is a Mojo::ByteStream, which the calling\n# template's `<%==` raw-emit passes through unescaped.\nsub mark_raw ($self, $str) {\n return b($str);\n}\n\n# JSX children / async fallbacks arrive via Mojo's `begin %>...<% end`\n# capture, which produces a CODE ref returning a Mojo::ByteStream. Resolve\n# it to a string before embedding. Plain (already-rendered) strings pass\n# through unchanged.\nsub materialize ($self, $value) {\n return ref($value) eq 'CODE' ? $value->() : $value;\n}\n\n# Render a named template with `$child_bf` bound as the active runtime\n# instance for that render. The Mojo `bf` helper resolves the current\n# instance off `$c->stash->{'bf.instance'}`; swap it for the duration of\n# the nested render and restore it afterwards so sibling renders are\n# unaffected.\nsub render_named ($self, $template_name, $child_bf, $vars) {\n my $c = $self->c;\n my $prev = $c->stash->{'bf.instance'};\n $c->stash->{'bf.instance'} = $child_bf;\n my $html = $c->render_to_string(template => $template_name, %$vars);\n $c->stash->{'bf.instance'} = $prev;\n return $html;\n}\n\n1;\n";
|
|
23551
23569
|
barefootPluginPmSource = "package Mojolicious::Plugin::BarefootJS;\nour $VERSION = \"0.8.0\";\nuse Mojo::Base 'Mojolicious::Plugin', -signatures;\n\nuse Mojo::File qw(path);\nuse Mojo::JSON qw(decode_json);\n\nuse BarefootJS;\n\n# Plugin entry point. Wires up:\n#\n# 1. The `bf` controller helper. Lazily instantiates one\n# BarefootJS object per request and stashes it under\n# `bf.instance`.\n#\n# 2. A `before_render` hook that, when the rendered template name\n# matches a top-level component in the build manifest, fills the\n# heavy boilerplate the user previously hand-rolled in `app.pl`:\n# generates the scope id, registers every UI-registry child\n# renderer from the manifest, and seeds the stash with each\n# template variable's static default (issue #1416).\n#\n# Configuration (all optional):\n# - manifest_path: absolute path to the `bf build`-emitted\n# `manifest.json`. Defaults to `<app->home>/dist/templates/manifest.json`.\n# Pass `undef` to disable manifest-driven auto-init entirely; the\n# bf helper is still installed and callers can drive everything\n# manually as before.\nsub register ($self, $app, $config = {}) {\n $app->helper(bf => sub ($c) {\n $c->stash->{'bf.instance'} //= BarefootJS->new($c, $config);\n });\n\n my $manifest = _load_manifest($app, $config);\n return unless $manifest;\n\n # Cache the set of UI-registry slot keys so we can answer\n # \"is this template name a child or a top-level page?\" with a\n # single hash lookup at render time. Top-level entries are\n # everything that isn't `__barefoot__` and doesn't match\n # `ui/<name>/index` \u2014 the same partition `register_components_from_manifest`\n # applies internally.\n my %is_child_entry;\n for my $entry_name (keys %$manifest) {\n next if $entry_name eq '__barefoot__';\n next unless $entry_name =~ m{^ui/[^/]+/index$};\n $is_child_entry{$entry_name} = 1;\n }\n\n $app->hook(before_render => sub ($c, $args) {\n my $template = $args->{template};\n return unless defined $template && length $template;\n my $entry = $manifest->{$template};\n return unless $entry;\n return if $is_child_entry{$template};\n # Idempotency guard for nested renders. A controller might\n # call `render_to_string` inside an action and then `render`\n # \u2014 without this we'd re-init `bf` on the second pass and\n # wipe the script registrations the first pass collected.\n return if $c->stash->{'bf.auto_init_done'};\n\n # Escape hatch for callers that wire `bf` up by hand (the\n # existing `render_component` helper in the showcase app does\n # this). If `_scope_id` is already set we treat the request as\n # \"manually managed\" and leave it alone \u2014 same outcome as\n # before the plugin gained auto-init.\n my $bf = $c->bf;\n if (defined $bf->_scope_id && length $bf->_scope_id) {\n $c->stash->{'bf.auto_init_done'} = 1;\n return;\n }\n $c->stash->{'bf.auto_init_done'} = 1;\n\n $bf->_scope_id($template . '_' . substr(rand() =~ s/^0\\.//r, 0, 6));\n $bf->register_components_from_manifest($manifest);\n\n # Seed each ssrDefault into the stash unless the caller has\n # already supplied a value for that key \u2014 callers always win.\n my $defaults = $entry->{ssrDefaults};\n if (ref($defaults) eq 'HASH') {\n for my $name (keys %$defaults) {\n next if exists $c->stash->{$name};\n my $d = $defaults->{$name};\n my $value = ref($d) eq 'HASH' ? $d->{value} : $d;\n $c->stash->{$name} = $value;\n }\n }\n });\n}\n\nsub _load_manifest ($app, $config) {\n return undef if exists $config->{manifest_path} && !defined $config->{manifest_path};\n my $manifest_path = $config->{manifest_path}\n // $app->home->child('dist/templates/manifest.json');\n my $file = path($manifest_path);\n return undef unless -r $file;\n my $manifest = eval { decode_json($file->slurp) };\n if ($@ || ref($manifest) ne 'HASH') {\n $app->log->warn(\"BarefootJS: cannot parse manifest at $file: $@\") if $@;\n return undef;\n }\n return $manifest;\n}\n\n1;\n__END__\n\n=encoding utf8\n\n=head1 NAME\n\nMojolicious::Plugin::BarefootJS - Mojolicious integration for BarefootJS\n\n=head1 SYNOPSIS\n\n # Mojolicious application\n $self->plugin('BarefootJS');\n\n # In a controller / template, the `bf` helper exposes a per-request\n # BarefootJS runtime backed by BarefootJS::Backend::Mojo.\n\n=head1 DESCRIPTION\n\nWires the L<BarefootJS> server runtime into L<Mojolicious>. It registers a\nC<bf> controller helper that lazily instantiates one BarefootJS object per\nrequest (rendering via L<BarefootJS::Backend::Mojo>), and supports rendering\ncompiled marked templates as Mojolicious templates.\n\nFor non-Mojolicious / PSGI hosts, see L<BarefootJS::Backend::Xslate>, which\ndrives the same runtime with Text::Xslate and no web framework.\n\n=head1 METHODS\n\nL<Mojolicious::Plugin::BarefootJS> inherits all methods from\nL<Mojolicious::Plugin> and implements the following new one.\n\n=head2 register\n\n $plugin->register(Mojolicious->new, \\%conf);\n\nRegisters the plugin (the C<bf> helper and supporting hooks) in a Mojolicious\napplication.\n\n=head1 SEE ALSO\n\nL<BarefootJS>, L<BarefootJS::Backend::Mojo>, L<BarefootJS::Backend::Xslate>,\nL<Mojolicious>, L<https://github.com/piconic-ai/barefootjs>\n\n=head1 AUTHOR\n\nkobaken E<lt>kentafly88@gmail.comE<gt>\n\n=head1 LICENSE\n\nCopyright (c) 2025-present BarefootJS Contributors.\n\nThis library is free software; you can redistribute it and/or modify it under\nthe MIT License. See the F<LICENSE> file in the distribution for the full text.\n\n=cut\n";
|
|
23552
23570
|
barefootDevReloadPmSource = `package Mojolicious::Plugin::BarefootJS::DevReload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "CLI for agent-driven UI component discovery and scaffolding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"typescript": "^5.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@barefootjs/jsx": "0.9.
|
|
34
|
+
"@barefootjs/jsx": "0.9.3",
|
|
35
35
|
"@types/node": "^22.0.0"
|
|
36
36
|
}
|
|
37
37
|
}
|