");
+ cart_size_default({});
+ settings_default({ content: _content("__tests__/template.marko_2_content", () => {
+ _scope_reason();
+ const $scope2_id = _scope_id();
+ const theme = _context_get("__tests__/settings.marko", "./settings.marko");
+ _html(`${_escape(items.join(","))}${_el_resume($scope2_id, "#text/0")}-${_escape(theme)}${_el_resume($scope2_id, "#text/1")}
`);
+ _subscribe($cartprovider_content__items__closures, writeScope($scope2_id, { _: _scope_with_id($scope1_id) }, "__tests__/template.marko", "6:4"));
+ _resume_branch($scope2_id);
+ }) });
+ writeScope($scope1_id, { "ClosureScopes:items": $cartprovider_content__items__closures }, "__tests__/template.marko", "3:2");
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..769f7318b4a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.js
@@ -0,0 +1,53 @@
+// settings.marko
+var settings_default = _template("a", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("a", "dark", () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {});
+});
+
+// tags/cart-size.marko
+var cart_size_default = _template("d", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_get("c").length)}${_el_resume($scope0_id, "a")} items `);
+ writeScope($scope0_id, {});
+});
+
+// tags/cart-provider.marko
+var cart_provider_default = _template("c", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("c", [
+ "a",
+ "b",
+ "c"
+ ], () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {});
+});
+
+// template.marko
+var template_default = _template("b", (input) => {
+ _scope_reason();
+ _scope_id();
+ cart_provider_default({ content: _content("b1", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ const $cartprovider_content__items__closures = /* @__PURE__ */ new Set();
+ const items = _context_get("c");
+ cart_size_default({});
+ settings_default({ content: _content("b0", () => {
+ _scope_reason();
+ const $scope2_id = _scope_id();
+ const theme = _context_get("a");
+ _html(`${_escape(items.join(","))}${_el_resume($scope2_id, "a")}-${_escape(theme)}${_el_resume($scope2_id, "b")}
`);
+ _subscribe($cartprovider_content__items__closures, writeScope($scope2_id, { _: _scope_with_id($scope1_id) }));
+ _resume_branch($scope2_id);
+ }) });
+ writeScope($scope1_id, { Bc: $cartprovider_content__items__closures });
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..d423b2cccb6
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.debug.md
@@ -0,0 +1,9 @@
+# Render
+```html
+
+ 3 items
+
+
+ a,b,c-dark
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.md
new file mode 100644
index 00000000000..d423b2cccb6
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.md
@@ -0,0 +1,9 @@
+# Render
+```html
+
+ 3 items
+
+
+ a,b,c-dark
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..d5f8db108e1
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.debug.html
@@ -0,0 +1,10 @@
+3 items
+a,b,c-dark
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.html
new file mode 100644
index 00000000000..19f5f779b99
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.html
@@ -0,0 +1,10 @@
+3 items
+a,b,c-dark
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/settings.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/settings.marko
new file mode 100644
index 00000000000..f398f5740e0
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/settings.marko
@@ -0,0 +1,5 @@
+// A non-taglib template addressable only via a relative `from=` path (e.g.
+// a `+layout.marko`-shaped provider).
+
+ <${input.content}/>
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-basics/sizes.json
new file mode 100644
index 00000000000..10059a89a06
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/sizes.json
@@ -0,0 +1,7 @@
+{
+ "dom": {},
+ "html": {
+ "min": 402,
+ "brotli": 282
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-provider.marko
new file mode 100644
index 00000000000..331f5cfe4ca
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-provider.marko
@@ -0,0 +1,5 @@
+// Taglib-discoverable provider wrapping passed content (dynamic-extent
+// resolution); the literal value classifies server-only.
+
+ <${input.content}/>
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-size.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-size.marko
new file mode 100644
index 00000000000..049b60414b5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-size.marko
@@ -0,0 +1,3 @@
+// A second, distinct consumer template of the same provider.
+
+${items.length} items
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/template.marko
new file mode 100644
index 00000000000..fea10970490
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/template.marko
@@ -0,0 +1,10 @@
+import Settings from "./settings.marko"
+
+
+
+
+
+
+ ${items.join(",")}-${theme}
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-basics/test.ts
new file mode 100644
index 00000000000..53375135d86
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ steps: [{}],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..4fca45f573f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,49 @@
+// counter.js
+let n = 0;
+function next() {
+ return "value-" + ++n;
+}
+
+// provider.marko
+const $template$2 = "";
+const $walks$2 = "b%c";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+function $setup$2($scope) {
+ $scope["#ContextValue"] = next();
+ _context_branch($scope, "__tests__/provider.marko");
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var provider_default = /*@__PURE__*/ _template("__tests__/provider.marko", $template$2, "b%c", $setup$2, $input);
+
+// consumer.marko
+const $template$1 = " ";
+const $walks$1 = "bD l";
+const $label = ($scope, label) => _text($scope["#text/0"], label);
+function $setup$1($scope) {
+ $label($scope, _context_read($scope, "label", "__tests__/provider.marko", "./provider.marko"));
+}
+var consumer_default = /*@__PURE__*/ _template("__tests__/consumer.marko", $template$1, $walks$1, $setup$1);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0, _w1) => `${_w0}${_w1}`)($template$2, $template$2);
+const $walks = /*@__PURE__*/ ((_w0, _w1) => `b/${_w0}&/${_w1}&b`)("b%c", "b%c");
+const $Provider_content2 = /*@__PURE__*/ _content("__tests__/template.marko_3_content", "sibling
", "b");
+const $await_content__setup = ($scope) => {
+ $setup$1($scope["#childScope/0"]);
+};
+const $await_content = /*@__PURE__*/ _await_content("#text/0", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $await_content__setup);
+const $Provider_content__await_promise = /*@__PURE__*/ _await_promise("#text/0");
+const $Provider_content__setup = ($scope) => {
+ $await_content($scope);
+ $Provider_content__await_promise($scope, resolveAfter(1));
+};
+const $Provider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "", "b%c", $Provider_content__setup);
+function $setup($scope) {
+ $setup$2($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $Provider_content($scope));
+ $setup$2($scope["#childScope/1"]);
+ $input_content_direct($scope["#childScope/1"], $Provider_content2($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..c3c2f7cf7ea
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,43 @@
+// counter.js
+let n = 0;
+function next() {
+ return "value-" + ++n;
+}
+
+// provider.marko
+var provider_default = _template("__tests__/provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("__tests__/provider.marko", next(), () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/provider.marko", 0);
+});
+
+// consumer.marko
+var consumer_default = _template("__tests__/consumer.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const label = _context_get("__tests__/provider.marko", "./provider.marko");
+ _html(`${_escape(label)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/consumer.marko", 0);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ _await($scope1_id, "#text/0", resolveAfter(1), (_ignored) => {
+ const $scope2_id = _scope_id();
+ consumer_default({});
+ }, 0);
+ }) });
+ provider_default({ content: _content("__tests__/template.marko_3_content", () => {
+ _scope_reason();
+ const $scope3_id = _scope_id();
+ _html("sibling
");
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..ed9767cde32
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.js
@@ -0,0 +1,41 @@
+// counter.js
+let n = 0;
+function next() {
+ return "value-" + ++n;
+}
+
+// provider.marko
+var provider_default = _template("b", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("b", next(), () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {});
+});
+
+// consumer.marko
+var consumer_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_get("b"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+});
+
+// template.marko
+var template_default = _template("c", (input) => {
+ _scope_reason();
+ _scope_id();
+ provider_default({ content: _content("c0", () => {
+ _scope_reason();
+ _await(_scope_id(), "a", resolveAfter(1), (_ignored) => {
+ _scope_id();
+ consumer_default({});
+ }, 0);
+ }) });
+ provider_default({ content: _content("c1", () => {
+ _scope_reason();
+ _scope_id();
+ _html("sibling
");
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..6a0517d6784
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.debug.md
@@ -0,0 +1,18 @@
+# Render
+
+# Update
+```html
+
+ value-1
+
+
+ sibling
+
+```
+## Change
+```
+INSERT: span
+INSERT: span::text("value-1")
+INSERT: span + div
+INSERT: div::text("sibling")
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.md
new file mode 100644
index 00000000000..6a0517d6784
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.md
@@ -0,0 +1,18 @@
+# Render
+
+# Update
+```html
+
+ value-1
+
+
+ sibling
+
+```
+## Change
+```
+INSERT: span
+INSERT: span::text("value-1")
+INSERT: span + div
+INSERT: div::text("sibling")
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..3bbbd2f26e5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.debug.html
@@ -0,0 +1,7 @@
+
+
+value-1
+sibling
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.html
new file mode 100644
index 00000000000..a79ec7f304c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.html
@@ -0,0 +1,7 @@
+
+
+value-1
+sibling
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/consumer.marko b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/consumer.marko
new file mode 100644
index 00000000000..552da2e324c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/consumer.marko
@@ -0,0 +1,2 @@
+
+${label}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/counter.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/counter.js
new file mode 100644
index 00000000000..6e59b8613eb
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/counter.js
@@ -0,0 +1,5 @@
+// Distinct value per provider instance while classifying immutable.
+let n = 0;
+export default function next() {
+ return "value-" + ++n;
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/provider.marko
new file mode 100644
index 00000000000..904f4c0a0d3
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/provider.marko
@@ -0,0 +1,5 @@
+import next from "./counter.js"
+
+
+ <${input.content}/>
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/sizes.json
new file mode 100644
index 00000000000..2fa87af1622
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/sizes.json
@@ -0,0 +1,7 @@
+{
+ "dom": {},
+ "html": {
+ "min": 323,
+ "brotli": 235
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/template.marko
new file mode 100644
index 00000000000..b3e907f7646
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/template.marko
@@ -0,0 +1,14 @@
+import { resolveAfter } from "../../utils/resolve"
+import Provider from "./provider.marko"
+import Consumer from "./consumer.marko"
+
+// The consumer inside the deferred `` body must read the value
+// captured at defer time, not the sibling provider's later push.
+
+
+
+
+
+
+ sibling
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/test.ts
new file mode 100644
index 00000000000..8678ab208e3
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/test.ts
@@ -0,0 +1,9 @@
+import type { TestConfig } from "../../main.test";
+import { flush, wait } from "../../utils/resolve";
+
+export const config: TestConfig = {
+ steps: [{}, flush, wait],
+ // Two sibling instances of one provider share the root branch, which
+ // client-side (branch based) resolution cannot distinguish.
+ skip_csr: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.debug.txt
new file mode 100644
index 00000000000..e24b63045ce
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.debug.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14
+ 1 |
+ > 2 |
+ | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`).
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.txt
new file mode 100644
index 00000000000..e24b63045ce
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14
+ 1 |
+ > 2 |
+ | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`).
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.debug.txt
new file mode 100644
index 00000000000..e24b63045ce
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.debug.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14
+ 1 |
+ > 2 |
+ | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`).
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.txt
new file mode 100644
index 00000000000..e24b63045ce
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14
+ 1 |
+ > 2 |
+ | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`).
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/provider.marko
new file mode 100644
index 00000000000..0c279465187
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/provider.marko
@@ -0,0 +1,3 @@
+
+ provided
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko
new file mode 100644
index 00000000000..be4ce17ad94
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko
@@ -0,0 +1,2 @@
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/test.ts
new file mode 100644
index 00000000000..87d511a1f39
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ error_compiler: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.debug.txt
new file mode 100644
index 00000000000..ce3347b6589
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.debug.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.txt
new file mode 100644
index 00000000000..ce3347b6589
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.debug.txt
new file mode 100644
index 00000000000..ce3347b6589
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.debug.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.txt
new file mode 100644
index 00000000000..ce3347b6589
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/not-a-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/not-a-provider.marko
new file mode 100644
index 00000000000..018e857be3f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/not-a-provider.marko
@@ -0,0 +1 @@
+I do not provide a context value.
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko
new file mode 100644
index 00000000000..79075d22b07
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko
@@ -0,0 +1 @@
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/test.ts
new file mode 100644
index 00000000000..87d511a1f39
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ error_compiler: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.debug.txt
new file mode 100644
index 00000000000..0dbb550afd2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.debug.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2
+ > 1 |
+ | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value.
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.txt
new file mode 100644
index 00000000000..0dbb550afd2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2
+ > 1 |
+ | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value.
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.debug.txt
new file mode 100644
index 00000000000..0dbb550afd2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.debug.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2
+ > 1 |
+ | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value.
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.txt
new file mode 100644
index 00000000000..0dbb550afd2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.txt
@@ -0,0 +1,5 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2
+ > 1 |
+ | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value.
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko
new file mode 100644
index 00000000000..577202e9cf2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko
@@ -0,0 +1 @@
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/test.ts
new file mode 100644
index 00000000000..87d511a1f39
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ error_compiler: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.debug.txt
new file mode 100644
index 00000000000..4e23c980b6f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.debug.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21
+ 1 |
+ > 2 | click
+ | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable.
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.txt
new file mode 100644
index 00000000000..4e23c980b6f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21
+ 1 |
+ > 2 | click
+ | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable.
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.debug.txt
new file mode 100644
index 00000000000..4e23c980b6f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.debug.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21
+ 1 |
+ > 2 | click
+ | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable.
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.txt
new file mode 100644
index 00000000000..4e23c980b6f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21
+ 1 |
+ > 2 | click
+ | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable.
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/provider.marko
new file mode 100644
index 00000000000..0c279465187
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/provider.marko
@@ -0,0 +1,3 @@
+
+ provided
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko
new file mode 100644
index 00000000000..d997ed4410b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko
@@ -0,0 +1,2 @@
+
+click
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/test.ts
new file mode 100644
index 00000000000..87d511a1f39
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ error_compiler: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.debug.txt
new file mode 100644
index 00000000000..5f294099b9d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.debug.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 | ${x}
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.txt
new file mode 100644
index 00000000000..5f294099b9d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 | ${x}
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.debug.txt
new file mode 100644
index 00000000000..5f294099b9d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.debug.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 | ${x}
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.txt
new file mode 100644
index 00000000000..5f294099b9d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.txt
@@ -0,0 +1,6 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag).
+ 2 | ${x}
+ 3 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko
new file mode 100644
index 00000000000..3872e3ebe8c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko
@@ -0,0 +1,2 @@
+
+${x}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/test.ts
new file mode 100644
index 00000000000..87d511a1f39
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ error_compiler: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.debug.txt
new file mode 100644
index 00000000000..071a3bfce20
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.debug.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2
+ 2 | first
+ 3 |
+ > 4 |
+ | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous.
+ 5 | second
+ 6 |
+ 7 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.txt
new file mode 100644
index 00000000000..071a3bfce20
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2
+ 2 | first
+ 3 |
+ > 4 |
+ | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous.
+ 5 | second
+ 6 |
+ 7 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.debug.txt
new file mode 100644
index 00000000000..071a3bfce20
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.debug.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2
+ 2 | first
+ 3 |
+ > 4 |
+ | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous.
+ 5 | second
+ 6 |
+ 7 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.txt
new file mode 100644
index 00000000000..071a3bfce20
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2
+ 2 | first
+ 3 |
+ > 4 |
+ | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous.
+ 5 | second
+ 6 |
+ 7 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko
new file mode 100644
index 00000000000..9d6de2f5593
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko
@@ -0,0 +1,6 @@
+
+ first
+
+
+ second
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/test.ts
new file mode 100644
index 00000000000..87d511a1f39
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ error_compiler: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.debug.txt
new file mode 100644
index 00000000000..4e5f3f1fe71
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.debug.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "":
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag .
+ 2 |
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.txt
new file mode 100644
index 00000000000..4e5f3f1fe71
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "":
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag .
+ 2 |
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.debug.txt
new file mode 100644
index 00000000000..4e5f3f1fe71
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.debug.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "":
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag .
+ 2 |
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.txt
new file mode 100644
index 00000000000..4e5f3f1fe71
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.txt
@@ -0,0 +1,9 @@
+
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "":
+ at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1
+ > 1 |
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag .
+ 2 |
+ 2 |
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko
new file mode 100644
index 00000000000..8bcb8cd06d6
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko
@@ -0,0 +1 @@
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/test.ts
new file mode 100644
index 00000000000..87d511a1f39
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/test.ts
@@ -0,0 +1,5 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ error_compiler: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..69fbf03e0a4
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,48 @@
+// tags/theme-display.marko
+const $template$2 = " ";
+const $walks$2 = "bD l";
+const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", "");
+const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme));
+const $setup$2 = $context_theme;
+var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2);
+
+// tags/theme-provider.marko
+const $template$1 = "change ";
+const $walks$1 = "b%b b";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko"));
+const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() {
+ $theme($scope, "dark");
+}));
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/theme-provider.marko");
+ $theme($scope, "light");
+ $setup__script($scope);
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1);
+_enable_catch();
+const $await_content__setup = ($scope) => {
+ $setup$2($scope["#childScope/0"]);
+};
+const $placeholder_content = _content_resume("__tests__/template.marko_3_content", "loading", "b");
+const $await_content = /*@__PURE__*/ _await_content("#text/0", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $await_content__setup);
+const $try_content__await_promise = /*@__PURE__*/ _await_promise("#text/0");
+const $try_content__setup = ($scope) => {
+ $await_content($scope);
+ $try_content__await_promise($scope, resolveAfter(0, 1));
+};
+const $themeprovider_content__try = /*@__PURE__*/ _try("#text/0", "", "b%c", $try_content__setup);
+const $themeprovider_content__setup = ($scope) => $themeprovider_content__try($scope, { placeholder: attrTag({ content: $placeholder_content($scope) }) });
+const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "", "b%c", $themeprovider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..02baa5eaddf
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.js
@@ -0,0 +1,13 @@
+// tags/theme-display.marko
+const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0");
+const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b));
+
+// tags/theme-provider.marko
+const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "c"));
+const $setup__script = _script("c0", ($scope) => _on($scope.b, "click", function() {
+ $theme($scope, "dark");
+}));
+
+// template.marko
+_enable_catch();
+const $placeholder_content = _content_resume("a0", "loading", "b");
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..0a268a25dcf
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,46 @@
+// tags/theme-display.marko
+var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", "");
+ _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("__tests__/tags/theme-provider.marko", theme, () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`change ${_el_resume($scope0_id, "#button/1")}`);
+ _script($scope0_id, "__tests__/tags/theme-provider.marko_0");
+ writeScope($scope0_id, {}, "__tests__/tags/theme-provider.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ _try($scope1_id, "#text/0", _content_resume("__tests__/template.marko_2_content", () => {
+ const $scope2_id = _scope_id();
+ _scope_reason();
+ _await($scope2_id, "#text/0", resolveAfter(0, 1), (_ignored) => {
+ const $scope4_id = _scope_id();
+ theme_display_default({});
+ }, 0);
+ }, $scope1_id), { placeholder: attrTag({ content: _content_resume("__tests__/template.marko_3_content", () => {
+ _scope_reason();
+ const $scope3_id = _scope_id();
+ _html("loading");
+ }, $scope1_id) }) });
+ }) });
+ writeScope($scope0_id, {}, "__tests__/template.marko", 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..cf1754fa059
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.js
@@ -0,0 +1,44 @@
+// tags/theme-display.marko
+var theme_display_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("c", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("c", "light", () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`change ${_el_resume($scope0_id, "b")}`);
+ _script($scope0_id, "c0");
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("a2", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ _try($scope1_id, "a", _content_resume("a1", () => {
+ const $scope2_id = _scope_id();
+ _scope_reason();
+ _await($scope2_id, "a", resolveAfter(0, 1), (_ignored) => {
+ _scope_id();
+ theme_display_default({});
+ }, 0);
+ }, $scope1_id), { placeholder: attrTag({ content: _content_resume("a0", () => {
+ _scope_reason();
+ _scope_id();
+ _html("loading");
+ }, $scope1_id) }) });
+ }) });
+ writeScope($scope0_id, {});
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-csr.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-csr.debug.md
new file mode 100644
index 00000000000..65a8223afa9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-csr.debug.md
@@ -0,0 +1,63 @@
+# Render
+```html
+
+ change
+
+```
+
+# Update
+```html
+loading
+
+ change
+
+```
+## Change
+```
+INSERT: ::text("loading")
+```
+
+# Update
+```html
+
+ light
+
+
+ change
+
+```
+## Change
+```
+INSERT: .display
+REMOVE: .display + ::text("loading")
+UPDATE: .display::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector("button.change").click();
+```
+```html
+
+ dark
+
+
+ change
+
+```
+## Change
+```
+UPDATE: .display::text "light" => "dark"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.debug.md
new file mode 100644
index 00000000000..f3146a775b5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.debug.md
@@ -0,0 +1,34 @@
+# Render
+```html
+loading
+
+ change
+
+```
+
+# Update
+```html
+
+ light
+
+
+ change
+
+```
+## Change
+```
+INSERT: .display::text("light")
+REMOVE: ::text("loading")
+INSERT: .display
+```
+
+# Update
+```js
+container.querySelector("button.change").click();
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.md
new file mode 100644
index 00000000000..f3146a775b5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.md
@@ -0,0 +1,34 @@
+# Render
+```html
+loading
+
+ change
+
+```
+
+# Update
+```html
+
+ light
+
+
+ change
+
+```
+## Change
+```
+INSERT: .display::text("light")
+REMOVE: ::text("loading")
+INSERT: .display
+```
+
+# Update
+```js
+container.querySelector("button.change").click();
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..45e092f2ba5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.debug.html
@@ -0,0 +1,37 @@
+loadingchange
+
+
+
+
+
+light
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.html
new file mode 100644
index 00000000000..d4c29eb985b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.html
@@ -0,0 +1,28 @@
+loadingchange
+
+
+
+
+
+light
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/sizes.json
new file mode 100644
index 00000000000..5fe8d6d9309
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/sizes.json
@@ -0,0 +1,19 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 7776,
+ "brotli": 3408
+ },
+ "files": {
+ "tags/theme-display.marko": 240,
+ "tags/theme-provider.marko": 253,
+ "template.marko": 123
+ }
+ }
+ },
+ "html": {
+ "min": 1078,
+ "brotli": 619
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-display.marko
new file mode 100644
index 00000000000..60b9ab808db
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-display.marko
@@ -0,0 +1,2 @@
+
+${theme}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-provider.marko
new file mode 100644
index 00000000000..d9808943a47
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-provider.marko
@@ -0,0 +1,5 @@
+
+
+ <${input.content}/>
+
+change
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/template.marko
new file mode 100644
index 00000000000..321c29f382a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/template.marko
@@ -0,0 +1,12 @@
+import { resolveAfter } from "../../utils/resolve"
+
+// A mutable consumer inside a deferred `` body subscribes from a
+// later flush; a provider change after resume must still reach it.
+
+
+ <@placeholder>loading@placeholder>
+
+
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/test.ts
new file mode 100644
index 00000000000..3e0946ba3d1
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/test.ts
@@ -0,0 +1,12 @@
+import type { TestConfig } from "../../main.test";
+import { after, flush } from "../../utils/resolve";
+
+function change(container: Element) {
+ container.querySelector("button.change")!.click();
+}
+
+export const config: TestConfig = {
+ // csr and ssr render logs legitimately differ for awaited content.
+ equivalent: false,
+ steps: [{}, after(1), flush, change],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..1aa15b3f354
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,39 @@
+// tags/inert-log.marko
+const $template$2 = "inert
";
+const $walks$2 = "c";
+const $setup$2 = () => {};
+var inert_log_default = /*@__PURE__*/ _template("__tests__/tags/inert-log.marko", $template$2, "c", $setup$2);
+
+// tags/theme-provider.marko
+const $template$1 = " toggle ";
+const $walks$1 = "bD l%b b";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/1");
+const $theme = /*@__PURE__*/ _let("theme/6", ($scope) => {
+ _text($scope["#text/0"], $scope.theme);
+ _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko");
+});
+const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/2"], "click", function() {
+ $theme($scope, $scope.theme === "light" ? "dark" : "light");
+}));
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/theme-provider.marko");
+ $theme($scope, "light");
+ $setup__script($scope);
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/1");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1);
+const $themeprovider_content__setup = ($scope) => {
+ /* @__PURE__ */ $setup$2($scope["#childScope/0"]);
+};
+const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)("c"), $themeprovider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..ea25da16a9f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.js
@@ -0,0 +1,8 @@
+// tags/theme-provider.marko
+const $theme = /*@__PURE__*/ _let(6, ($scope) => {
+ _text($scope.a, $scope.g);
+ _context_value($scope, $scope.g, "c");
+});
+const $setup__script = _script("c0", ($scope) => _on($scope.c, "click", function() {
+ $theme($scope, $scope.g === "light" ? "dark" : "light");
+}));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..341ecd70863
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,34 @@
+// tags/inert-log.marko
+var inert_log_default = _template("__tests__/tags/inert-log.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const theme = _context_get("__tests__/tags/theme-provider.marko", "");
+ _html("inert
");
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("__tests__/tags/theme-provider.marko", theme, () => {
+ _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")} `);
+ _dynamic_tag($scope0_id, "#text/1", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`toggle ${_el_resume($scope0_id, "#button/2")}`);
+ _script($scope0_id, "__tests__/tags/theme-provider.marko_0");
+ writeScope($scope0_id, { theme }, "__tests__/tags/theme-provider.marko", 0, { theme: "2:6" });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ inert_log_default({});
+ }) });
+ writeScope($scope0_id, {}, "__tests__/template.marko", 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..77dd3e42b1c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.js
@@ -0,0 +1,34 @@
+// tags/inert-log.marko
+var inert_log_default = _template("b", (input) => {
+ _scope_reason();
+ _scope_id();
+ _context_get("c");
+ _html("inert
");
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("c", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("c", theme, () => {
+ _html(`${_escape(theme)}${_el_resume($scope0_id, "a")} `);
+ _dynamic_tag($scope0_id, "b", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`toggle ${_el_resume($scope0_id, "c")}`);
+ _script($scope0_id, "c0");
+ writeScope($scope0_id, { g: theme });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("a0", () => {
+ _scope_reason();
+ _scope_id();
+ inert_log_default({});
+ }) });
+ writeScope($scope0_id, {});
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..fd2edca2c76
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.debug.md
@@ -0,0 +1,36 @@
+# Render
+```html
+
+ light
+
+
+ inert
+
+
+ toggle
+
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ dark
+
+
+ inert
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "light" => "dark"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.md
new file mode 100644
index 00000000000..fd2edca2c76
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.md
@@ -0,0 +1,36 @@
+# Render
+```html
+
+ light
+
+
+ inert
+
+
+ toggle
+
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ dark
+
+
+ inert
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "light" => "dark"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..1ce641e4925
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.debug.html
@@ -0,0 +1,16 @@
+light
+inert
toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.html
new file mode 100644
index 00000000000..6ac936f28a9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.html
@@ -0,0 +1,13 @@
+light
+inert
toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/sizes.json
new file mode 100644
index 00000000000..44c34ceceed
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/sizes.json
@@ -0,0 +1,12 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "min": 2776,
+ "brotli": 1405
+ }
+ },
+ "html": {
+ "min": 442,
+ "brotli": 302
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/inert-log.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/inert-log.marko
new file mode 100644
index 00000000000..f5751a0d369
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/inert-log.marko
@@ -0,0 +1,4 @@
+// This consumer never observes the value client-side, so no wiring
+// serializes and no fan-out signal reaches the bundle (`bundle_excludes`).
+
+inert
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/theme-provider.marko
new file mode 100644
index 00000000000..2a223538945
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/theme-provider.marko
@@ -0,0 +1,9 @@
+// Mutable provider: ``-derived, so consumers subscribe.
+
+
+ ${theme}
+ <${input.content}/>
+
+
+ toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/template.marko
new file mode 100644
index 00000000000..ac87bdf513f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/template.marko
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/test.ts
new file mode 100644
index 00000000000..4e2068c3504
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/test.ts
@@ -0,0 +1,12 @@
+import type { TestConfig } from "../../main.test";
+
+function toggle(container: Element) {
+ container.querySelector("button.toggle")!.click();
+}
+
+export const config: TestConfig = {
+ steps: [{}, toggle],
+ // Even with a mutable provider, an unobserved consumer serializes no
+ // wiring and registers no fan-out signal.
+ bundle_excludes: { dom: ["_context_closure"] },
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..3bd312a2a99
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,31 @@
+// tags/theme-provider.marko
+const $template$1 = "toggle ";
+const $walks$1 = "b%b b";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko"));
+const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() {
+ $theme($scope, $scope.theme === "light" ? "dark" : "light");
+}));
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/theme-provider.marko");
+ $theme($scope, "light");
+ $setup__script($scope);
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1);
+const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => ($themeprovider_content__theme($scope2, $provider["#ContextValue"]), $themeprovider_content__accent($scope2, $provider["#ContextValue"])), "__tests__/template.marko_1_theme/context", "");
+const $themeprovider_content__theme = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme));
+const $themeprovider_content__accent = /*@__PURE__*/ _const("accent", ($scope) => _text($scope["#text/1"], $scope.accent === "light" ? "gold" : "silver"));
+const $themeprovider_content__setup = $context_theme;
+const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", " ", "bD lD l", $themeprovider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..74668e68c22
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.js
@@ -0,0 +1,10 @@
+// tags/theme-provider.marko
+const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "b"));
+const $setup__script = _script("b0", ($scope) => _on($scope.b, "click", function() {
+ $theme($scope, $scope.f === "light" ? "dark" : "light");
+}));
+
+// template.marko
+const $context_theme = _context_closure("b", ($scope2, $provider) => ($themeprovider_content__theme($scope2, $provider["I"]), $themeprovider_content__accent($scope2, $provider["I"])), "a0");
+const $themeprovider_content__theme = /*@__PURE__*/ _const(2, ($scope) => _text($scope.a, $scope.c));
+const $themeprovider_content__accent = /*@__PURE__*/ _const(3, ($scope) => _text($scope.b, $scope.d === "light" ? "gold" : "silver"));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..468e35d1ba3
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,29 @@
+// tags/theme-provider.marko
+var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("__tests__/tags/theme-provider.marko", theme, () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`toggle ${_el_resume($scope0_id, "#button/1")}`);
+ _script($scope0_id, "__tests__/tags/theme-provider.marko_0");
+ writeScope($scope0_id, { theme }, "__tests__/tags/theme-provider.marko", 0, { theme: "2:6" });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ const theme = _context_link($scope1_id, "__tests__/tags/theme-provider.marko", "__tests__/template.marko_1_theme/context", "");
+ const accent = _context_get("__tests__/tags/theme-provider.marko", "");
+ _html(`${_escape(theme)}${_el_resume($scope1_id, "#text/0")} ${accent === "light" ? "gold" : "silver"}${_el_resume($scope1_id, "#text/1")} `);
+ writeScope($scope1_id, {}, "__tests__/template.marko", "3:2");
+ _resume_branch($scope1_id);
+ }) });
+ writeScope($scope0_id, {}, "__tests__/template.marko", 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..980c993f7b5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.js
@@ -0,0 +1,29 @@
+// tags/theme-provider.marko
+var theme_provider_default = _template("b", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("b", theme, () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`toggle ${_el_resume($scope0_id, "b")}`);
+ _script($scope0_id, "b0");
+ writeScope($scope0_id, { f: theme });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("a1", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ const theme = _context_link($scope1_id, "b", "a0");
+ const accent = _context_get("b");
+ _html(`${_escape(theme)}${_el_resume($scope1_id, "a")} ${accent === "light" ? "gold" : "silver"}${_el_resume($scope1_id, "b")} `);
+ writeScope($scope1_id, {});
+ _resume_branch($scope1_id);
+ }) });
+ writeScope($scope0_id, {});
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..72723f4ae3b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.debug.md
@@ -0,0 +1,60 @@
+# Render
+```html
+
+ light
+
+
+ gold
+
+
+ toggle
+
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ dark
+
+
+ silver
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "light" => "dark"
+UPDATE: em::text "gold" => "silver"
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ light
+
+
+ gold
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "dark" => "light"
+UPDATE: em::text "silver" => "gold"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.md
new file mode 100644
index 00000000000..72723f4ae3b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.md
@@ -0,0 +1,60 @@
+# Render
+```html
+
+ light
+
+
+ gold
+
+
+ toggle
+
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ dark
+
+
+ silver
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "light" => "dark"
+UPDATE: em::text "gold" => "silver"
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ light
+
+
+ gold
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "dark" => "light"
+UPDATE: em::text "silver" => "gold"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..c6d3a55ec62
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.debug.html
@@ -0,0 +1,23 @@
+light gold toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.html
new file mode 100644
index 00000000000..085418c7396
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.html
@@ -0,0 +1,16 @@
+light gold toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/sizes.json
new file mode 100644
index 00000000000..83f04ac218a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/sizes.json
@@ -0,0 +1,18 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 3352,
+ "brotli": 1659
+ },
+ "files": {
+ "tags/theme-provider.marko": 286,
+ "template.marko": 466
+ }
+ }
+ },
+ "html": {
+ "min": 480,
+ "brotli": 316
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/tags/theme-provider.marko
new file mode 100644
index 00000000000..3efeca46d5d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/tags/theme-provider.marko
@@ -0,0 +1,8 @@
+// Mutable provider: ``-derived, so consumers subscribe.
+
+
+ <${input.content}/>
+
+
+ toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/template.marko
new file mode 100644
index 00000000000..03ff9e26cb4
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/template.marko
@@ -0,0 +1,8 @@
+// Same-scope consumers of one provider compose into a single fan-out entry;
+// an update must reach both.
+
+
+
+ ${theme}
+ ${accent === "light" ? "gold" : "silver"}
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/test.ts
new file mode 100644
index 00000000000..a99c7830772
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/test.ts
@@ -0,0 +1,9 @@
+import type { TestConfig } from "../../main.test";
+
+function toggle(container: Element) {
+ container.querySelector("button.toggle")!.click();
+}
+
+export const config: TestConfig = {
+ steps: [{}, toggle, toggle],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..a9d03b577f2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,61 @@
+// tags/theme-display.marko
+const $template$2 = " ";
+const $walks$2 = "bD l";
+const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", "");
+const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme));
+const $setup$2 = $context_theme;
+var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2);
+
+// tags/theme-provider.marko
+const $template$1 = "change ";
+const $walks$1 = "b%b b";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+const $theme = /*@__PURE__*/ _let("theme/6", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko"));
+const $input_id__script = _script("__tests__/tags/theme-provider.marko_0_input_id", ($scope) => _on($scope["#button/1"], "click", function() {
+ $theme($scope, `dark-${$scope.input_id}`);
+}));
+const $input_id = /*@__PURE__*/ _const("input_id", ($scope) => {
+ _attr_class($scope["#button/1"], `change-${$scope.input_id}`);
+ $theme($scope, `light-${$scope.input_id}`);
+ $input_id__script($scope);
+});
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/theme-provider.marko");
+}
+const $input = ($scope, input) => {
+ $input_id($scope, input.id);
+ $input_content($scope, input.content);
+};
+var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = "";
+const $walks = "b%c";
+const $if_content__setup = ($scope) => {
+ $setup$2($scope["#childScope/0"]);
+};
+const $themeprovider_content__id = /*@__PURE__*/ _closure_get("id", ($scope) => _attr_class($scope["#button/0"], `show-${$scope._.id}`));
+const $themeprovider_content__if = /*@__PURE__*/ _if("#text/1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $if_content__setup);
+const $themeprovider_content__show = /*@__PURE__*/ _let("show/2", ($scope) => $themeprovider_content__if($scope, $scope.show ? 0 : 1));
+const $themeprovider_content__setup__script = _script("__tests__/template.marko_2", ($scope) => _on($scope["#button/0"], "click", function() {
+ $themeprovider_content__show($scope, true);
+}));
+const $themeprovider_content__setup = ($scope) => {
+ $themeprovider_content__id($scope);
+ $themeprovider_content__show($scope, false);
+ $themeprovider_content__setup__script($scope);
+};
+const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_2_content", "show ", " b%c", $themeprovider_content__setup);
+const $for_content__setup = ($scope) => {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope));
+};
+const $for_content__id = /*@__PURE__*/ _const("id", ($scope) => $input_id($scope["#childScope/0"], $scope.id));
+const $for_content__$params = ($scope, $params2) => $for_content__id($scope, $params2[0]);
+const $for = /*@__PURE__*/ _for_of("#text/0", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1), $for_content__setup, $for_content__$params);
+function $setup($scope) {
+ $for($scope, [[0, 1], (id) => id]);
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, "b%c", $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..5552e787800
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.js
@@ -0,0 +1,22 @@
+// tags/theme-display.marko
+const $template = " ";
+const $walks = "bD l";
+const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0");
+const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b));
+const $setup = $context_theme;
+
+// tags/theme-provider.marko
+const $theme = /*@__PURE__*/ _let(6, ($scope) => _context_value($scope, $scope.g, "c"));
+const $input_id__script = _script("c0", ($scope) => _on($scope.b, "click", function() {
+ $theme($scope, `dark-${$scope.e}`);
+}));
+
+// template.marko
+const $if_content__setup = ($scope) => {
+ $setup($scope.a);
+};
+const $themeprovider_content__if = /*@__PURE__*/ _if(1, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $if_content__setup);
+const $themeprovider_content__show = /*@__PURE__*/ _let(2, ($scope) => $themeprovider_content__if($scope, $scope.c ? 0 : 1));
+const $themeprovider_content__setup__script = _script("a0", ($scope) => _on($scope.a, "click", function() {
+ $themeprovider_content__show($scope, true);
+}));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..a846173e864
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,53 @@
+// tags/theme-display.marko
+var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", "");
+ _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = `light-${input.id}`;
+ _context_provide("__tests__/tags/theme-provider.marko", theme, () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`change ${_el_resume($scope0_id, "#button/1")}`);
+ _script($scope0_id, "__tests__/tags/theme-provider.marko_0_input_id");
+ writeScope($scope0_id, { input_id: input.id }, "__tests__/tags/theme-provider.marko", 0, { input_id: ["input.id"] });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _for_of([0, 1], (id) => {
+ const $scope1_id = _scope_id();
+ theme_provider_default({
+ id,
+ content: _content("__tests__/template.marko_2_content", () => {
+ _scope_reason();
+ const $scope2_id = _scope_id();
+ let show = false;
+ _html(`show ${_el_resume($scope2_id, "#button/0")}`);
+ _if(() => {
+ if (show) {
+ const $scope3_id = _scope_id();
+ theme_display_default({});
+ writeScope($scope3_id, {}, "__tests__/template.marko", "7:6");
+ return 0;
+ }
+ }, $scope2_id, "#text/1");
+ _script($scope2_id, "__tests__/template.marko_2");
+ writeScope($scope2_id, {}, "__tests__/template.marko", "4:4");
+ _resume_branch($scope2_id);
+ })
+ });
+ writeScope($scope1_id, {}, "__tests__/template.marko", "3:2");
+ }, (id) => id, $scope0_id, "#text/0", 1, 0, 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..fdd99f5084f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.js
@@ -0,0 +1,42 @@
+// tags/theme-display.marko
+var theme_display_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("c", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("c", `light-${input.id}`, () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`change ${_el_resume($scope0_id, "b")}`);
+ _script($scope0_id, "c0");
+ writeScope($scope0_id, { e: input.id });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ _for_of([0, 1], (id) => {
+ const $scope1_id = _scope_id();
+ theme_provider_default({
+ id,
+ content: _content("a1", () => {
+ _scope_reason();
+ const $scope2_id = _scope_id();
+ _html(`show ${_el_resume($scope2_id, "a")}`);
+ _if(() => {}, $scope2_id, "b");
+ _script($scope2_id, "a0");
+ writeScope($scope2_id, {});
+ _resume_branch($scope2_id);
+ })
+ });
+ writeScope($scope1_id, {});
+ }, (id) => id, _scope_id(), "a", 1, 0, 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..5a169660786
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.debug.md
@@ -0,0 +1,143 @@
+# Render
+```html
+
+ show
+
+
+ change
+
+
+ show
+
+
+ change
+
+```
+
+# Update
+```js
+container.querySelector(sel).click();
+```
+```html
+
+ show
+
+
+ light-0
+
+
+ change
+
+
+ show
+
+
+ change
+
+```
+## Change
+```
+INSERT: .show-0 + .display
+UPDATE: .display::text " " => "light-0"
+```
+
+# Update
+```js
+container.querySelector(sel).click();
+```
+```html
+
+ show
+
+
+ light-0
+
+
+ change
+
+
+ show
+
+
+ light-1
+
+
+ change
+
+```
+## Change
+```
+INSERT: .show-1 + span
+UPDATE: span:nth-of-type(2)::text " " => "light-1"
+```
+
+# Update
+```js
+container.querySelector(sel).click();
+```
+```html
+
+ show
+
+
+ light-0
+
+
+ change
+
+
+ show
+
+
+ dark-1
+
+
+ change
+
+```
+## Change
+```
+UPDATE: span:nth-of-type(2)::text "light-1" => "dark-1"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.md
new file mode 100644
index 00000000000..5a169660786
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.md
@@ -0,0 +1,143 @@
+# Render
+```html
+
+ show
+
+
+ change
+
+
+ show
+
+
+ change
+
+```
+
+# Update
+```js
+container.querySelector(sel).click();
+```
+```html
+
+ show
+
+
+ light-0
+
+
+ change
+
+
+ show
+
+
+ change
+
+```
+## Change
+```
+INSERT: .show-0 + .display
+UPDATE: .display::text " " => "light-0"
+```
+
+# Update
+```js
+container.querySelector(sel).click();
+```
+```html
+
+ show
+
+
+ light-0
+
+
+ change
+
+
+ show
+
+
+ light-1
+
+
+ change
+
+```
+## Change
+```
+INSERT: .show-1 + span
+UPDATE: span:nth-of-type(2)::text " " => "light-1"
+```
+
+# Update
+```js
+container.querySelector(sel).click();
+```
+```html
+
+ show
+
+
+ light-0
+
+
+ change
+
+
+ show
+
+
+ dark-1
+
+
+ change
+
+```
+## Change
+```
+UPDATE: span:nth-of-type(2)::text "light-1" => "dark-1"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..9cf66d122c9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.debug.html
@@ -0,0 +1,34 @@
+show change show change
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.html
new file mode 100644
index 00000000000..b2aca21478c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.html
@@ -0,0 +1,29 @@
+show change show change
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/sizes.json
new file mode 100644
index 00000000000..3f6bbc31e8e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/sizes.json
@@ -0,0 +1,19 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 6857,
+ "brotli": 3090
+ },
+ "files": {
+ "tags/theme-display.marko": 347,
+ "tags/theme-provider.marko": 268,
+ "template.marko": 557
+ }
+ }
+ },
+ "html": {
+ "min": 672,
+ "brotli": 359
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-display.marko
new file mode 100644
index 00000000000..60b9ab808db
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-display.marko
@@ -0,0 +1,2 @@
+
+${theme}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-provider.marko
new file mode 100644
index 00000000000..7bb3a9abc53
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-provider.marko
@@ -0,0 +1,8 @@
+// Mutable provider: ``-derived, so consumers subscribe.
+
+
+ <${input.content}/>
+
+
+ change
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/template.marko
new file mode 100644
index 00000000000..4686eeee04b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/template.marko
@@ -0,0 +1,11 @@
+// Providers inside keyed `` iterations: each iteration's branch carries
+// its own link entry, resolved by client-created consumers within it.
+ id>
+
+
+ show
+
+
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/test.ts
new file mode 100644
index 00000000000..2196da52861
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/test.ts
@@ -0,0 +1,10 @@
+import type { TestConfig } from "../../main.test";
+
+function click(sel: string) {
+ return (container: Element) =>
+ container.querySelector(sel)!.click();
+}
+
+export const config: TestConfig = {
+ steps: [{}, click(".show-0"), click(".show-1"), click(".change-1")],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..d69b996f791
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,47 @@
+// tags/theme-display.marko
+const $template$2 = " ";
+const $walks$2 = "bD l";
+const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", "");
+const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme));
+const $setup$2 = $context_theme;
+var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2);
+
+// tags/theme-provider.marko
+const $template$1 = "change ";
+const $walks$1 = "b%b b";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko"));
+const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() {
+ $theme($scope, "dark");
+}));
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/theme-provider.marko");
+ $theme($scope, "light");
+ $setup__script($scope);
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1);
+const $if_content__setup = ($scope) => {
+ $setup$2($scope["#childScope/0"]);
+};
+const $themeprovider_content__if = /*@__PURE__*/ _if("#text/1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $if_content__setup);
+const $themeprovider_content__show = /*@__PURE__*/ _let("show/2", ($scope) => $themeprovider_content__if($scope, $scope.show ? 0 : 1));
+const $themeprovider_content__setup__script = _script("__tests__/template.marko_1", ($scope) => _on($scope["#button/0"], "click", function() {
+ $themeprovider_content__show($scope, true);
+}));
+const $themeprovider_content__setup = ($scope) => {
+ $themeprovider_content__show($scope, false);
+ $themeprovider_content__setup__script($scope);
+};
+const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "show ", " b%c", $themeprovider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..915f42576c6
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.js
@@ -0,0 +1,22 @@
+// tags/theme-display.marko
+const $template = " ";
+const $walks = "bD l";
+const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0");
+const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b));
+const $setup = $context_theme;
+
+// tags/theme-provider.marko
+const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "c"));
+const $setup__script = _script("c0", ($scope) => _on($scope.b, "click", function() {
+ $theme($scope, "dark");
+}));
+
+// template.marko
+const $if_content__setup = ($scope) => {
+ $setup($scope.a);
+};
+const $themeprovider_content__if = /*@__PURE__*/ _if(1, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $if_content__setup);
+const $themeprovider_content__show = /*@__PURE__*/ _let(2, ($scope) => $themeprovider_content__if($scope, $scope.c ? 0 : 1));
+const $themeprovider_content__setup__script = _script("a0", ($scope) => _on($scope.a, "click", function() {
+ $themeprovider_content__show($scope, true);
+}));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..1a01ed615b5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,47 @@
+// tags/theme-display.marko
+var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", "");
+ _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("__tests__/tags/theme-provider.marko", theme, () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`change ${_el_resume($scope0_id, "#button/1")}`);
+ _script($scope0_id, "__tests__/tags/theme-provider.marko_0");
+ writeScope($scope0_id, {}, "__tests__/tags/theme-provider.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ let show = false;
+ _html(`show ${_el_resume($scope1_id, "#button/0")}`);
+ _if(() => {
+ if (show) {
+ const $scope2_id = _scope_id();
+ theme_display_default({});
+ writeScope($scope2_id, {}, "__tests__/template.marko", "6:4");
+ return 0;
+ }
+ }, $scope1_id, "#text/1");
+ _script($scope1_id, "__tests__/template.marko_1");
+ writeScope($scope1_id, {}, "__tests__/template.marko", "3:2");
+ _resume_branch($scope1_id);
+ }) });
+ writeScope($scope0_id, {}, "__tests__/template.marko", 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..c9706ce1918
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.js
@@ -0,0 +1,37 @@
+// tags/theme-display.marko
+var theme_display_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("c", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("c", "light", () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`change ${_el_resume($scope0_id, "b")}`);
+ _script($scope0_id, "c0");
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("a1", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ _html(`show ${_el_resume($scope1_id, "a")}`);
+ _if(() => {}, $scope1_id, "b");
+ _script($scope1_id, "a0");
+ writeScope($scope1_id, {});
+ _resume_branch($scope1_id);
+ }) });
+ writeScope($scope0_id, {});
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..034b1318153
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.debug.md
@@ -0,0 +1,66 @@
+# Render
+```html
+
+ show
+
+
+ change
+
+```
+
+# Update
+```js
+container.querySelector("button.show").click();
+```
+```html
+
+ show
+
+
+ light
+
+
+ change
+
+```
+## Change
+```
+INSERT: .show + .display
+UPDATE: .display::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector("button.change").click();
+```
+```html
+
+ show
+
+
+ dark
+
+
+ change
+
+```
+## Change
+```
+UPDATE: .display::text "light" => "dark"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.md
new file mode 100644
index 00000000000..034b1318153
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.md
@@ -0,0 +1,66 @@
+# Render
+```html
+
+ show
+
+
+ change
+
+```
+
+# Update
+```js
+container.querySelector("button.show").click();
+```
+```html
+
+ show
+
+
+ light
+
+
+ change
+
+```
+## Change
+```
+INSERT: .show + .display
+UPDATE: .display::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector("button.change").click();
+```
+```html
+
+ show
+
+
+ dark
+
+
+ change
+
+```
+## Change
+```
+UPDATE: .display::text "light" => "dark"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..6cb5ddf5af0
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.debug.html
@@ -0,0 +1,16 @@
+show change
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.html
new file mode 100644
index 00000000000..1a1fcf50eda
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.html
@@ -0,0 +1,12 @@
+show change
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/sizes.json
new file mode 100644
index 00000000000..163681278b2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/sizes.json
@@ -0,0 +1,19 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 6850,
+ "brotli": 3086
+ },
+ "files": {
+ "tags/theme-display.marko": 347,
+ "tags/theme-provider.marko": 253,
+ "template.marko": 557
+ }
+ }
+ },
+ "html": {
+ "min": 458,
+ "brotli": 299
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-display.marko
new file mode 100644
index 00000000000..60b9ab808db
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-display.marko
@@ -0,0 +1,2 @@
+
+${theme}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-provider.marko
new file mode 100644
index 00000000000..d9808943a47
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-provider.marko
@@ -0,0 +1,5 @@
+
+
+ <${input.content}/>
+
+change
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/template.marko
new file mode 100644
index 00000000000..9abf3edb3db
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/template.marko
@@ -0,0 +1,9 @@
+// SSR renders the `` off; the first click creates the consumer branch
+// client-side (resolved via the serialized entry), the second updates it.
+
+
+ show
+
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/test.ts
new file mode 100644
index 00000000000..6e8dde4ad9e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/test.ts
@@ -0,0 +1,13 @@
+import type { TestConfig } from "../../main.test";
+
+function show(container: Element) {
+ container.querySelector("button.show")!.click();
+}
+
+function change(container: Element) {
+ container.querySelector("button.change")!.click();
+}
+
+export const config: TestConfig = {
+ steps: [{}, show, change],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..a0b82da4719
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,38 @@
+// tags/theme-display.marko
+const $template$2 = " ";
+const $walks$2 = "bD l";
+const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", "");
+const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme));
+const $setup$2 = $context_theme;
+var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2);
+
+// tags/theme-provider.marko
+const $template$1 = "toggle ";
+const $walks$1 = "b%b b";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko"));
+const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() {
+ $theme($scope, $scope.theme === "light" ? "dark" : "light");
+}));
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/theme-provider.marko");
+ $theme($scope, "light");
+ $setup__script($scope);
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1);
+const $themeprovider_content__setup = ($scope) => {
+ $setup$2($scope["#childScope/0"]);
+};
+const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0) => `${_w0}static
`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$2), $themeprovider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..2f5203cf1e9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.js
@@ -0,0 +1,9 @@
+// tags/theme-display.marko
+const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0");
+const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b));
+
+// tags/theme-provider.marko
+const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "c"));
+const $setup__script = _script("c0", ($scope) => _on($scope.b, "click", function() {
+ $theme($scope, $scope.f === "light" ? "dark" : "light");
+}));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..8b2070a4056
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,36 @@
+// tags/theme-display.marko
+var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", "");
+ _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("__tests__/tags/theme-provider.marko", theme, () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`toggle ${_el_resume($scope0_id, "#button/1")}`);
+ _script($scope0_id, "__tests__/tags/theme-provider.marko_0");
+ writeScope($scope0_id, { theme }, "__tests__/tags/theme-provider.marko", 0, { theme: "2:6" });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ theme_display_default({});
+ _html("static
");
+ }) });
+ writeScope($scope0_id, {}, "__tests__/template.marko", 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..b78e6a20d96
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.js
@@ -0,0 +1,35 @@
+// tags/theme-display.marko
+var theme_display_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// tags/theme-provider.marko
+var theme_provider_default = _template("c", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let theme = "light";
+ _context_provide("c", theme, () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`toggle ${_el_resume($scope0_id, "b")}`);
+ _script($scope0_id, "c0");
+ writeScope($scope0_id, { f: theme });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ theme_provider_default({ content: _content("a0", () => {
+ _scope_reason();
+ _scope_id();
+ theme_display_default({});
+ _html("static
");
+ }) });
+ writeScope($scope0_id, {});
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..1dd8dc8ce5e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.debug.md
@@ -0,0 +1,58 @@
+# Render
+```html
+
+ light
+
+
+ static
+
+
+ toggle
+
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ dark
+
+
+ static
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "light" => "dark"
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ light
+
+
+ static
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "dark" => "light"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.md
new file mode 100644
index 00000000000..1dd8dc8ce5e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.md
@@ -0,0 +1,58 @@
+# Render
+```html
+
+ light
+
+
+ static
+
+
+ toggle
+
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ dark
+
+
+ static
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "light" => "dark"
+```
+
+# Update
+```js
+container.querySelector("button.toggle").click();
+```
+```html
+
+ light
+
+
+ static
+
+
+ toggle
+
+```
+## Change
+```
+UPDATE: span::text "dark" => "light"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..5fb2db51dca
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.debug.html
@@ -0,0 +1,23 @@
+light
+static
toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.html
new file mode 100644
index 00000000000..0568bdef5be
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.html
@@ -0,0 +1,16 @@
+light
+static
toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/sizes.json
new file mode 100644
index 00000000000..ac3fdec49ee
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/sizes.json
@@ -0,0 +1,18 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 3288,
+ "brotli": 1641
+ },
+ "files": {
+ "tags/theme-display.marko": 240,
+ "tags/theme-provider.marko": 286
+ }
+ }
+ },
+ "html": {
+ "min": 473,
+ "brotli": 319
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-display.marko
new file mode 100644
index 00000000000..f7293b73a54
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-display.marko
@@ -0,0 +1,2 @@
+
+${theme}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-provider.marko
new file mode 100644
index 00000000000..3efeca46d5d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-provider.marko
@@ -0,0 +1,8 @@
+// Mutable provider: ``-derived, so consumers subscribe.
+
+
+ <${input.content}/>
+
+
+ toggle
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/template.marko
new file mode 100644
index 00000000000..25e08a918ef
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/template.marko
@@ -0,0 +1,4 @@
+
+
+ static
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/test.ts
new file mode 100644
index 00000000000..a99c7830772
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/test.ts
@@ -0,0 +1,9 @@
+import type { TestConfig } from "../../main.test";
+
+function toggle(container: Element) {
+ container.querySelector("button.toggle")!.click();
+}
+
+export const config: TestConfig = {
+ steps: [{}, toggle, toggle],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..ee53d2758a9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,35 @@
+// resolve-label.js
+function resolveLabel($global, _marker) {
+ return $global.label;
+}
+
+// labeled-region.marko
+const $template$1 = " ";
+const $walks$1 = "bD l%c";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/1");
+const $label = /*@__PURE__*/ _const("label", ($scope) => _text($scope["#text/0"], $scope.label));
+function $setup$1($scope) {
+ $scope["#ContextValue"] = resolveLabel($scope.$global, "self_context_sentinel");
+ _context_branch($scope, "__tests__/labeled-region.marko");
+ $label($scope, _context_read($scope, "label", "__tests__/labeled-region.marko", "./labeled-region.marko"));
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/1");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var labeled_region_default = /*@__PURE__*/ _template("__tests__/labeled-region.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0} `)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}& D l`)($walks$1);
+const $LabeledRegion_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "static
", "b");
+const $count = /*@__PURE__*/ _let("count/3", ($scope) => _text($scope["#text/2"], $scope.count));
+const $setup__script = _script("__tests__/template.marko_0", ($scope) => _on($scope["#button/1"], "click", function() {
+ $count($scope, $scope.count + 1);
+}));
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $LabeledRegion_content($scope));
+ $count($scope, 0);
+ $setup__script($scope);
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..51ecbe37266
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.js
@@ -0,0 +1,5 @@
+// template.marko
+const $count = /*@__PURE__*/ _let(3, ($scope) => _text($scope.c, $scope.d));
+const $setup__script = _script("b1", ($scope) => _on($scope.b, "click", function() {
+ $count($scope, $scope.d + 1);
+}));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..b113a2e0f04
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,33 @@
+// resolve-label.js
+function resolveLabel($global, _marker) {
+ return $global.label;
+}
+
+// labeled-region.marko
+var labeled_region_default = _template("__tests__/labeled-region.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("__tests__/labeled-region.marko", resolveLabel($global(), "self_context_sentinel"), () => {
+ const label = _context_get("__tests__/labeled-region.marko", "./labeled-region.marko");
+ _html(`${_escape(label)}${_el_resume($scope0_id, "#text/0")} `);
+ _dynamic_tag($scope0_id, "#text/1", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ writeScope($scope0_id, {}, "__tests__/labeled-region.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ labeled_region_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ _html("static
");
+ }) });
+ let count = 0;
+ _html(`${_escape(count)}${_el_resume($scope0_id, "#text/2")} ${_el_resume($scope0_id, "#button/1")}`);
+ _script($scope0_id, "__tests__/template.marko_0");
+ writeScope($scope0_id, { count }, "__tests__/template.marko", 0, { count: "9:6" });
+ _resume_branch($scope0_id);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..f464cc40f98
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.js
@@ -0,0 +1,32 @@
+// resolve-label.js
+function resolveLabel($global, _marker) {
+ return $global.label;
+}
+
+// labeled-region.marko
+var labeled_region_default = _template("a", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("a", resolveLabel($global(), "self_context_sentinel"), () => {
+ _html(`${_escape(_context_get("a"))}${_el_resume($scope0_id, "a")} `);
+ _dynamic_tag($scope0_id, "b", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ labeled_region_default({ content: _content("b0", () => {
+ _scope_reason();
+ _scope_id();
+ _html("static
");
+ }) });
+ let count = 0;
+ _html(`${_escape(count)}${_el_resume($scope0_id, "c")} ${_el_resume($scope0_id, "b")}`);
+ _script($scope0_id, "b1");
+ writeScope($scope0_id, { d: count });
+ _resume_branch($scope0_id);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..7637e013399
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.debug.md
@@ -0,0 +1,36 @@
+# Render `{"$global":{"label":"intro"}}`
+```html
+
+ intro
+
+
+ static
+
+
+ 0
+
+```
+
+# Update
+```js
+container.querySelector("button.inc").click();
+```
+```html
+
+ intro
+
+
+ static
+
+
+ 1
+
+```
+## Change
+```
+UPDATE: .inc::text "0" => "1"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.md
new file mode 100644
index 00000000000..7637e013399
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.md
@@ -0,0 +1,36 @@
+# Render `{"$global":{"label":"intro"}}`
+```html
+
+ intro
+
+
+ static
+
+
+ 0
+
+```
+
+# Update
+```js
+container.querySelector("button.inc").click();
+```
+```html
+
+ intro
+
+
+ static
+
+
+ 1
+
+```
+## Change
+```
+UPDATE: .inc::text "0" => "1"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..103a1e952b8
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.debug.html
@@ -0,0 +1,12 @@
+intro
+static
0
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.html
new file mode 100644
index 00000000000..1080a7a22d8
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.html
@@ -0,0 +1,9 @@
+intro
+static
0
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/labeled-region.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/labeled-region.marko
new file mode 100644
index 00000000000..171c1ea0c9f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/labeled-region.marko
@@ -0,0 +1,9 @@
+import resolveLabel from "./resolve-label.js"
+
+// A server-only value consumed by the providing template itself: the whole
+// context (provide and self-consume) compiles to zero client bytes.
+
+
+ ${label}
+ <${input.content}/>
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/resolve-label.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/resolve-label.js
new file mode 100644
index 00000000000..2906baba699
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/resolve-label.js
@@ -0,0 +1,5 @@
+// Sentinel reachable only through the provider's context value expression;
+// the dom bundle pins that this module tree-shakes out.
+export default function resolveLabel($global, _marker) {
+ return $global.label;
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/sizes.json
new file mode 100644
index 00000000000..4a62ed4b982
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/sizes.json
@@ -0,0 +1,12 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "min": 2618,
+ "brotli": 1330
+ }
+ },
+ "html": {
+ "min": 406,
+ "brotli": 272
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/template.marko
new file mode 100644
index 00000000000..0c30ab83ac2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/template.marko
@@ -0,0 +1,10 @@
+import LabeledRegion from "./labeled-region.marko"
+
+
+ static
+
+
+// Unrelated interactivity keeps the client bundle nonempty, so the
+// `bundle_excludes` assertion is meaningful.
+
+${count}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/test.ts
new file mode 100644
index 00000000000..1beb153f57c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/test.ts
@@ -0,0 +1,12 @@
+import type { TestConfig } from "../../main.test";
+
+function inc(container: Element) {
+ container.querySelector("button.inc")!.click();
+}
+
+export const config: TestConfig = {
+ steps: [{ $global: { label: "intro" } }, inc],
+ // The self-consume drops to the immutable tier once the provide side
+ // classifies server-only: no context runtime may reach the client.
+ bundle_excludes: { dom: ["self_context_sentinel", "_context"] },
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..ebab8d69328
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,48 @@
+// tags/labeled-region.marko
+const $template$1 = "toggle ";
+const $walks$1 = "b b%c";
+const $context_label = _context_closure("__tests__/tags/labeled-region.marko", ($scope2, $provider) => $if_content__label($scope2, $provider["#ContextValue"]), "__tests__/tags/labeled-region.marko_1_label/context", "");
+const $if_content__dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $if_content__input_content = /*@__PURE__*/ _if_closure("#text/1", 0, ($scope) => $if_content__dynamicTag($scope, $scope._.input_content));
+const $if_content__setup = ($scope) => {
+ $if_content__input_content._($scope);
+ $context_label($scope);
+};
+const $if_content__label = /*@__PURE__*/ _const("label", ($scope) => _text($scope["#text/1"], $scope.label));
+const $if = /*@__PURE__*/ _if("#text/1", "
", "b%bD l", $if_content__setup);
+const $open = /*@__PURE__*/ _let("open/6", ($scope) => $if($scope, $scope.open ? 0 : 1));
+const $setup__script = _script("__tests__/tags/labeled-region.marko_0", ($scope) => _on($scope["#button/0"], "click", function() {
+ $open($scope, !$scope.open);
+}));
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/labeled-region.marko");
+ $open($scope, true);
+ $setup__script($scope);
+}
+const $input_label = /*@__PURE__*/ _const("input_label", ($scope) => {
+ _attr_class($scope["#button/0"], `toggle-${$scope.input_label}`);
+ _context_value($scope, $scope.input_label, "__tests__/tags/labeled-region.marko");
+});
+const $input = ($scope, input) => {
+ $input_label($scope, input.label);
+ $input_content($scope, input.content);
+};
+const $input_content = /*@__PURE__*/ _const("input_content", $if_content__input_content);
+var labeled_region_default = /*@__PURE__*/ _template("__tests__/tags/labeled-region.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1);
+const $labeledregion_content2 = _content_resume("__tests__/template.marko_2_content", "content ", "b");
+const $labeledregion_content__setup = ($scope) => {
+ $setup$1($scope["#childScope/0"]);
+ $input_content($scope["#childScope/0"], $labeledregion_content2($scope));
+ $input_label($scope["#childScope/0"], "inner");
+};
+const $labeledregion_content = _content_resume("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $labeledregion_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content($scope["#childScope/0"], $labeledregion_content($scope));
+ $input_label($scope["#childScope/0"], "outer");
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..d79c6301bed
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.js
@@ -0,0 +1,35 @@
+// tags/labeled-region.marko
+const $template = "toggle ";
+const $walks = "b b%c";
+const $context_label = _context_closure("b", ($scope2, $provider) => $if_content__label($scope2, $provider["I"]), "b0");
+const $if_content__dynamicTag = /*@__PURE__*/ _dynamic_tag(0);
+const $if_content__input_content = /*@__PURE__*/ _if_closure(1, 0, ($scope) => $if_content__dynamicTag($scope, $scope._.f));
+const $if_content__setup = ($scope) => {
+ $if_content__input_content._($scope);
+ $context_label($scope);
+};
+const $if_content__label = /*@__PURE__*/ _const(2, ($scope) => _text($scope.b, $scope.c));
+const $if = /*@__PURE__*/ _if(1, "
", "b%bD l", $if_content__setup);
+const $open = /*@__PURE__*/ _let(6, ($scope) => $if($scope, $scope.g ? 0 : 1));
+const $setup__script = _script("b1", ($scope) => _on($scope.a, "click", function() {
+ $open($scope, !$scope.g);
+}));
+function $setup($scope) {
+ _context_branch($scope, "b");
+ $open($scope, true);
+ $setup__script($scope);
+}
+const $input_label = /*@__PURE__*/ _const(4, ($scope) => {
+ _attr_class($scope.a, `toggle-${$scope.e}`);
+ _context_value($scope, $scope.e, "b");
+});
+const $input_content = /*@__PURE__*/ _const(5, $if_content__input_content);
+
+// template.marko
+const $labeledregion_content2 = _content_resume("a0", "content ", "b");
+const $labeledregion_content__setup = ($scope) => {
+ $setup($scope.a);
+ $input_content($scope.a, $labeledregion_content2($scope));
+ $input_label($scope.a, "inner");
+};
+const $labeledregion_content = _content_resume("a1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks), $labeledregion_content__setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..24dba71eaa3
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,51 @@
+// tags/labeled-region.marko
+var labeled_region_default = _template("__tests__/tags/labeled-region.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let open = true;
+ _context_provide("__tests__/tags/labeled-region.marko", input.label, () => {
+ _html(`toggle ${_el_resume($scope0_id, "#button/0")}`);
+ _if(() => {
+ if (open) {
+ const $scope1_id = _scope_id();
+ _dynamic_tag($scope1_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ const label = _context_link($scope1_id, "__tests__/tags/labeled-region.marko", "__tests__/tags/labeled-region.marko_1_label/context", "");
+ _html(`${_escape(label)}${_el_resume($scope1_id, "#text/1")}
`);
+ writeScope($scope1_id, {}, "__tests__/tags/labeled-region.marko", "10:4");
+ return 0;
+ }
+ }, $scope0_id, "#text/1");
+ }, $scope0_id);
+ _script($scope0_id, "__tests__/tags/labeled-region.marko_0");
+ writeScope($scope0_id, {
+ input_content: input.content,
+ open
+ }, "__tests__/tags/labeled-region.marko", 0, {
+ input_content: ["input.content"],
+ open: "5:6"
+ });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ labeled_region_default({
+ label: "outer",
+ content: _content_resume("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ labeled_region_default({
+ label: "inner",
+ content: _content_resume("__tests__/template.marko_2_content", () => {
+ _scope_reason();
+ const $scope2_id = _scope_id();
+ _html("content ");
+ }, $scope1_id)
+ });
+ writeScope($scope1_id, {}, "__tests__/template.marko", "1:2");
+ }, $scope0_id)
+ });
+ writeScope($scope0_id, {}, "__tests__/template.marko", 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..41e11a51d4f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.js
@@ -0,0 +1,47 @@
+// tags/labeled-region.marko
+var labeled_region_default = _template("b", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let open = true;
+ _context_provide("b", input.label, () => {
+ _html(`toggle ${_el_resume($scope0_id, "a")}`);
+ _if(() => {
+ {
+ const $scope1_id = _scope_id();
+ _dynamic_tag($scope1_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ _html(`${_escape(_context_link($scope1_id, "b", "b0"))}${_el_resume($scope1_id, "b")}
`);
+ writeScope($scope1_id, {});
+ return 0;
+ }
+ }, $scope0_id, "b");
+ }, $scope0_id);
+ _script($scope0_id, "b1");
+ writeScope($scope0_id, {
+ f: input.content,
+ g: open
+ });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ labeled_region_default({
+ label: "outer",
+ content: _content_resume("a1", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ labeled_region_default({
+ label: "inner",
+ content: _content_resume("a0", () => {
+ _scope_reason();
+ _scope_id();
+ _html("content ");
+ }, $scope1_id)
+ });
+ writeScope($scope1_id, {});
+ }, $scope0_id)
+ });
+ writeScope($scope0_id, {});
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..b309e42f683
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.debug.md
@@ -0,0 +1,85 @@
+# Render
+```html
+
+ toggle
+
+
+ toggle
+
+
+ content
+
+
+ inner
+
+
+ outer
+
+```
+
+# Update
+```js
+container.querySelector(`button.toggle-${label}`).click();
+```
+```html
+
+ toggle
+
+```
+## Change
+```
+REMOVE: .toggle-outer + button
+REMOVE: .toggle-outer + span
+REMOVE: .toggle-outer + div
+REMOVE: .toggle-outer + div
+```
+
+# Update
+```js
+container.querySelector(`button.toggle-${label}`).click();
+```
+```html
+
+ toggle
+
+
+ toggle
+
+
+ content
+
+
+ inner
+
+
+ outer
+
+```
+## Change
+```
+INSERT: .toggle-outer + div
+INSERT: .toggle-outer + .toggle-inner
+UPDATE: div:nth-of-type(2)::text " " => "outer"
+INSERT: .toggle-inner + div
+UPDATE: .toggle-inner[class] null => "toggle-inner"
+INSERT: .toggle-inner + span
+UPDATE: div:nth-of-type(1)::text " " => "inner"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.md
new file mode 100644
index 00000000000..b309e42f683
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.md
@@ -0,0 +1,85 @@
+# Render
+```html
+
+ toggle
+
+
+ toggle
+
+
+ content
+
+
+ inner
+
+
+ outer
+
+```
+
+# Update
+```js
+container.querySelector(`button.toggle-${label}`).click();
+```
+```html
+
+ toggle
+
+```
+## Change
+```
+REMOVE: .toggle-outer + button
+REMOVE: .toggle-outer + span
+REMOVE: .toggle-outer + div
+REMOVE: .toggle-outer + div
+```
+
+# Update
+```js
+container.querySelector(`button.toggle-${label}`).click();
+```
+```html
+
+ toggle
+
+
+ toggle
+
+
+ content
+
+
+ inner
+
+
+ outer
+
+```
+## Change
+```
+INSERT: .toggle-outer + div
+INSERT: .toggle-outer + .toggle-inner
+UPDATE: div:nth-of-type(2)::text " " => "outer"
+INSERT: .toggle-inner + div
+UPDATE: .toggle-inner[class] null => "toggle-inner"
+INSERT: .toggle-inner + span
+UPDATE: div:nth-of-type(1)::text " " => "inner"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..0b1b886f1a4
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.debug.html
@@ -0,0 +1,43 @@
+toggle toggle content
+inner
+outer
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.html
new file mode 100644
index 00000000000..cc9ba7e0ced
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.html
@@ -0,0 +1,28 @@
+toggle toggle content
+inner
+outer
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-self/sizes.json
new file mode 100644
index 00000000000..5b60ae65e08
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/sizes.json
@@ -0,0 +1,18 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 14438,
+ "brotli": 5574
+ },
+ "files": {
+ "tags/labeled-region.marko": 1268,
+ "template.marko": 474
+ }
+ }
+ },
+ "html": {
+ "min": 732,
+ "brotli": 394
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/tags/labeled-region.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self/tags/labeled-region.marko
new file mode 100644
index 00000000000..5429bc32a69
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/tags/labeled-region.marko
@@ -0,0 +1,15 @@
+// A self-referencing consume (inside the provide's own body) resolves this
+// instance; nested instances shadow per dynamic extent. The ``
+// gives each instance its own resumable branch (nested same-template
+// providers sharing one branch are rejected -- see `_context_branch`).
+
+
+
+ toggle
+
+
+ <${input.content}/>
+
+ ${label}
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self/template.marko
new file mode 100644
index 00000000000..a0ba6eb862c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/template.marko
@@ -0,0 +1,5 @@
+
+
+ content
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-self/test.ts
new file mode 100644
index 00000000000..733a3d3dbfa
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/test.ts
@@ -0,0 +1,12 @@
+import type { TestConfig } from "../../main.test";
+
+function toggle(label: string) {
+ return (container: Element) =>
+ container
+ .querySelector(`button.toggle-${label}`)!
+ .click();
+}
+
+export const config: TestConfig = {
+ steps: [{}, toggle("outer"), toggle("outer")],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..aa8b04ba57d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,36 @@
+// tags/counter.marko
+const $template2 = "add ";
+const $walks2 = "b bD l";
+const $context_count = _context_closure("__tests__/template.marko", ($scope2, $provider) => $count$1($scope2, $provider["#ContextValue"]), "__tests__/tags/counter.marko_0_count/context", "../template.marko");
+const $count$1 = /*@__PURE__*/ _const("count", ($scope) => _text($scope["#text/1"], $scope.count));
+const $setup2__script = _script("__tests__/tags/counter.marko_0", ($scope) => _on($scope["#button/0"], "click", function() {
+ _context_write($scope, "__tests__/template.marko", $scope.count + 1);
+}));
+function $setup2($scope) {
+ $context_count($scope);
+ $setup2__script($scope);
+}
+var counter_default = /*@__PURE__*/ _template("__tests__/tags/counter.marko", $template2, $walks2, $setup2);
+
+// template.marko
+const $template = "log ";
+const $walks = "b%b b";
+const $count = /*@__PURE__*/ _let("count/2", ($scope) => _context_value($scope, $scope.count, "__tests__/template.marko"));
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $setup__script = _script("__tests__/template.marko_0", ($scope) => _on($scope["#button/1"], "click", function() {
+ console.log($scope.$global.settings.message);
+}));
+function $setup($scope) {
+ _context_branch($scope, "__tests__/template.marko");
+ _context_change($scope, $valueChange($scope));
+ $count($scope, 0);
+ $dynamicTag($scope, counter_default);
+ $setup__script($scope);
+}
+function $valueChange($scope) {
+ return (_new_count) => {
+ $count($scope, _new_count);
+ };
+}
+_resume("__tests__/template.marko_0/valueChange", $valueChange);
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..d7a63180867
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.js
@@ -0,0 +1,18 @@
+// tags/counter.marko
+const $context_count = _context_closure("a", ($scope2, $provider) => $count$1($scope2, $provider["I"]), "b0");
+const $count$1 = /*@__PURE__*/ _const(2, ($scope) => _text($scope.b, $scope.c));
+const $setup2__script = _script("b1", ($scope) => _on($scope.a, "click", function() {
+ _context_write($scope, "a", $scope.c + 1);
+}));
+
+// template.marko
+const $count = /*@__PURE__*/ _let(2, ($scope) => _context_value($scope, $scope.c, "a"));
+const $setup__script = _script("a1", ($scope) => _on($scope.b, "click", function() {
+ console.log($scope.$.settings.message);
+}));
+function $valueChange($scope) {
+ return (_new_count) => {
+ $count($scope, _new_count);
+ };
+}
+_resume("a0", $valueChange);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..097ea48105a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,26 @@
+// tags/counter.marko
+var counter_default = _template("__tests__/tags/counter.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = _context_link($scope0_id, "__tests__/template.marko", "__tests__/tags/counter.marko_0_count/context", "../template.marko");
+ _html(`add ${_el_resume($scope0_id, "#button/0")}${_escape(count)}${_el_resume($scope0_id, "#text/1")} `);
+ _script($scope0_id, "__tests__/tags/counter.marko_0");
+ writeScope($scope0_id, { count }, "__tests__/tags/counter.marko", 0, { count: "1:10" });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = 0;
+ _context_provide("__tests__/template.marko", count, () => {
+ _dynamic_tag($scope0_id, "#text/0", counter_default, {}, 0, 0, 0);
+ }, $scope0_id);
+ _html(`log ${_el_resume($scope0_id, "#button/1")}`);
+ _script($scope0_id, "__tests__/template.marko_0");
+ writeScope($scope0_id, { "TagVariableChange:#ContextValue": _resume((_new_count) => {
+ count = _new_count;
+ }, "__tests__/template.marko_0/valueChange", $scope0_id) || void 0 }, "__tests__/template.marko", 0);
+ _resume_branch($scope0_id);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..4e623d217a0
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.js
@@ -0,0 +1,26 @@
+// tags/counter.marko
+var counter_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = _context_link($scope0_id, "a", "b0");
+ _html(`add ${_el_resume($scope0_id, "a")}${_escape(count)}${_el_resume($scope0_id, "b")} `);
+ _script($scope0_id, "b1");
+ writeScope($scope0_id, { c: count });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = 0;
+ _context_provide("a", count, () => {
+ _dynamic_tag($scope0_id, "a", counter_default, {}, 0, 0, 0);
+ }, $scope0_id);
+ _html(`log ${_el_resume($scope0_id, "b")}`);
+ _script($scope0_id, "a1");
+ writeScope($scope0_id, { MI: _resume((_new_count) => {
+ count = _new_count;
+ }, "a0", $scope0_id) || void 0 });
+ _resume_branch($scope0_id);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..54754384386
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.debug.md
@@ -0,0 +1,44 @@
+# Render `{"$global":{"settings":{"message":"a globals message long enough to dedup"},"serializedGlobals":["settings"]}}`
+```html
+
+ add
+
+
+ 0
+
+
+ log
+
+```
+
+# Update
+```js
+container.querySelector("button.add").click();
+```
+```html
+
+ add
+
+
+ 1
+
+
+ log
+
+```
+## Change
+```
+UPDATE: .count::text "0" => "1"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.md
new file mode 100644
index 00000000000..54754384386
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.md
@@ -0,0 +1,44 @@
+# Render `{"$global":{"settings":{"message":"a globals message long enough to dedup"},"serializedGlobals":["settings"]}}`
+```html
+
+ add
+
+
+ 0
+
+
+ log
+
+```
+
+# Update
+```js
+container.querySelector("button.add").click();
+```
+```html
+
+ add
+
+
+ 1
+
+
+ log
+
+```
+## Change
+```
+UPDATE: .count::text "0" => "1"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..72a401508e0
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.debug.html
@@ -0,0 +1,31 @@
+add 0 log
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.html
new file mode 100644
index 00000000000..0d6104083fe
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.html
@@ -0,0 +1,21 @@
+add 0 log
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/sizes.json
new file mode 100644
index 00000000000..03b98712d29
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/sizes.json
@@ -0,0 +1,18 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 3435,
+ "brotli": 1683
+ },
+ "files": {
+ "tags/counter.marko": 369,
+ "template.marko": 381
+ }
+ }
+ },
+ "html": {
+ "min": 568,
+ "brotli": 348
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/tags/counter.marko b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/tags/counter.marko
new file mode 100644
index 00000000000..8b494280a84
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/tags/counter.marko
@@ -0,0 +1,3 @@
+
+add
+${count}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/template.marko
new file mode 100644
index 00000000000..e52f87e2a4d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/template.marko
@@ -0,0 +1,8 @@
+// A root writable provider stamps its branch link on scope 0, which must
+// coexist with the serialized-globals partial for the same slot (the
+// serializer's signed slot deltas) for post-resume writes to work.
+
+
+
+
+log
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/test.ts
new file mode 100644
index 00000000000..151112e4e82
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/test.ts
@@ -0,0 +1,17 @@
+import type { TestConfig } from "../../main.test";
+
+function add(container: Element) {
+ container.querySelector("button.add")!.click();
+}
+
+export const config: TestConfig = {
+ steps: [
+ {
+ $global: {
+ settings: { message: "a globals message long enough to dedup" },
+ serializedGlobals: ["settings"],
+ },
+ },
+ add,
+ ],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..3f069cef5a4
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,59 @@
+// tags/info-display.marko
+const $template$2 = " ";
+const $walks$2 = "bD l";
+const $theme = ($scope, theme) => _text($scope["#text/0"], theme);
+function $setup$2($scope) {
+ $theme($scope, _context_read($scope, "theme", "__tests__/tags/info-provider.marko", ""));
+}
+var info_display_default = /*@__PURE__*/ _template("__tests__/tags/info-display.marko", $template$2, $walks$2, $setup$2);
+
+// resolve-info.js
+function resolveInfo($global, _marker) {
+ return $global.theme;
+}
+
+// tags/info-provider.marko
+const $template$1 = "";
+const $walks$1 = "b%c";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+function $setup$1($scope) {
+ $scope["#ContextValue"] = resolveInfo($scope.$global, "toggle_context_sentinel");
+ _context_branch($scope, "__tests__/tags/info-provider.marko");
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var info_provider_default = /*@__PURE__*/ _template("__tests__/tags/info-provider.marko", $template$1, "b%c", $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)("b%c");
+const $for_content__setup = ($scope) => {
+ $setup$2($scope["#childScope/0"]);
+};
+const $if_content__setup = ($scope) => {
+ $setup$2($scope["#childScope/0"]);
+};
+const $infoprovider_content__if = /*@__PURE__*/ _if("#text/2", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $if_content__setup);
+const $infoprovider_content__show = /*@__PURE__*/ _let("show/4", ($scope) => $infoprovider_content__if($scope, $scope.show ? 0 : 1));
+const $infoprovider_content__for = /*@__PURE__*/ _for_of("#text/3", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $for_content__setup);
+const $infoprovider_content__items = /*@__PURE__*/ _let("items/5", ($scope) => $infoprovider_content__for($scope, [$scope.items, (x) => x]));
+const $infoprovider_content__setup__script = _script("__tests__/template.marko_1", ($scope) => {
+ _on($scope["#button/0"], "click", function() {
+ $infoprovider_content__show($scope, !$scope.show);
+ });
+ _on($scope["#button/1"], "click", function() {
+ $infoprovider_content__items($scope, [...$scope.items, $scope.items?.length]);
+ });
+});
+const $infoprovider_content__setup = ($scope) => {
+ $infoprovider_content__show($scope, false);
+ $infoprovider_content__items($scope, []);
+ $infoprovider_content__setup__script($scope);
+};
+const $infoprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "show add ", " b b%b%c", $infoprovider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $infoprovider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..5388be1de76
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.js
@@ -0,0 +1,27 @@
+// tags/info-display.marko
+const $template = " ";
+const $walks = "bD l";
+const $theme = ($scope, theme) => _text($scope.a, theme);
+function $setup($scope) {
+ $theme($scope, _context_read($scope, 1, "c"));
+}
+
+// template.marko
+const $for_content__setup = ($scope) => {
+ $setup($scope.a);
+};
+const $if_content__setup = ($scope) => {
+ $setup($scope.a);
+};
+const $infoprovider_content__if = /*@__PURE__*/ _if(2, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $if_content__setup);
+const $infoprovider_content__show = /*@__PURE__*/ _let(4, ($scope) => $infoprovider_content__if($scope, $scope.e ? 0 : 1));
+const $infoprovider_content__for = /*@__PURE__*/ _for_of(3, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $for_content__setup);
+const $infoprovider_content__items = /*@__PURE__*/ _let(5, ($scope) => $infoprovider_content__for($scope, [$scope.f, (x) => x]));
+const $infoprovider_content__setup__script = _script("a0", ($scope) => {
+ _on($scope.a, "click", function() {
+ $infoprovider_content__show($scope, !$scope.e);
+ });
+ _on($scope.b, "click", function() {
+ $infoprovider_content__items($scope, [...$scope.f, $scope.f?.length]);
+ });
+});
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..d8da5907593
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,60 @@
+// tags/info-display.marko
+var info_display_default = _template("__tests__/tags/info-display.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const theme = _context_get("__tests__/tags/info-provider.marko", "");
+ _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/tags/info-display.marko", 0);
+});
+
+// resolve-info.js
+function resolveInfo($global, _marker) {
+ return $global.theme;
+}
+
+// tags/info-provider.marko
+var info_provider_default = _template("__tests__/tags/info-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("__tests__/tags/info-provider.marko", resolveInfo($global(), "toggle_context_sentinel"), () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/tags/info-provider.marko", 0);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ info_provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ let show = false;
+ let items = [];
+ _html(`show ${_el_resume($scope1_id, "#button/0")}add ${_el_resume($scope1_id, "#button/1")}`);
+ _context_reserve("__tests__/tags/info-provider.marko");
+ _if(() => {
+ if (show) {
+ const $scope2_id = _scope_id();
+ info_display_default({});
+ writeScope($scope2_id, {}, "__tests__/template.marko", "8:4");
+ return 0;
+ }
+ }, $scope1_id, "#text/2");
+ _context_reserve("__tests__/tags/info-provider.marko");
+ _for_of(items, (i) => {
+ const $scope3_id = _scope_id();
+ info_display_default({});
+ writeScope($scope3_id, {}, "__tests__/template.marko", "11:4");
+ }, (x) => x, $scope1_id, "#text/3");
+ _script($scope1_id, "__tests__/template.marko_1");
+ writeScope($scope1_id, {
+ show,
+ items
+ }, "__tests__/template.marko", "3:2", {
+ show: "4:8",
+ items: "5:8"
+ });
+ _resume_branch($scope1_id);
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..ddcd97b9ef1
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.js
@@ -0,0 +1,49 @@
+// tags/info-display.marko
+var info_display_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_get("c"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+});
+
+// resolve-info.js
+function resolveInfo($global, _marker) {
+ return $global.theme;
+}
+
+// tags/info-provider.marko
+var info_provider_default = _template("c", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("c", resolveInfo($global(), "toggle_context_sentinel"), () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {});
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ _scope_id();
+ info_provider_default({ content: _content("a1", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ let show = false;
+ let items = [];
+ _html(`show ${_el_resume($scope1_id, "a")}add ${_el_resume($scope1_id, "b")}`);
+ _context_reserve("c");
+ _if(() => {}, $scope1_id, "c");
+ _context_reserve("c");
+ _for_of(items, (i) => {
+ const $scope3_id = _scope_id();
+ info_display_default({});
+ writeScope($scope3_id, {});
+ }, (x) => x, $scope1_id, "d");
+ _script($scope1_id, "a0");
+ writeScope($scope1_id, {
+ e: show,
+ f: items
+ });
+ _resume_branch($scope1_id);
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..48e2a6f8c43
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.debug.md
@@ -0,0 +1,143 @@
+# Render `{"$global":{"theme":"light"}}`
+```html
+
+ show
+
+
+ add
+
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+```
+## Change
+```
+INSERT: .add + span
+UPDATE: span::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+```
+## Change
+```
+REMOVE: .add + span
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+```
+## Change
+```
+INSERT: .add + span
+UPDATE: span::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+
+ light
+
+```
+## Change
+```
+INSERT: span:nth-of-type(1) + span
+UPDATE: span:nth-of-type(2)::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+
+ light
+
+
+ light
+
+```
+## Change
+```
+INSERT: span:nth-of-type(2) + span
+UPDATE: span:nth-of-type(3)::text " " => "light"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.md
new file mode 100644
index 00000000000..48e2a6f8c43
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.md
@@ -0,0 +1,143 @@
+# Render `{"$global":{"theme":"light"}}`
+```html
+
+ show
+
+
+ add
+
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+```
+## Change
+```
+INSERT: .add + span
+UPDATE: span::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+```
+## Change
+```
+REMOVE: .add + span
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+```
+## Change
+```
+INSERT: .add + span
+UPDATE: span::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+
+ light
+
+```
+## Change
+```
+INSERT: span:nth-of-type(1) + span
+UPDATE: span:nth-of-type(2)::text " " => "light"
+```
+
+# Update
+```js
+container.querySelector(selector).click();
+```
+```html
+
+ show
+
+
+ add
+
+
+ light
+
+
+ light
+
+
+ light
+
+```
+## Change
+```
+INSERT: span:nth-of-type(2) + span
+UPDATE: span:nth-of-type(3)::text " " => "light"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..4c1996a2f49
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.debug.html
@@ -0,0 +1,17 @@
+show add
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.html
new file mode 100644
index 00000000000..322eed43ba5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.html
@@ -0,0 +1,14 @@
+show add
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/resolve-info.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/resolve-info.js
new file mode 100644
index 00000000000..14342d276cf
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/resolve-info.js
@@ -0,0 +1,6 @@
+// Sentinel reachable only through the provider's context value expression;
+// the optimize dom bundle pins that this module tree-shakes out even though
+// consumers of the context render client-side.
+export default function resolveInfo($global, _marker) {
+ return $global.theme;
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/sizes.json
new file mode 100644
index 00000000000..4a07e12f722
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/sizes.json
@@ -0,0 +1,18 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 7851,
+ "brotli": 3558
+ },
+ "files": {
+ "tags/info-display.marko": 243,
+ "template.marko": 1051
+ }
+ }
+ },
+ "html": {
+ "min": 462,
+ "brotli": 293
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-display.marko
new file mode 100644
index 00000000000..4e9d0fc55d2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-display.marko
@@ -0,0 +1,2 @@
+
+${theme}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-provider.marko
new file mode 100644
index 00000000000..df3fcd96d9e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-provider.marko
@@ -0,0 +1,5 @@
+import resolveInfo from "../resolve-info.js"
+
+
+ <${input.content}/>
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/template.marko
new file mode 100644
index 00000000000..dc711c6ccce
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/template.marko
@@ -0,0 +1,14 @@
+// Consumers of a server-only context created client-side (a toggled ``
+// and appended `` items) resolve the reserved provider box.
+
+
+
+ show
+ add
+
+
+
+ x>
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/test.ts
new file mode 100644
index 00000000000..099f06eabae
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/test.ts
@@ -0,0 +1,20 @@
+import type { TestConfig } from "../../main.test";
+
+function click(selector: string) {
+ return (container: Element) =>
+ container.querySelector(selector)!.click();
+}
+
+export const config: TestConfig = {
+ steps: [
+ { $global: { theme: "light" } },
+ click("button.show"),
+ click("button.show"),
+ click("button.show"),
+ click("button.add"),
+ click("button.add"),
+ ],
+ // The provider's value expression stays server-side; only its computed
+ // value reaches the client (in the reserved box).
+ bundle_excludes: { dom: ["toggle_context_sentinel"] },
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..753b8286a8c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,37 @@
+// resolve-theme.js
+function resolveTheme($global, _marker) {
+ return $global.theme;
+}
+
+// provider.marko
+const $template$1 = "";
+const $walks$1 = "b%c";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+function $setup$1($scope) {
+ $scope["#ContextValue"] = resolveTheme($scope.$global, "context_value_sentinel");
+ _context_branch($scope, "__tests__/provider.marko");
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var provider_default = /*@__PURE__*/ _template("__tests__/provider.marko", $template$1, "b%c", $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)("b%c");
+const $Provider_content__theme = ($scope, theme) => _text($scope["#text/0"], theme);
+const $Provider_content__loggedTheme = /*@__PURE__*/ _const("loggedTheme");
+const $Provider_content__setup__script = _script("__tests__/template.marko_1", ($scope) => _on($scope["#button/1"], "click", function() {
+ console.log($scope.loggedTheme);
+}));
+const $Provider_content__setup = ($scope) => {
+ $Provider_content__theme($scope, _context_read($scope, "theme", "__tests__/provider.marko", "./provider.marko"));
+ $Provider_content__loggedTheme($scope, _context_read($scope, "loggedTheme", "__tests__/provider.marko", "./provider.marko"));
+ $Provider_content__setup__script($scope);
+};
+const $Provider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "
log theme ", "bD l b", $Provider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $Provider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..d59ca1abca4
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.js
@@ -0,0 +1,4 @@
+// template.marko
+const $Provider_content__setup__script = _script("b0", ($scope) => _on($scope.b, "click", function() {
+ console.log($scope.d);
+}));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..484843ace42
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,29 @@
+// resolve-theme.js
+function resolveTheme($global, _marker) {
+ return $global.theme;
+}
+
+// provider.marko
+var provider_default = _template("__tests__/provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("__tests__/provider.marko", resolveTheme($global(), "context_value_sentinel"), () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/provider.marko", 0);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ const theme = _context_get("__tests__/provider.marko", "./provider.marko");
+ const loggedTheme = _context_get("__tests__/provider.marko", "./provider.marko");
+ _html(`${_escape(theme)}${_el_resume($scope1_id, "#text/0")}
log theme ${_el_resume($scope1_id, "#button/1")}`);
+ _script($scope1_id, "__tests__/template.marko_1");
+ writeScope($scope1_id, { loggedTheme }, "__tests__/template.marko", "3:2", { loggedTheme: "9:12" });
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..7a87752766d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.js
@@ -0,0 +1,29 @@
+// resolve-theme.js
+function resolveTheme($global, _marker) {
+ return $global.theme;
+}
+
+// provider.marko
+var provider_default = _template("a", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("a", resolveTheme($global(), "context_value_sentinel"), () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {});
+});
+
+// template.marko
+var template_default = _template("b", (input) => {
+ _scope_reason();
+ _scope_id();
+ provider_default({ content: _content("b1", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ const theme = _context_get("a");
+ const loggedTheme = _context_get("a");
+ _html(`${_escape(theme)}${_el_resume($scope1_id, "a")}
log theme ${_el_resume($scope1_id, "b")}`);
+ _script($scope1_id, "b0");
+ writeScope($scope1_id, { d: loggedTheme });
+ }) });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..49979dee439
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.debug.md
@@ -0,0 +1,9 @@
+# Render `{"$global":{"theme":"light"}}`
+```html
+
+ light
+
+
+ log theme
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.md
new file mode 100644
index 00000000000..49979dee439
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.md
@@ -0,0 +1,9 @@
+# Render `{"$global":{"theme":"light"}}`
+```html
+
+ light
+
+
+ log theme
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..343379e3479
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.debug.html
@@ -0,0 +1,11 @@
+light
log
+ theme
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.html
new file mode 100644
index 00000000000..ae1429288c3
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.html
@@ -0,0 +1,8 @@
+light
log theme
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/provider.marko
new file mode 100644
index 00000000000..5217b9c1cbb
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/provider.marko
@@ -0,0 +1,7 @@
+import resolveTheme from "./resolve-theme.js"
+
+// `$global` is not client-reactive, so this classifies server-only; the
+// use-site sentinel is checked by the fixture's `bundle_excludes`.
+
+ <${input.content}/>
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/resolve-theme.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/resolve-theme.js
new file mode 100644
index 00000000000..40dd36594f8
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/resolve-theme.js
@@ -0,0 +1,5 @@
+// Sentinel reachable only through the provider's context value expression;
+// the optimize dom bundle pins that this module tree-shakes out.
+export default function resolveTheme($global, _marker) {
+ return $global.theme;
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/sizes.json
new file mode 100644
index 00000000000..f9905293459
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/sizes.json
@@ -0,0 +1,12 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "min": 1891,
+ "brotli": 976
+ }
+ },
+ "html": {
+ "min": 382,
+ "brotli": 262
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/template.marko
new file mode 100644
index 00000000000..c17d9f27d42
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/template.marko
@@ -0,0 +1,11 @@
+import Provider from "./provider.marko"
+
+
+ // Static-only consumer: never serializes or ships client behavior.
+
+ ${theme}
+
+ // Handler-read consumer: the value must serialize for hydration.
+
+ log theme
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/test.ts
new file mode 100644
index 00000000000..a82afcb490d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/test.ts
@@ -0,0 +1,8 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ steps: [{ $global: { theme: "light" } }],
+ // Zero-bundle, made executable: no provide-value sentinel and no context
+ // runtime helper may reach the client output.
+ bundle_excludes: { dom: ["context_value_sentinel", "_context"] },
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..3f4ee8ba510
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,61 @@
+// counter.js
+let n = 0;
+function next() {
+ return "p" + ++n;
+}
+
+// provider.marko
+const $template$2 = "";
+const $walks$2 = "b%c";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+function $setup$2($scope) {
+ $scope["#ContextValue"] = next();
+ _context_branch($scope, "__tests__/provider.marko");
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+var provider_default = /*@__PURE__*/ _template("__tests__/provider.marko", $template$2, "b%c", $setup$2, $input);
+
+// consumer.marko
+const $template$1 = " ";
+const $walks$1 = "bD l";
+const $label = ($scope, label) => _text($scope["#text/0"], label);
+function $setup$1($scope) {
+ $label($scope, _context_read($scope, "label", "__tests__/provider.marko", "./provider.marko"));
+}
+var consumer_default = /*@__PURE__*/ _template("__tests__/consumer.marko", $template$1, $walks$1, $setup$1);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&%c`)("b%c");
+const $Provider_content3__setup = ($scope) => {
+ $setup$1($scope["#childScope/0"]);
+};
+const $Provider_content3 = /*@__PURE__*/ _content("__tests__/template.marko_4_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $Provider_content3__setup);
+const $for_content__setup = ($scope) => {
+ $setup$2($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $Provider_content3($scope));
+};
+const $Provider_content2__setup = ($scope) => {
+ $setup$1($scope["#childScope/0"]);
+};
+const $Provider_content2 = /*@__PURE__*/ _content("__tests__/template.marko_2_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $Provider_content2__setup);
+const $Provider_content__setup = ($scope) => {
+ $setup$1($scope["#childScope/0"]);
+ $setup$2($scope["#childScope/1"]);
+ $input_content_direct($scope["#childScope/1"], $Provider_content2($scope));
+ $setup$1($scope["#childScope/2"]);
+};
+const $Provider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0, _w1, _w2) => `${_w0}${_w1}${_w2}`)($template$1, $template$2, $template$1), /*@__PURE__*/ ((_w0, _w1, _w2) => `b/${_w0}&/${_w1}&/${_w2}&b`)($walks$1, "b%c", $walks$1), $Provider_content__setup);
+const $for = /*@__PURE__*/ _for_of("#text/1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)("b%c"), $for_content__setup);
+function $setup($scope) {
+ $setup$2($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $Provider_content($scope));
+ $for($scope, [[
+ "x",
+ "y",
+ "z"
+ ], (x) => x]);
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..88b5124b46b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,53 @@
+// counter.js
+let n = 0;
+function next() {
+ return "p" + ++n;
+}
+
+// provider.marko
+var provider_default = _template("__tests__/provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("__tests__/provider.marko", next(), () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/provider.marko", 0);
+});
+
+// consumer.marko
+var consumer_default = _template("__tests__/consumer.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ const label = _context_get("__tests__/provider.marko", "./provider.marko");
+ _html(`${_escape(label)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/consumer.marko", 0);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ consumer_default({});
+ provider_default({ content: _content("__tests__/template.marko_2_content", () => {
+ _scope_reason();
+ const $scope2_id = _scope_id();
+ consumer_default({});
+ }) });
+ consumer_default({});
+ }) });
+ forOf([
+ "x",
+ "y",
+ "z"
+ ], (item) => {
+ const $scope3_id = _scope_id();
+ provider_default({ content: _content("__tests__/template.marko_4_content", () => {
+ _scope_reason();
+ const $scope4_id = _scope_id();
+ consumer_default({});
+ }) });
+ });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..fea9015bafe
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.js
@@ -0,0 +1,52 @@
+// counter.js
+let n = 0;
+function next() {
+ return "p" + ++n;
+}
+
+// provider.marko
+var provider_default = _template("b", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ _context_provide("b", next(), () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ });
+ _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {});
+});
+
+// consumer.marko
+var consumer_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_get("b"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+});
+
+// template.marko
+var template_default = _template("c", (input) => {
+ _scope_reason();
+ _scope_id();
+ provider_default({ content: _content("c1", () => {
+ _scope_reason();
+ _scope_id();
+ consumer_default({});
+ provider_default({ content: _content("c0", () => {
+ _scope_reason();
+ _scope_id();
+ consumer_default({});
+ }) });
+ consumer_default({});
+ }) });
+ forOf([
+ "x",
+ "y",
+ "z"
+ ], (item) => {
+ _scope_id();
+ provider_default({ content: _content("c2", () => {
+ _scope_reason();
+ _scope_id();
+ consumer_default({});
+ }) });
+ });
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..26bb8d5075e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.debug.md
@@ -0,0 +1,21 @@
+# Render
+```html
+
+ p1
+
+
+ p2
+
+
+ p1
+
+
+ p3
+
+
+ p4
+
+
+ p5
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.md
new file mode 100644
index 00000000000..26bb8d5075e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.md
@@ -0,0 +1,21 @@
+# Render
+```html
+
+ p1
+
+
+ p2
+
+
+ p1
+
+
+ p3
+
+
+ p4
+
+
+ p5
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..5d789e5f8f7
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.debug.html
@@ -0,0 +1,9 @@
+p1
+p2
+p1
+p3
+p4
+p5
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.html
new file mode 100644
index 00000000000..46133219ff2
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.html
@@ -0,0 +1,9 @@
+p1
+p2
+p1
+p3
+p4
+p5
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/consumer.marko b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/consumer.marko
new file mode 100644
index 00000000000..cee302a0d16
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/consumer.marko
@@ -0,0 +1,2 @@
+
+${label}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/counter.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/counter.js
new file mode 100644
index 00000000000..e94bcd104eb
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/counter.js
@@ -0,0 +1,6 @@
+// Distinct value per provider instance while classifying immutable (no
+// `let`/`input` reference).
+let n = 0;
+export default function next() {
+ return "p" + ++n;
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/provider.marko
new file mode 100644
index 00000000000..904f4c0a0d3
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/provider.marko
@@ -0,0 +1,5 @@
+import next from "./counter.js"
+
+
+ <${input.content}/>
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/sizes.json
new file mode 100644
index 00000000000..9d2102c848b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/sizes.json
@@ -0,0 +1,7 @@
+{
+ "dom": {},
+ "html": {
+ "min": 419,
+ "brotli": 244
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/template.marko
new file mode 100644
index 00000000000..1e4b07bd6b4
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/template.marko
@@ -0,0 +1,19 @@
+import Provider from "./provider.marko"
+import Consumer from "./consumer.marko"
+
+// The inner provider shadows the outer; after its block closes the outer
+// instance is visible again.
+
+
+
+
+
+
+
+
+// A provider inside a keyed `` is one instance per iteration.
+ x>
+
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/test.ts
new file mode 100644
index 00000000000..838290b7932
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/test.ts
@@ -0,0 +1,10 @@
+import type { TestConfig } from "../../main.test";
+
+export const config: TestConfig = {
+ steps: [{}],
+ // The inner sibling instance shares its branch with the trailing
+ // consumer, which client-side (branch based) resolution cannot
+ // distinguish; the keyed `` instances below are the supported
+ // client-side shape.
+ skip_csr: true,
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.debug.js
new file mode 100644
index 00000000000..af613d0275d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.debug.js
@@ -0,0 +1,59 @@
+// tags/add-button.marko
+const $template$3 = "add ";
+const $walks$3 = "b b";
+const $context_count$1 = _context_closure("__tests__/tags/count-provider.marko", ($scope2, $provider) => $count$2($scope2, $provider["#ContextValue"]), "__tests__/tags/add-button.marko_0_count/context", "");
+const $count$2 = /*@__PURE__*/ _const("count");
+const $setup__script = _script("__tests__/tags/add-button.marko_0", ($scope) => _on($scope["#button/0"], "click", function() {
+ _context_write($scope, "__tests__/tags/count-provider.marko", $scope.count + 1);
+}));
+function $setup$3($scope) {
+ $context_count$1($scope);
+ $setup__script($scope);
+}
+var add_button_default = /*@__PURE__*/ _template("__tests__/tags/add-button.marko", $template$3, "b b", $setup$3);
+
+// tags/count-display.marko
+const $template$2 = " ";
+const $walks$2 = "bD l";
+const $context_count = _context_closure("__tests__/tags/count-provider.marko", ($scope2, $provider) => $count$1($scope2, $provider["#ContextValue"]), "__tests__/tags/count-display.marko_0_count/context", "");
+const $count$1 = /*@__PURE__*/ _const("count", ($scope) => _text($scope["#text/0"], $scope.count));
+const $setup$2 = $context_count;
+var count_display_default = /*@__PURE__*/ _template("__tests__/tags/count-display.marko", $template$2, $walks$2, $setup$2);
+
+// tags/count-provider.marko
+const $template$1 = "
";
+const $walks$1 = "b%bD l";
+const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0");
+const $count = /*@__PURE__*/ _let("count/5", ($scope) => {
+ _context_value($scope, $scope.count, "__tests__/tags/count-provider.marko");
+ _text($scope["#text/1"], $scope.count);
+});
+function $setup$1($scope) {
+ _context_branch($scope, "__tests__/tags/count-provider.marko");
+ _context_change($scope, $valueChange($scope));
+ $count($scope, 0);
+}
+const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0");
+const $input_content = $dynamicTag;
+const $input = ($scope, input) => $input_content($scope, input.content);
+function $valueChange($scope) {
+ return (_new_count) => {
+ $count($scope, _new_count);
+ };
+}
+_resume("__tests__/tags/count-provider.marko_0/valueChange", $valueChange);
+var count_provider_default = /*@__PURE__*/ _template("__tests__/tags/count-provider.marko", $template$1, $walks$1, $setup$1, $input);
+
+// template.marko
+const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1);
+const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1);
+const $countprovider_content__setup = ($scope) => {
+ $setup$3($scope["#childScope/0"]);
+ $setup$2($scope["#childScope/1"]);
+};
+const $countprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0, _w1) => `${_w0}${_w1}`)($template$3, $template$2), /*@__PURE__*/ ((_w0, _w1) => `b/${_w0}&/${_w1}&`)("b b", $walks$2), $countprovider_content__setup);
+function $setup($scope) {
+ $setup$1($scope["#childScope/0"]);
+ $input_content_direct($scope["#childScope/0"], $countprovider_content($scope));
+}
+var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.js
new file mode 100644
index 00000000000..6a1ba555b7a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.js
@@ -0,0 +1,22 @@
+// tags/add-button.marko
+const $context_count$1 = _context_closure("d", ($scope2, $provider) => $count$2($scope2, $provider["I"]), "b0");
+const $count$2 = /*@__PURE__*/ _const(1);
+const $setup__script = _script("b1", ($scope) => _on($scope.a, "click", function() {
+ _context_write($scope, "d", $scope.b + 1);
+}));
+
+// tags/count-display.marko
+const $context_count = _context_closure("d", ($scope2, $provider) => $count$1($scope2, $provider["I"]), "c0");
+const $count$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b));
+
+// tags/count-provider.marko
+const $count = /*@__PURE__*/ _let(5, ($scope) => {
+ _context_value($scope, $scope.f, "d");
+ _text($scope.b, $scope.f);
+});
+function $valueChange($scope) {
+ return (_new_count) => {
+ $count($scope, _new_count);
+ };
+}
+_resume("d0", $valueChange);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.debug.js
new file mode 100644
index 00000000000..67d21ee7871
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.debug.js
@@ -0,0 +1,48 @@
+// tags/add-button.marko
+var add_button_default = _template("__tests__/tags/add-button.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = _context_link($scope0_id, "__tests__/tags/count-provider.marko", "__tests__/tags/add-button.marko_0_count/context", "");
+ _html(`add ${_el_resume($scope0_id, "#button/0")}`);
+ _script($scope0_id, "__tests__/tags/add-button.marko_0");
+ writeScope($scope0_id, { count }, "__tests__/tags/add-button.marko", 0, { count: "1:10" });
+ _resume_branch($scope0_id);
+});
+
+// tags/count-display.marko
+var count_display_default = _template("__tests__/tags/count-display.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = _context_link($scope0_id, "__tests__/tags/count-provider.marko", "__tests__/tags/count-display.marko_0_count/context", "");
+ _html(`${_escape(count)}${_el_resume($scope0_id, "#text/0")} `);
+ writeScope($scope0_id, {}, "__tests__/tags/count-display.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// tags/count-provider.marko
+var count_provider_default = _template("__tests__/tags/count-provider.marko", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = 0;
+ _context_provide("__tests__/tags/count-provider.marko", count, () => {
+ _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`${_escape(count)}${_el_resume($scope0_id, "#text/1")}
`);
+ writeScope($scope0_id, { "TagVariableChange:#ContextValue": _resume((_new_count) => {
+ count = _new_count;
+ }, "__tests__/tags/count-provider.marko_0/valueChange", $scope0_id) || void 0 }, "__tests__/tags/count-provider.marko", 0);
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("__tests__/template.marko", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ count_provider_default({ content: _content("__tests__/template.marko_1_content", () => {
+ _scope_reason();
+ const $scope1_id = _scope_id();
+ add_button_default({});
+ count_display_default({});
+ }) });
+ writeScope($scope0_id, {}, "__tests__/template.marko", 0);
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.js
new file mode 100644
index 00000000000..4f4d2840345
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.js
@@ -0,0 +1,47 @@
+// tags/add-button.marko
+var add_button_default = _template("b", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = _context_link($scope0_id, "d", "b0");
+ _html(`add ${_el_resume($scope0_id, "a")}`);
+ _script($scope0_id, "b1");
+ writeScope($scope0_id, { b: count });
+ _resume_branch($scope0_id);
+});
+
+// tags/count-display.marko
+var count_display_default = _template("c", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ _html(`${_escape(_context_link($scope0_id, "d", "c0"))}${_el_resume($scope0_id, "a")} `);
+ writeScope($scope0_id, {});
+ _resume_branch($scope0_id);
+});
+
+// tags/count-provider.marko
+var count_provider_default = _template("d", (input) => {
+ const $scope0_reason = _scope_reason();
+ const $scope0_id = _scope_id();
+ let count = 0;
+ _context_provide("d", count, () => {
+ _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0));
+ }, $scope0_id);
+ _html(`${_escape(count)}${_el_resume($scope0_id, "b")}
`);
+ writeScope($scope0_id, { MI: _resume((_new_count) => {
+ count = _new_count;
+ }, "d0", $scope0_id) || void 0 });
+ _resume_branch($scope0_id);
+});
+
+// template.marko
+var template_default = _template("a", (input) => {
+ _scope_reason();
+ const $scope0_id = _scope_id();
+ count_provider_default({ content: _content("a0", () => {
+ _scope_reason();
+ _scope_id();
+ add_button_default({});
+ count_display_default({});
+ }) });
+ writeScope($scope0_id, {});
+}, 1);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.debug.md
new file mode 100644
index 00000000000..5c81b665cbe
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.debug.md
@@ -0,0 +1,72 @@
+# Render
+```html
+
+ add
+
+
+ 0
+
+
+ 0
+
+```
+
+# Update
+```js
+container.querySelector("button.add").click();
+```
+```html
+
+ add
+
+
+ 1
+
+
+ 1
+
+```
+## Change
+```
+UPDATE: .provider-count::text "0" => "1"
+UPDATE: .display::text "0" => "1"
+```
+
+# Update
+```js
+container.querySelector("button.add").click();
+```
+```html
+
+ add
+
+
+ 2
+
+
+ 2
+
+```
+## Change
+```
+UPDATE: .provider-count::text "1" => "2"
+UPDATE: .display::text "1" => "2"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.md
new file mode 100644
index 00000000000..5c81b665cbe
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.md
@@ -0,0 +1,72 @@
+# Render
+```html
+
+ add
+
+
+ 0
+
+
+ 0
+
+```
+
+# Update
+```js
+container.querySelector("button.add").click();
+```
+```html
+
+ add
+
+
+ 1
+
+
+ 1
+
+```
+## Change
+```
+UPDATE: .provider-count::text "0" => "1"
+UPDATE: .display::text "0" => "1"
+```
+
+# Update
+```js
+container.querySelector("button.add").click();
+```
+```html
+
+ add
+
+
+ 2
+
+
+ 2
+
+```
+## Change
+```
+UPDATE: .provider-count::text "1" => "2"
+UPDATE: .display::text "1" => "2"
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.debug.html
new file mode 100644
index 00000000000..44d0e35d211
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.debug.html
@@ -0,0 +1,34 @@
+add 0
+0
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.html
new file mode 100644
index 00000000000..229e8ce94f7
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.html
@@ -0,0 +1,21 @@
+add 0
+0
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-writable/sizes.json
new file mode 100644
index 00000000000..7baa5c2c3ca
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/sizes.json
@@ -0,0 +1,19 @@
+{
+ "dom": {
+ "template.marko.page.mjs": {
+ "total": {
+ "min": 3411,
+ "brotli": 1680
+ },
+ "files": {
+ "tags/add-button.marko": 334,
+ "tags/count-display.marko": 240,
+ "tags/count-provider.marko": 295
+ }
+ }
+ },
+ "html": {
+ "min": 532,
+ "brotli": 340
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/add-button.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/add-button.marko
new file mode 100644
index 00000000000..c7665f1b277
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/add-button.marko
@@ -0,0 +1,2 @@
+
+add
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-display.marko
new file mode 100644
index 00000000000..82aa3e9cdb7
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-display.marko
@@ -0,0 +1,2 @@
+
+${count}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-provider.marko
new file mode 100644
index 00000000000..9d63c42ca1a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-provider.marko
@@ -0,0 +1,6 @@
+// Writable provider: consumer assignments land here and fan back out.
+
+
+ <${input.content}/>
+
+${count}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/template.marko
new file mode 100644
index 00000000000..725787f3bab
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/template.marko
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-writable/test.ts
new file mode 100644
index 00000000000..0e3954256bc
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/test.ts
@@ -0,0 +1,9 @@
+import type { TestConfig } from "../../main.test";
+
+function add(container: Element) {
+ container.querySelector("button.add")!.click();
+}
+
+export const config: TestConfig = {
+ steps: [{}, add, add],
+};
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json
index 0aecd76db2e..3bb828eee42 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"child.mjs": {
- "min": 129,
- "brotli": 104
+ "min": 127,
+ "brotli": 105
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json
index 8ebfc801927..108d5318385 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"child.mjs": {
- "min": 817,
- "brotli": 366
+ "min": 808,
+ "brotli": 363
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json
index 070c25f3a2c..af891033cc5 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json
@@ -1,11 +1,11 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 362,
- "brotli": 247
+ "min": 212,
+ "brotli": 161
},
"child.mjs": {
- "min": 122,
+ "min": 120,
"brotli": 100
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json
index f63a4fac554..edb19a2e844 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json
@@ -1,16 +1,16 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1114,
- "brotli": 616
+ "min": 435,
+ "brotli": 246
},
"child.mjs": {
- "min": 162,
- "brotli": 127
+ "min": 198,
+ "brotli": 138
},
"v:child.marko.setup.mjs": {
- "min": 77,
- "brotli": 80
+ "min": 95,
+ "brotli": 81
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json
index b7457abd2cf..f1bc12adc7d 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json
@@ -1,11 +1,11 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1118,
- "brotli": 615
+ "min": 439,
+ "brotli": 246
},
"child.mjs": {
- "min": 296,
+ "min": 287,
"brotli": 193
},
"v:child.marko.setup.mjs": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json
index 1d17874b0c7..86fb9ccbec7 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1111,
- "brotli": 616
+ "min": 413,
+ "brotli": 253
},
"child.mjs": {
- "min": 221,
- "brotli": 152
+ "min": 189,
+ "brotli": 137
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json
index b182f9f44d3..771ad6ba25c 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1680,
- "brotli": 802
+ "min": 977,
+ "brotli": 453
},
"child.mjs": {
- "min": 223,
- "brotli": 158
+ "min": 186,
+ "brotli": 166
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json
index 71666c0e9fa..9aebff66e6f 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1032,
- "brotli": 570
+ "min": 327,
+ "brotli": 206
},
"child.mjs": {
- "min": 221,
- "brotli": 152
+ "min": 189,
+ "brotli": 156
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json
index e4e090ab364..7161b4d6a2f 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 545,
- "brotli": 323
+ "min": 247,
+ "brotli": 165
},
- "v:child.marko.input_value.mjs": {
- "min": 71,
- "brotli": 67
+ "child.mjs": {
+ "min": 80,
+ "brotli": 82
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json
index a99e7958cc3..440144710ad 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 65,
+ "min": 63,
"brotli": 63
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json
index 7c64c330ca2..cf73aaaae0e 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 65,
+ "min": 63,
"brotli": 63
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json
index a6314ac649c..6f7accd963c 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1279,
- "brotli": 691
+ "min": 442,
+ "brotli": 260
},
"child.mjs": {
- "min": 177,
- "brotli": 124
+ "min": 168,
+ "brotli": 120
},
"v:child.marko.setup.mjs": {
"min": 87,
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json
index fcc262a297c..1cff759d0c3 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 65,
+ "min": 63,
"brotli": 63
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json
index a2cef4d9df3..114204d081e 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 479,
- "brotli": 298
+ "min": 242,
+ "brotli": 168
},
- "v:child.marko.input_value.mjs": {
- "min": 71,
- "brotli": 67
+ "child.mjs": {
+ "min": 80,
+ "brotli": 82
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json
index b6aeb52a030..843d4930813 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1216,
- "brotli": 662
+ "min": 440,
+ "brotli": 258
},
"child.mjs": {
- "min": 177,
- "brotli": 124
+ "min": 168,
+ "brotli": 120
},
"v:child.marko.setup.mjs": {
"min": 87,
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json
index 3c81cf93757..a2eed697c44 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 65,
+ "min": 63,
"brotli": 63
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json
index da1dcaadeeb..fefd758cbae 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json
@@ -1,11 +1,11 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 247,
+ "min": 245,
"brotli": 164
},
"child.mjs": {
- "min": 147,
+ "min": 145,
"brotli": 114
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json
index adcabc18ef4..fc9acc488d3 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1170,
- "brotli": 657
+ "min": 513,
+ "brotli": 298
},
"child.mjs": {
- "min": 145,
- "brotli": 107
+ "min": 136,
+ "brotli": 103
},
"v:child.marko.setup.mjs": {
"min": 87,
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json
index 88ba521d056..a1113d782c7 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 65,
+ "min": 63,
"brotli": 63
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json
index 4a578943eb1..0be54e35541 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 1102,
- "brotli": 599
+ "min": 404,
+ "brotli": 249
},
"child.mjs": {
- "min": 199,
- "brotli": 140
+ "min": 167,
+ "brotli": 141
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json
index b707f6203bc..d333d6e6a6b 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 837,
- "brotli": 426
+ "min": 274,
+ "brotli": 188
},
- "v:child.marko.input_value.mjs": {
- "min": 71,
- "brotli": 67
+ "child.mjs": {
+ "min": 80,
+ "brotli": 82
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json
index ee6a96c9b98..bdcab3a498a 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 825,
- "brotli": 435
+ "min": 269,
+ "brotli": 179
},
- "v:child.marko.input_value.mjs": {
- "min": 71,
- "brotli": 67
+ "child.mjs": {
+ "min": 80,
+ "brotli": 82
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json
index 78e1f5fffc1..ae8e57c5417 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 65,
+ "min": 63,
"brotli": 63
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json
index 2e59aa842ef..0044da62404 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json
@@ -1,15 +1,15 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 430,
- "brotli": 262
+ "min": 280,
+ "brotli": 177
},
"child-a.mjs": {
- "min": 124,
- "brotli": 102
+ "min": 122,
+ "brotli": 103
},
"child-b.mjs": {
- "min": 126,
+ "min": 124,
"brotli": 103
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json
index 86ea5b28ab3..401db2518d8 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"child.mjs": {
- "min": 416,
- "brotli": 273
+ "min": 259,
+ "brotli": 177
},
"grand-child.mjs": {
- "min": 211,
- "brotli": 149
+ "min": 202,
+ "brotli": 142
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json
index 8474d5221ba..fa8276ef521 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"child.mjs": {
- "min": 402,
- "brotli": 278
+ "min": 255,
+ "brotli": 182
},
"grand-child.mjs": {
- "min": 211,
- "brotli": 160
+ "min": 204,
+ "brotli": 152
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json
index 8474d5221ba..fa8276ef521 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"child.mjs": {
- "min": 402,
- "brotli": 278
+ "min": 255,
+ "brotli": 182
},
"grand-child.mjs": {
- "min": 211,
- "brotli": 160
+ "min": 204,
+ "brotli": 152
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json
index fad3b619b04..aaf3a7d0051 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"child.mjs": {
- "min": 402,
- "brotli": 278
+ "min": 255,
+ "brotli": 182
},
"grand-child.mjs": {
- "min": 211,
- "brotli": 160
+ "min": 204,
+ "brotli": 152
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json
index 584555c2f13..f69512b4eeb 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"grand-child.mjs": {
- "min": 65,
- "brotli": 63
+ "min": 63,
+ "brotli": 62
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json
index 7058bc15296..0cf56d2d80b 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 384,
- "brotli": 261
+ "min": 232,
+ "brotli": 172
},
"child.mjs": {
- "min": 182,
- "brotli": 147
+ "min": 180,
+ "brotli": 137
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json
index b2596c005da..47911520b6c 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 110,
- "brotli": 89
+ "min": 101,
+ "brotli": 84
},
"child.mjs": {
- "min": 107,
- "brotli": 87
+ "min": 98,
+ "brotli": 102
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json
index f22427a1cdd..635f655f286 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 428,
- "brotli": 288
+ "min": 279,
+ "brotli": 192
},
- "v:child.marko.input_value.mjs": {
- "min": 78,
- "brotli": 74
+ "child.mjs": {
+ "min": 80,
+ "brotli": 82
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json
index 1bd2e7b27dc..78e580f5f93 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 750,
- "brotli": 331
+ "min": 741,
+ "brotli": 329
},
"child.mjs": {
- "min": 143,
- "brotli": 112
+ "min": 134,
+ "brotli": 111
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json
index eeb5e72faed..946144251e3 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"child.mjs": {
- "min": 129,
- "brotli": 102
+ "min": 127,
+ "brotli": 104
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json
index a9b3f5e603f..798c89f8704 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 107,
+ "min": 105,
"brotli": 93
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json
index de69894f1db..cb49746bdcd 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"child.mjs": {
- "min": 111,
- "brotli": 85
+ "min": 109,
+ "brotli": 88
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json
index 21eb2c7decc..13a35d23097 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"child.mjs": {
- "min": 143,
- "brotli": 109
+ "min": 141,
+ "brotli": 110
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json
index 5b9df0cbf09..3a94f04036c 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json
@@ -2,8 +2,8 @@
"dom": {
"template.marko.page.mjs": {
"total": {
- "min": 474,
- "brotli": 279
+ "min": 324,
+ "brotli": 185
},
"files": {
"tags/parent-a.marko": 258,
@@ -12,7 +12,7 @@
}
},
"child.mjs": {
- "min": 122,
+ "min": 120,
"brotli": 100
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json
index 98a32947b72..1ebeb348fb9 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json
@@ -1,15 +1,15 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 324,
- "brotli": 194
+ "min": 322,
+ "brotli": 185
},
"child-b.mjs": {
- "min": 140,
+ "min": 138,
"brotli": 108
},
"child-s.mjs": {
- "min": 140,
+ "min": 138,
"brotli": 108
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json
index ddfc3adf4f4..ed94aa8b34a 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"child.mjs": {
- "min": 129,
- "brotli": 104
+ "min": 127,
+ "brotli": 105
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json
index baadd04c91e..3f2504f7f09 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 2003,
- "brotli": 953
+ "min": 1237,
+ "brotli": 567
},
"child.mjs": {
- "min": 177,
- "brotli": 124
+ "min": 168,
+ "brotli": 120
},
"v:child.marko.setup.mjs": {
"min": 87,
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json
index e1e9c7cd214..67855c40551 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 600,
- "brotli": 359
+ "min": 235,
+ "brotli": 161
},
- "v:child.marko.input_value.mjs": {
- "min": 71,
- "brotli": 67
+ "child.mjs": {
+ "min": 80,
+ "brotli": 82
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json
index f397d124f5b..26f5e7b3dae 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json
@@ -1,12 +1,12 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 159,
- "brotli": 121
+ "min": 157,
+ "brotli": 141
},
"child.mjs": {
- "min": 129,
- "brotli": 102
+ "min": 127,
+ "brotli": 104
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json
index c174b7cac32..d1edd8740c2 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json
@@ -1,7 +1,7 @@
{
"dom": {
"child.mjs": {
- "min": 65,
+ "min": 63,
"brotli": 63
}
},
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json
index 39b0beb9a90..07fb3b24a04 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"child.mjs": {
- "min": 129,
- "brotli": 103
+ "min": 127,
+ "brotli": 105
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json
index ba5d5d6bb83..e69a6e1e0d9 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json
@@ -1,8 +1,8 @@
{
"dom": {
"template.marko.page.mjs": {
- "min": 9848,
- "brotli": 3880
+ "min": 10051,
+ "brotli": 3955
}
},
"html": {
diff --git a/packages/runtime-tags/src/__tests__/main.test.ts b/packages/runtime-tags/src/__tests__/main.test.ts
index 7c5c2c76474..d785b58f413 100644
--- a/packages/runtime-tags/src/__tests__/main.test.ts
+++ b/packages/runtime-tags/src/__tests__/main.test.ts
@@ -52,6 +52,9 @@ export type TestConfig = {
error_compiler?: true | string[];
/** Compiles the fixture with a custom `runtimeId` compiler option. */
runtime_id?: string;
+ /** Strings that must not appear in the given output's optimize bundle
+ * snapshot (debug bundles skip tree-shaking). */
+ bundle_excludes?: { dom?: string[]; html?: string[] };
};
// `scripts/test-parallel` fans the fixtures across CPU cores by giving each
@@ -218,7 +221,19 @@ function testFixtures(interop?: true) {
const runner = await ssrRunner();
const { snapshot, sizes } = await runner[`${output}Bundle`]();
if (optimize && sizes) stats.dom = sizes;
- return stripFixtureDir(snapshot);
+ const result = await stripFixtureDir(snapshot);
+ if (optimize) {
+ // Debug bundles skip tree-shaking, so exclusion is only a
+ // meaningful guarantee for the optimized artifact.
+ for (const exclude of config.bundle_excludes?.[output] || []) {
+ if (result.includes(exclude)) {
+ throw new Error(
+ `Expected the ${output} bundle to not contain ${JSON.stringify(exclude)} (bundle_excludes).`,
+ );
+ }
+ }
+ }
+ return result;
}, `${output}.bundle.js`);
};
diff --git a/packages/runtime-tags/src/common/accessor.debug.ts b/packages/runtime-tags/src/common/accessor.debug.ts
index 29250bc8218..7350d1f6df3 100644
--- a/packages/runtime-tags/src/common/accessor.debug.ts
+++ b/packages/runtime-tags/src/common/accessor.debug.ts
@@ -3,6 +3,8 @@ export enum AccessorPrefix {
ClosureScopes = "ClosureScopes:",
ClosureSignalIndex = "ClosureSignalIndex:",
ConditionalRenderer = "ConditionalRenderer:",
+ ContextLink = "ContextLink:",
+ ContextSignal = "ContextSignal:",
ControlledHandler = "ControlledHandler:",
ControlledType = "ControlledType:",
ControlledValue = "ControlledValue:",
@@ -26,6 +28,8 @@ export enum AccessorProp {
CatchContent = "#CatchContent",
ClosestBranch = "#ClosestBranch",
ClosestBranchId = "#ClosestBranchId",
+ ContextScopes = "#ContextScopes",
+ ContextValue = "#ContextValue",
Gen = "#Gen",
DetachedAwait = "#DetachedAwait",
EndNode = "#EndNode",
diff --git a/packages/runtime-tags/src/common/accessor.ts b/packages/runtime-tags/src/common/accessor.ts
index d26ffde0852..d2ab8fca060 100644
--- a/packages/runtime-tags/src/common/accessor.ts
+++ b/packages/runtime-tags/src/common/accessor.ts
@@ -3,6 +3,8 @@ export enum AccessorPrefix {
ClosureScopes = "B",
ClosureSignalIndex = "C",
ConditionalRenderer = "D",
+ ContextLink = "V",
+ ContextSignal = "W",
ControlledHandler = "E",
ControlledType = "F",
ControlledValue = "G",
@@ -26,6 +28,8 @@ export enum AccessorProp {
CatchContent = "E",
ClosestBranch = "F",
ClosestBranchId = "G",
+ ContextScopes = "Z",
+ ContextValue = "I",
Gen = "H",
DetachedAwait = "V",
EndNode = "K",
diff --git a/packages/runtime-tags/src/dom.ts b/packages/runtime-tags/src/dom.ts
index 9872fe29744..4cf3bdf760c 100644
--- a/packages/runtime-tags/src/dom.ts
+++ b/packages/runtime-tags/src/dom.ts
@@ -4,6 +4,14 @@ export { forIn, forOf, forTo, forUntil } from "./common/for";
export { _call } from "./common/helpers";
export { $signal, $signalReset } from "./dom/abort-signal";
export { compat } from "./dom/compat";
+export {
+ _context_branch,
+ _context_change,
+ _context_closure,
+ _context_read,
+ _context_value,
+ _context_write,
+} from "./dom/context";
export {
_await_content,
_await_promise,
diff --git a/packages/runtime-tags/src/dom/context.ts b/packages/runtime-tags/src/dom/context.ts
new file mode 100644
index 00000000000..011cef6af31
--- /dev/null
+++ b/packages/runtime-tags/src/dom/context.ts
@@ -0,0 +1,137 @@
+import { AccessorPrefix, AccessorProp, type Scope } from "../common/types";
+import { queueRender, runId } from "./queue";
+import { _resume } from "./resume";
+import { findBranchWithKey } from "./scope";
+import { type SignalFn, subscribeToScopeSet } from "./signals";
+
+// `` client runtime; server-rendered immutable consumers read a
+// serialized slot and pull nothing from this module.
+
+// Immutable consume: the serialized slot when present (server-rendered),
+// otherwise resolve the nearest provider (a reserved box or a
+// client-created instance); the value signal populates the slot.
+export function _context_read(
+ scope: Scope,
+ accessor: string,
+ providerId: string,
+ debugName?: string,
+) {
+ if (accessor in scope) return scope[accessor];
+
+ const linkAccessor = AccessorPrefix.ContextLink + providerId;
+ const provider = (findBranchWithKey(scope, linkAccessor)?.[linkAccessor] ||
+ scope[AccessorProp.Global][linkAccessor]) as Scope | undefined;
+ if (MARKO_DEBUG && !provider) {
+ throw new Error(
+ `No provider found for ${JSON.stringify(debugName || providerId)}. Render a \`\` (or \`\`) tag above this consumer in the render tree.`,
+ );
+ }
+ return provider && provider[AccessorProp.ContextValue];
+}
+
+// Consumer fan-out signal: resolves the provider (serialized link, branch
+// entry, or root $global), applies the value, and subscribes. `fn` always
+// receives a provider scope (an empty one when missing in production, so
+// consumers compile plain member reads); debug throws instead.
+export function _context_closure(
+ providerId: string,
+ fn: (scope: Scope, provider: Scope) => void,
+ resumeId: string,
+ debugName?: string,
+) {
+ const linkAccessor = AccessorPrefix.ContextLink + providerId;
+ const signal: SignalFn = (scope) => {
+ let provider = (scope[linkAccessor] ||=
+ findBranchWithKey(scope, linkAccessor)?.[linkAccessor] ||
+ scope[AccessorProp.Global][linkAccessor]) as Scope | undefined;
+ if (provider) {
+ scope[AccessorPrefix.ContextSignal + providerId] = signal;
+ subscribeToScopeSet(provider, AccessorProp.ContextScopes, scope);
+ } else if (MARKO_DEBUG) {
+ throw new Error(
+ `No provider found for ${JSON.stringify(debugName || providerId)}. Render a \`\` (or \`\`) tag above this consumer in the render tree.`,
+ );
+ } else {
+ provider = {} as Scope;
+ }
+ fn(scope, provider);
+ };
+ return _resume(resumeId, signal);
+}
+
+// Provider value write + fan-out, re-run whenever the value can change.
+export function _context_value(
+ scope: Scope,
+ value: unknown,
+ providerId: string,
+) {
+ if (
+ scope[AccessorProp.ContextValue] !== value ||
+ !(AccessorProp.ContextValue in scope)
+ ) {
+ scope[AccessorProp.ContextValue] = value;
+ const subscribers = scope[AccessorProp.ContextScopes] as
+ Set | undefined;
+ if (subscribers) {
+ const signalAccessor = AccessorPrefix.ContextSignal + providerId;
+ for (const subscriber of subscribers) {
+ // Skip destroyed scopes and scopes rendering this pass (same
+ // liveness guard as `_closure`).
+ if (
+ subscriber[AccessorProp.Gen] > 0 &&
+ subscriber[AccessorProp.Gen] < runId
+ ) {
+ queueRender(subscriber, subscriber[signalAccessor] as SignalFn, -1);
+ }
+ }
+ }
+ }
+}
+
+// Branch entry so client-created consumers below resolve this provider
+// (root providers stamp $global); mirrors the serialized entry for resume.
+export function _context_branch(scope: Scope, providerId: string) {
+ const target =
+ scope[AccessorProp.ClosestBranch] || scope[AccessorProp.Global];
+ const linkAccessor = AccessorPrefix.ContextLink + providerId;
+ if (MARKO_DEBUG) {
+ const existing = target[linkAccessor] as Scope | undefined;
+ if (existing && existing !== scope && existing[AccessorProp.Gen] > 0) {
+ throw new Error(
+ `Multiple live instances of the provider ${JSON.stringify(providerId)} share the same branch; consumers created client-side cannot tell them apart. Wrap each provider instance in its own control flow (eg a keyed \`\` iteration or an \`\`).`,
+ );
+ }
+ }
+ target[linkAccessor] = scope;
+}
+
+// The change handler rides the tag-variable-change prefix keyed by the
+// context value prop, which no real binding accessor can collide with.
+const changeAccessor =
+ AccessorPrefix.TagVariableChange + AccessorProp.ContextValue;
+
+// Installs the writable provider's change handler for consumer writes.
+export function _context_change(
+ scope: Scope,
+ changeHandler: (value: unknown) => void,
+) {
+ scope[changeAccessor] = changeHandler;
+}
+
+// Writable consumer assignment: the write lands in provider state and fans
+// back out; the local value updates then, not synchronously.
+export function _context_write(
+ scope: Scope,
+ providerId: string,
+ value: T,
+): T {
+ const provider = scope[AccessorPrefix.ContextLink + providerId] as
+ Scope | undefined;
+ if (MARKO_DEBUG && !provider?.[changeAccessor]) {
+ throw new Error(
+ `Cannot write to the value for ${JSON.stringify(providerId)}; no writable provider was found.`,
+ );
+ }
+ provider?.[changeAccessor]?.(value);
+ return value;
+}
diff --git a/packages/runtime-tags/src/html.ts b/packages/runtime-tags/src/html.ts
index 52367140091..d9d94dfc782 100644
--- a/packages/runtime-tags/src/html.ts
+++ b/packages/runtime-tags/src/html.ts
@@ -37,6 +37,12 @@ export {
_to_text,
_unescaped,
} from "./html/content";
+export {
+ _context_get,
+ _context_link,
+ _context_provide,
+ _context_reserve,
+} from "./html/context";
export { _content, _content_resume, _dynamic_tag } from "./html/dynamic-tag";
export {
forIn,
diff --git a/packages/runtime-tags/src/html/context.ts b/packages/runtime-tags/src/html/context.ts
new file mode 100644
index 00000000000..2c004616c19
--- /dev/null
+++ b/packages/runtime-tags/src/html/context.ts
@@ -0,0 +1,150 @@
+import { AccessorPrefix, AccessorProp } from "../common/types";
+import {
+ _resume,
+ _scope,
+ _scope_id,
+ _scope_with_id,
+ getBranchId,
+ getContext,
+ withContext,
+} from "./writer";
+
+// Boxed so "no provider above" is distinguishable from an explicitly
+// provided `undefined`.
+interface ContextBox {
+ v: unknown;
+ /** The provider's scope id; assigned lazily for immutable providers. */
+ s?: number;
+ /** Immutable only: provider id, containing branch, and open-chain link. */
+ k?: string;
+ b?: number;
+ p?: ContextBox;
+}
+
+// Chain of open immutable boxes, for reserve-all at dynamic content holes.
+const kOpenBoxes = Symbol();
+
+export function _context_provide(
+ key: string,
+ value: unknown,
+ cb: () => T,
+ scopeId?: number,
+): T {
+ const box: ContextBox = { v: value };
+ if (scopeId === undefined) {
+ // Immutable: serialized only if a `_context_reserve` names it.
+ box.k = key;
+ box.b = getBranchId() ?? 0;
+ box.p = getContext(kOpenBoxes as never) as ContextBox | undefined;
+ return withContext(key, box, () => withContext(kOpenBoxes, box, cb));
+ }
+ {
+ // Mutable providers serialize their value, fan-out set, and a branch
+ // entry (scope 0 for root providers) for client-created consumers.
+ box.s = scopeId;
+ const branchId = getBranchId() ?? 0;
+ const providerScope = _scope(scopeId, {
+ [AccessorProp.ContextValue]: value,
+ [AccessorProp.ContextScopes]:
+ (_scope_with_id(scopeId) as Record)[
+ AccessorProp.ContextScopes
+ ] || new Set(),
+ });
+ if (MARKO_DEBUG) {
+ assertSoleBranchInstance(branchId, key, providerScope);
+ }
+ _scope(branchId, {
+ [AccessorPrefix.ContextLink + key]: providerScope,
+ });
+ }
+ return withContext(key, box, cb);
+}
+
+// Compiler-emitted where client-re-renderable content may consume an
+// immutable context: lazily serializes the named provider boxes (or every
+// open one when called bare) so client-created consumers can resolve them.
+export function _context_reserve(...keys: string[]) {
+ if (keys.length) {
+ for (const key of keys) {
+ reserveBox(getContext(key) as ContextBox | undefined);
+ }
+ } else {
+ for (
+ let box = getContext(kOpenBoxes as never) as ContextBox | undefined;
+ box;
+ box = box.p
+ ) {
+ reserveBox(box);
+ }
+ }
+}
+
+function reserveBox(box: ContextBox | undefined) {
+ if (box && box.k && box.s === undefined) {
+ const providerScope = _scope((box.s = _scope_id()), {
+ [AccessorProp.ContextValue]: box.v,
+ });
+ if (MARKO_DEBUG) {
+ assertSoleBranchInstance(box.b!, box.k, providerScope);
+ }
+ _scope(box.b!, {
+ [AccessorPrefix.ContextLink + box.k]: providerScope,
+ });
+ }
+}
+
+function assertSoleBranchInstance(
+ branchId: number,
+ key: string,
+ providerScope: unknown,
+) {
+ const existing = (_scope_with_id(branchId) as Record)[
+ AccessorPrefix.ContextLink + key
+ ];
+ if (existing && existing !== providerScope) {
+ throw new Error(
+ `Multiple instances of the provider ${JSON.stringify(key)} share the same branch; consumers created client-side cannot tell them apart. Wrap each provider instance in its own control flow (eg a keyed \`\` iteration or an \`\`).`,
+ );
+ }
+}
+
+export function _context_get(key: string, debugName?: string) {
+ const box = getContext(key) as ContextBox | undefined;
+ if (MARKO_DEBUG && !box) {
+ throw missingProviderError(debugName ?? key);
+ }
+
+ return box && box.v;
+}
+
+// Mutable-provider consume: returns the value and serializes the link,
+// registered fan-out signal, and fan-out set membership.
+export function _context_link(
+ scopeId: number,
+ key: string,
+ registryId: string,
+ debugName?: string,
+) {
+ const box = getContext(key) as ContextBox | undefined;
+ if (!box) {
+ if (MARKO_DEBUG) {
+ throw missingProviderError(debugName ?? key);
+ }
+ return undefined;
+ }
+
+ const providerScope = _scope_with_id(box.s!) as Record;
+ (providerScope[AccessorProp.ContextScopes] as Set).add(
+ _scope(scopeId, {
+ [AccessorPrefix.ContextLink + key]: providerScope,
+ [AccessorPrefix.ContextSignal + key]: _resume({}, registryId),
+ }),
+ );
+ return box.v;
+}
+
+function missingProviderError(name: string) {
+ return new Error(
+ `No provider found for ${JSON.stringify(name)}. Render a \`\` (or \`\`) tag above this consumer in the render tree.`,
+ );
+}
diff --git a/packages/runtime-tags/src/html/dynamic-tag.ts b/packages/runtime-tags/src/html/dynamic-tag.ts
index 7b970d08b43..3a4a79d6741 100644
--- a/packages/runtime-tags/src/html/dynamic-tag.ts
+++ b/packages/runtime-tags/src/html/dynamic-tag.ts
@@ -9,6 +9,7 @@ import {
ResumeSymbol,
} from "../common/types";
import { _attr_select_value, _attr_textarea_value, _attrs } from "./attrs";
+import { _context_reserve } from "./context";
import type { ServerRenderer } from "./template";
import {
_html,
@@ -49,6 +50,12 @@ export let _dynamic_tag = (
let rendered: boolean;
let result: unknown;
+ if (shouldResume) {
+ // Statically unknowable content that can render client-side may
+ // consume any open server-only context.
+ _context_reserve();
+ }
+
if (typeof renderer === "string") {
if (MARKO_DEBUG) {
assertValidTagName(renderer);
diff --git a/packages/runtime-tags/src/html/serializer.ts b/packages/runtime-tags/src/html/serializer.ts
index a4d0682974d..4097b3010fd 100644
--- a/packages/runtime-tags/src/html/serializer.ts
+++ b/packages/runtime-tags/src/html/serializer.ts
@@ -424,10 +424,15 @@ function writeScopesRoot(state: State, flushes: ScopeFlush[]) {
// skip count (the browser creates scopes on demand).
const openIndex = buf.push("") - 1;
if (writeObjectProps(state, flush[2], ref)) {
+ // The delta may be negative: a payload can carry two partials for the
+ // same slot under different identities (the serialized globals object
+ // and scope 0's own record both flush as slot 0 when a root mutable
+ // `` provider stamps its branch link), and the browser's
+ // `applyScopes` sums deltas signed.
buf[openIndex] =
nextSlotId === -1
? "[" + scopeId + ",{"
- : (scopeId > nextSlotId ? "," + (scopeId - nextSlotId) : "") + ",{";
+ : (scopeId !== nextSlotId ? "," + (scopeId - nextSlotId) : "") + ",{";
if (fillIndex === -1) fillIndex = openIndex;
nextSlotId = scopeId + 1;
buf.push("}");
diff --git a/packages/runtime-tags/src/html/writer.ts b/packages/runtime-tags/src/html/writer.ts
index 98924919408..397a85b73d0 100644
--- a/packages/runtime-tags/src/html/writer.ts
+++ b/packages/runtime-tags/src/html/writer.ts
@@ -17,6 +17,7 @@ import {
} from "../common/types";
import { RendererProp } from "../common/types";
import { attrAssignment } from "./attrs";
+import { _context_reserve } from "./context";
import { forInBy, forOfBy, forStepBy } from "./for";
import {
REORDER_RUNTIME_CODE,
@@ -135,6 +136,9 @@ export function _attr_content(
const branchId = _peek_scope_id();
if (render) {
if (shouldResume) {
+ // Statically unknowable content that can render client-side may
+ // consume any open server-only context.
+ _context_reserve();
withBranchId(branchId, render);
} else {
render();
@@ -331,6 +335,10 @@ export function isInResumedBranch() {
return $chunk?.context?.[kBranchId] !== undefined;
}
+export function getBranchId() {
+ return $chunk.context?.[kBranchId] as number | undefined;
+}
+
export function withBranchId(branchId: number, cb: () => T): T {
return withContext(kBranchId, branchId, cb);
}
diff --git a/packages/runtime-tags/src/translator/core/context.ts b/packages/runtime-tags/src/translator/core/context.ts
new file mode 100644
index 00000000000..b24ae674a2e
--- /dev/null
+++ b/packages/runtime-tags/src/translator/core/context.ts
@@ -0,0 +1,801 @@
+import { types as t } from "@marko/compiler";
+import {
+ assertNoArgs,
+ assertNoParams,
+ getProgram,
+ loadFileForImport,
+ type Tag,
+} from "@marko/compiler/babel-utils";
+
+import type { AccessorProp } from "../../common/types";
+import { assertNoBodyContent, assertNoSpreadAttrs } from "../util/assert";
+import { generateUidIdentifier } from "../util/generate-uid";
+import { getAccessorPrefix, getAccessorProp } from "../util/get-accessor-char";
+import { isOptimize } from "../util/marko-config";
+import {
+ type Binding,
+ BindingType,
+ getScopeAccessorLiteral,
+ mergeReferences,
+ pruneBinding,
+ trackVarReferences,
+} from "../util/references";
+import { callRuntime } from "../util/runtime";
+import runtimeInfo from "../util/runtime-info";
+import { getScopeExpression } from "../util/scope-read";
+import {
+ getOrCreateSection,
+ getScopeIdIdentifier,
+ getSection,
+ type Section,
+} from "../util/sections";
+import { getExprIfSerialized } from "../util/serialize-guard";
+import {
+ addSerializeReason,
+ type SerializeReason,
+} from "../util/serialize-reasons";
+import { addSetupStatement } from "../util/setup-statements";
+import {
+ addStatement,
+ addValue,
+ getResumeRegisterId,
+ initValue,
+ setSectionSerializedValue,
+} from "../util/signals";
+import { createSectionState } from "../util/state";
+import translateVar from "../util/translate-var";
+import { translateByTarget } from "../util/visitors";
+import * as writer from "../util/writer";
+import { scopeIdentifier } from "../visitors/program";
+
+// Cross-template flags, set once at the provider's own analyze exit; a
+// mid-analysis reentrant read sees them unset and uses conservative defaults.
+declare module "@marko/compiler/dist/types" {
+ export interface ProgramExtra {
+ providesContext?: true;
+ contextMutable?: boolean;
+ contextWritable?: true;
+ [kProvideInfo]?: {
+ writable: boolean;
+ valueExtra: t.NodeExtra;
+ section: Section;
+ };
+ /** Statically known server-only provider ids consumed anywhere under
+ * this template (content rendered through dynamic holes is instead
+ * reserved at render time by the html content runtimes). */
+ consumedContexts?: Set;
+ [kSelfConsumes]?: Section[];
+ [kRecursiveRefs]?: Section[];
+ }
+
+ export interface MarkoTagExtra {
+ contextProviderId?: string;
+ contextProviderRequest?: string;
+ contextProviderMutable?: boolean;
+ contextProviderWritable?: boolean;
+ /** Shared by same-section consumers of one provider (a shared scope):
+ * they compose into a single wiring entry (one link + fan-out signal). */
+ contextConsumers?: Binding[];
+ }
+}
+
+export const kProvideInfo: unique symbol = Symbol("context provide info");
+export const kSelfConsumes: unique symbol = Symbol("context self consumes");
+export const kRecursiveRefs: unique symbol = Symbol("context recursive refs");
+
+// Server-only context ids consumed beneath each section (bubbled to
+// ancestors); a client-re-renderable branch serializes the boxes its
+// subtree may consume (`_context_reserve`), keeping every other page free
+// of context payload bytes.
+const sectionConsumedContexts = new WeakMap>();
+
+export function addConsumedContext(
+ section: Section | undefined,
+ ids: Set | string | undefined,
+) {
+ if (!ids || (typeof ids === "object" && !ids.size)) return;
+ for (let cur = section; cur; cur = cur.parent) {
+ let set = sectionConsumedContexts.get(cur);
+ if (!set) {
+ sectionConsumedContexts.set(cur, (set = new Set()));
+ }
+ if (typeof ids === "string") {
+ set.add(ids);
+ } else {
+ for (const id of ids) set.add(id);
+ }
+ }
+}
+
+export function getConsumedContexts(section: Section) {
+ return sectionConsumedContexts.get(section);
+}
+
+// Statement for a client-re-renderable branch site whose subtree consumes
+// statically known server-only contexts; `undefined` when the site cannot
+// re-render client-side or nothing known is consumed. Unknowable subtrees
+// (dynamic holes) are instead reserved at render time by the content
+// runtimes, so branches never server-rendered cover only known ids.
+export function buildContextReserve(
+ section: Section,
+ rerenderReason: SerializeReason | undefined,
+ bodySections: (Section | undefined)[],
+): t.Statement | undefined {
+ if (!rerenderReason) return;
+ let consumed: Set | undefined;
+ for (const body of bodySections) {
+ const cur = body && sectionConsumedContexts.get(body);
+ if (cur) {
+ if (consumed) {
+ for (const id of cur) consumed.add(id);
+ } else {
+ consumed = new Set(cur);
+ }
+ }
+ }
+ if (!consumed) return;
+
+ const gated = getExprIfSerialized(
+ section,
+ rerenderReason,
+ callRuntime(
+ "_context_reserve",
+ ...[...consumed].sort().map((id) => t.stringLiteral(id)),
+ ),
+ );
+ return gated && t.expressionStatement(gated);
+}
+
+// Groups same-section consumers per provider during analyze (phase-local;
+// the group crosses to translate via each tag's `contextConsumers` extra).
+const [getConsumerGroups] = createSectionState>(
+ "contextConsumers",
+ () => new Map(),
+);
+
+export default {
+ analyze(tag) {
+ assertNoArgs(tag);
+ assertNoParams(tag);
+ assertNoSpreadAttrs(tag);
+
+ const { node } = tag;
+ let valueAttr: t.MarkoAttribute | undefined;
+ let valueChangeAttr: t.MarkoAttribute | undefined;
+ let fromAttr: t.MarkoAttribute | undefined;
+
+ for (const attr of node.attributes) {
+ const markoAttr = attr as t.MarkoAttribute;
+ if (markoAttr.name === "from") {
+ fromAttr = markoAttr;
+ } else if (markoAttr.name === "valueChange") {
+ valueChangeAttr = markoAttr;
+ } else if (markoAttr.default || markoAttr.name === "value") {
+ valueAttr = markoAttr;
+ } else {
+ throw buildAttrError(
+ tag,
+ markoAttr,
+ "The [`` tag](https://markojs.com/docs/reference/core-tag#context) only supports the `value=` (default/bind) attribute, `valueChange=`, and `from=`.",
+ );
+ }
+ }
+
+ if (fromAttr) {
+ analyzeConsume(tag, fromAttr, valueAttr, valueChangeAttr);
+ } else if (valueAttr) {
+ analyzeProvide(tag, valueAttr, valueChangeAttr);
+ } else if (node.var) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "The [`` tag](https://markojs.com/docs/reference/core-tag#context) requires a `from=` attribute when consuming a context with a tag variable.",
+ );
+ } else {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "The [`` tag](https://markojs.com/docs/reference/core-tag#context) requires either a provided value (eg ``) or a `from=` attribute to consume a context.",
+ );
+ }
+ },
+ translate: translateByTarget({
+ html: {
+ enter(tag) {
+ if (isProvide(tag)) {
+ writer.flushBefore(tag);
+ }
+ },
+ exit(tag) {
+ if (isProvide(tag)) {
+ translateProvideHTML(tag);
+ } else {
+ translateConsumeHTML(tag);
+ }
+ },
+ },
+ dom: {
+ enter(tag) {
+ if (isProvide(tag)) {
+ // Emitted at enter so a client-created provider's value and
+ // branch stamp precede any same-section consumer's read.
+ translateProvideDOMStatements(tag);
+ }
+ },
+ exit(tag) {
+ if (isProvide(tag)) {
+ translateProvideDOM(tag);
+ } else {
+ translateConsumeDOM(tag);
+ }
+ },
+ },
+ }),
+ attributes: {},
+ autocomplete: [
+ {
+ displayText: "context=",
+ description: "Provides a value to its body content.",
+ snippet: "context=${1:value}",
+ descriptionMoreURL: "https://markojs.com/docs/reference/core-tag#context",
+ },
+ {
+ displayText: 'context/ from=""',
+ description: "Consumes a value provided by another template.",
+ descriptionMoreURL: "https://markojs.com/docs/reference/core-tag#context",
+ },
+ ],
+ types: runtimeInfo.name + "/tags/context.d.marko",
+} as Tag;
+
+function isProvide(tag: t.NodePath) {
+ return !tag.node.extra?.contextProviderId;
+}
+
+function analyzeProvide(
+ tag: t.NodePath,
+ valueAttr: t.MarkoAttribute,
+ valueChangeAttr: t.MarkoAttribute | undefined,
+) {
+ const { node } = tag;
+
+ if (node.var) {
+ throw tag
+ .get("var")
+ .buildCodeFrameError(
+ "A [`` provider](https://markojs.com/docs/reference/core-tag#context) does not take a tag variable; use `from=` on a separate `` tag to consume it.",
+ );
+ }
+
+ if (node.body.attributeTags) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "The [`` tag](https://markojs.com/docs/reference/core-tag#context) does not support attribute tags.",
+ );
+ }
+
+ const program = getProgram();
+ const programExtra = (program.node.extra ??= {});
+ if (programExtra[kProvideInfo]) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous.",
+ );
+ }
+
+ if (!node.body.body.length) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value.",
+ );
+ }
+
+ const section = getOrCreateSection(tag);
+ const tagExtra = mergeReferences(section, node, [valueAttr.value]);
+
+ if (valueChangeAttr) {
+ // The change handler serializes (registered) so resumed consumers can
+ // invoke it through the link.
+ (valueChangeAttr.value.extra ??= {}).isEffect = true;
+ addSerializeReason(section, true, contextChangeAccessor());
+ }
+
+ programExtra[kProvideInfo] = {
+ writable: !!valueChangeAttr,
+ valueExtra: tagExtra,
+ section,
+ };
+}
+
+function analyzeConsume(
+ tag: t.NodePath,
+ fromAttr: t.MarkoAttribute,
+ valueAttr: t.MarkoAttribute | undefined,
+ valueChangeAttr: t.MarkoAttribute | undefined,
+) {
+ const { node } = tag;
+ assertNoBodyContent(tag);
+
+ if (valueAttr) {
+ throw buildAttrError(
+ tag,
+ valueAttr,
+ "A [`` tag](https://markojs.com/docs/reference/core-tag#context) cannot both provide a value and consume one via `from=`.",
+ );
+ }
+
+ if (valueChangeAttr) {
+ throw buildAttrError(
+ tag,
+ valueChangeAttr,
+ "The `valueChange=` attribute is only supported on a provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context).",
+ );
+ }
+
+ if (!node.var) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "The [`` tag](https://markojs.com/docs/reference/core-tag#context) requires a [tag variable](https://markojs.com/docs/reference/language#tag-variables) to consume a context.",
+ );
+ }
+
+ if (!t.isIdentifier(node.var)) {
+ throw tag
+ .get("var")
+ .buildCodeFrameError(
+ "The [`` tag](https://markojs.com/docs/reference/core-tag#context) consumer variable cannot be destructured.",
+ );
+ }
+
+ if (!t.isStringLiteral(fromAttr.value)) {
+ throw buildAttrError(
+ tag,
+ fromAttr,
+ 'The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`).',
+ );
+ }
+
+ const request = fromAttr.value.value;
+ let providerFile;
+
+ try {
+ providerFile = loadFileForImport(tag.hub.file, request);
+ } catch (err) {
+ throw buildAttrError(
+ tag,
+ fromAttr,
+ `Unable to resolve the \`from=\` template ${JSON.stringify(request)}: ${
+ (err as Error).message
+ }`,
+ );
+ }
+
+ if (!providerFile) {
+ throw buildAttrError(
+ tag,
+ fromAttr,
+ `Unable to resolve the \`from=\` template ${JSON.stringify(request)}.`,
+ );
+ }
+
+ const providerExtra = providerFile.ast.program.extra ?? {};
+ // A self-referencing `from=` resolves to this same mid-analysis file, whose
+ // flags are not final yet -- answer from its AST and force the mutable tier
+ // (the program-exit hook makes the provide side match).
+ const self = providerFile === tag.hub.file;
+ const selfProvide = self
+ ? findProvideTag(providerFile.ast.program)
+ : undefined;
+
+ if (self ? !selfProvide : !providerExtra.providesContext) {
+ throw buildAttrError(
+ tag,
+ fromAttr,
+ `The template ${JSON.stringify(request)} does not provide a value (it has no provide-mode \`\` tag).`,
+ );
+ }
+
+ // Conservative defaults where flags are not final yet: a reentrant cycle
+ // is treated as read-only and mutable, and a self-consume as mutable (its
+ // tier is refined at translate once the provide side is classified).
+ const writable = self
+ ? hasValueChangeAttr(selfProvide!)
+ : !!providerExtra.contextWritable;
+ const mutable = self || (providerExtra.contextMutable ?? true);
+
+ // A mutable context's consumer variable changes client-side (fan-out), so
+ // it is tracked like a `` for serialize reasons and the update graph.
+ const varBinding = trackVarReferences(
+ tag,
+ mutable ? BindingType.let : BindingType.derived,
+ )!;
+ const tagExtra = (node.extra ??= {});
+ const section = getOrCreateSection(tag);
+
+ // Conservative: translate skips emission for pruned consumers, but that is
+ // only known after this analyze pass.
+ addSetupStatement(section);
+
+ if (mutable) {
+ const providerId = providerFile.metadata.marko.id;
+ const group = getConsumerGroups(section);
+ let members = group.get(providerId);
+ if (members) {
+ members.push(varBinding);
+ } else {
+ group.set(providerId, (members = [varBinding]));
+ }
+ tagExtra.contextConsumers = members;
+ if (self) {
+ // A self-consume may drop to the immutable tier at the program exit
+ // hook; its section joins the consumed-context sets there.
+ ((getProgram().node.extra ??= {})[kSelfConsumes] ??= []).push(section);
+ }
+ } else {
+ addConsumedContext(section, providerFile.metadata.marko.id);
+ }
+
+ if (varBinding.assignmentSections && !writable) {
+ const babelBinding = tag.scope.getBinding(node.var.name)!;
+ const violation = babelBinding.constantViolations[0];
+ throw (violation ?? tag.get("var")).buildCodeFrameError(
+ `Cannot assign to \`${node.var.name}\`; the provider for ${JSON.stringify(request)} is read-only. Add \`:=\` to the provider's default attribute to make it writable.`,
+ );
+ }
+
+ tagExtra.contextProviderId = providerFile.metadata.marko.id;
+ tagExtra.contextProviderRequest = request;
+ tagExtra.contextProviderMutable = mutable;
+ tagExtra.contextProviderWritable = writable;
+}
+
+// Provides compile inline into the parent section (`isNativeNode`, like
+// ``): the body runs once in place; `withContext` only needs a closure.
+function translateProvideHTML(tag: t.NodePath) {
+ const { node } = tag;
+ const { valueAttr, valueChangeAttr } = getContextAttrs(node);
+ const programExtra = getProgram().node.extra!;
+ const mutable = !!programExtra.contextMutable;
+ const section = getSection(tag);
+ writer.flushInto(tag);
+
+ if (valueChangeAttr) {
+ setSectionSerializedValue(
+ section,
+ contextChangeAccessor(),
+ t.logicalExpression(
+ "||",
+ valueChangeAttr.value,
+ t.unaryExpression("void", t.numericLiteral(0)),
+ ),
+ );
+ }
+
+ const bodyStatements = node.body.body as unknown as t.Statement[];
+ const providerId = tag.hub.file.metadata.marko.id;
+
+ tag
+ .replaceWith(
+ t.expressionStatement(
+ callRuntime(
+ "_context_provide",
+ t.stringLiteral(providerId),
+ valueAttr!.value,
+ t.arrowFunctionExpression([], t.blockStatement(bodyStatements)),
+ mutable ? getScopeIdIdentifier(section) : undefined,
+ ),
+ ),
+ )[0]
+ .skip();
+}
+
+function translateConsumeHTML(tag: t.NodePath) {
+ const { node } = tag;
+ const tagExtra = node.extra!;
+ const providerId = tagExtra.contextProviderId!;
+ const varBinding = (node.var as t.Identifier).extra!.binding!;
+ const debugName =
+ !isOptimize() && t.stringLiteral(tagExtra.contextProviderRequest!);
+ let read: t.Expression;
+
+ if (isMutableTierConsume(tag) && getWiringLead(tag) === varBinding) {
+ const section = getSection(tag);
+ read = callRuntime(
+ "_context_link",
+ getScopeIdIdentifier(section),
+ t.stringLiteral(providerId),
+ t.stringLiteral(getResumeRegisterId(section, varBinding, "context")),
+ debugName,
+ );
+ } else {
+ read = callRuntime("_context_get", t.stringLiteral(providerId), debugName);
+ }
+
+ // `let` matches ``'s server semantics for the writable form: an
+ // SSR-time assignment mutates the local for the remainder of the render.
+ translateVar(tag, read, tagExtra.contextProviderWritable ? "let" : "const");
+ tag.remove();
+}
+
+function translateProvideDOM(tag: t.NodePath) {
+ const { node } = tag;
+ const programExtra = getProgram().node.extra!;
+ const section = getSection(tag);
+ const tagExtra = node.extra!;
+ const { valueAttr, valueChangeAttr } = getContextAttrs(node);
+ const providerId = t.stringLiteral(tag.hub.file.metadata.marko.id);
+
+ if (programExtra.contextMutable) {
+ addStatement(
+ "render",
+ section,
+ tagExtra.referencedBindings,
+ t.expressionStatement(
+ callRuntime(
+ "_context_value",
+ scopeIdentifier,
+ valueAttr!.value,
+ providerId,
+ ),
+ ),
+ );
+ addStatement(
+ "render",
+ section,
+ undefined,
+ t.expressionStatement(
+ callRuntime("_context_branch", scopeIdentifier, providerId),
+ ),
+ );
+
+ if (valueChangeAttr) {
+ addStatement(
+ "render",
+ section,
+ valueChangeAttr.value.extra?.referencedBindings,
+ t.expressionStatement(
+ callRuntime(
+ "_context_change",
+ scopeIdentifier,
+ valueChangeAttr.value,
+ ),
+ ),
+ );
+ }
+ }
+
+ // Both tiers splice the body inline.
+ const bodyStatements = node.body.body as unknown as t.Statement[];
+ for (const replacement of tag.replaceWithMultiple(bodyStatements)) {
+ replacement.skip();
+ }
+}
+
+// A client-created immutable provider stamps its branch and value like the
+// serialized box (evaluating the value under client `$global` rules); the
+// tree-shaken bundle drops this when the provider only renders server-side.
+function translateProvideDOMStatements(tag: t.NodePath) {
+ const { node } = tag;
+ if (getProgram().node.extra!.contextMutable) return;
+ const section = getSection(tag);
+ const { valueAttr } = getContextAttrs(node);
+
+ addStatement(
+ "render",
+ section,
+ node.extra!.referencedBindings,
+ t.expressionStatement(
+ t.assignmentExpression(
+ "=",
+ t.memberExpression(
+ scopeIdentifier,
+ t.stringLiteral(getAccessorProp().ContextValue),
+ true,
+ ),
+ valueAttr!.value,
+ ),
+ ),
+ );
+ addStatement(
+ "render",
+ section,
+ undefined,
+ t.expressionStatement(
+ callRuntime(
+ "_context_branch",
+ scopeIdentifier,
+ t.stringLiteral(tag.hub.file.metadata.marko.id),
+ ),
+ ),
+ );
+}
+
+// Immutable: the serialized slot, or resolve the reserved box when created
+// client-side. Mutable: a registered fan-out signal from setup.
+function translateConsumeDOM(tag: t.NodePath) {
+ const { node } = tag;
+ const tagExtra = node.extra!;
+ const section = getSection(tag);
+ const varBinding = (node.var as t.Identifier).extra!.binding!;
+
+ if (isMutableTierConsume(tag)) {
+ const providerId = tagExtra.contextProviderId!;
+ if (!consumerNeedsWiring(varBinding)) {
+ tag.remove();
+ return;
+ }
+
+ if (tagExtra.contextProviderWritable) {
+ initValue(varBinding).buildAssignment = (valueSection, value) =>
+ callRuntime(
+ "_context_write",
+ getScopeExpression(valueSection, section),
+ t.stringLiteral(providerId),
+ value,
+ );
+ }
+
+ if (getWiringLead(tag) !== varBinding) {
+ tag.remove();
+ return;
+ }
+
+ const scopeParam = generateUidIdentifier("scope");
+ const providerParam = generateUidIdentifier("provider");
+ const wrapperId = generateUidIdentifier(
+ `context_${(node.var as t.Identifier).name}`,
+ );
+ const memberCalls = tagExtra
+ .contextConsumers!.filter((member) => !pruneBinding(member))
+ .map((member) =>
+ t.callExpression(initValue(member).identifier, [
+ t.identifier(scopeParam.name),
+ t.memberExpression(
+ t.identifier(providerParam.name),
+ t.stringLiteral(getAccessorProp().ContextValue),
+ true,
+ ),
+ ]),
+ );
+
+ getProgram().node.body.push(
+ t.variableDeclaration("const", [
+ t.variableDeclarator(
+ wrapperId,
+ callRuntime(
+ "_context_closure",
+ t.stringLiteral(providerId),
+ t.arrowFunctionExpression(
+ [scopeParam, providerParam],
+ memberCalls.length > 1
+ ? t.sequenceExpression(memberCalls)
+ : (memberCalls[0] ?? t.blockStatement([])),
+ ),
+ t.stringLiteral(
+ getResumeRegisterId(section, varBinding, "context"),
+ ),
+ !isOptimize() && t.stringLiteral(tagExtra.contextProviderRequest!),
+ ),
+ ),
+ ]),
+ );
+
+ addStatement(
+ "render",
+ section,
+ undefined,
+ t.expressionStatement(t.callExpression(wrapperId, [scopeIdentifier])),
+ );
+ } else {
+ const signal = initValue(varBinding);
+
+ // Slot when server-rendered, resolved from the reserved box when the
+ // consumer is created client-side.
+ addValue(
+ section,
+ undefined,
+ signal,
+ callRuntime(
+ "_context_read",
+ scopeIdentifier,
+ getScopeAccessorLiteral(varBinding, true),
+ t.stringLiteral(tagExtra.contextProviderId!),
+ !isOptimize() && t.stringLiteral(tagExtra.contextProviderRequest!),
+ ),
+ );
+ }
+
+ tag.remove();
+}
+
+// Wiring matters only when the value is observable client-side: any read
+// keeps the binding un-pruned; assignments need the link for writes.
+function consumerNeedsWiring(binding: Binding) {
+ return !pruneBinding(binding) || !!binding.assignmentSections;
+}
+
+// A self-consume is conservatively mutable at analyze (the provide side is
+// classified later); by translate the program flag is final, so a
+// server-only self-consume drops to the immutable tier.
+function isMutableTierConsume(tag: t.NodePath) {
+ const tagExtra = tag.node.extra!;
+ return (
+ !!tagExtra.contextProviderMutable &&
+ (tagExtra.contextProviderId !== tag.hub.file.metadata.marko.id ||
+ getProgram().node.extra!.contextMutable !== false)
+ );
+}
+
+// The first group member needing wiring carries the group's single link and
+// composed fan-out signal; `undefined` means the group serializes nothing.
+function getWiringLead(tag: t.NodePath) {
+ return tag.node.extra!.contextConsumers!.find(consumerNeedsWiring);
+}
+
+function getContextAttrs(node: t.MarkoTag) {
+ let valueAttr: t.MarkoAttribute | undefined;
+ let valueChangeAttr: t.MarkoAttribute | undefined;
+ for (const attr of node.attributes) {
+ const markoAttr = attr as t.MarkoAttribute;
+ if (markoAttr.name === "valueChange") {
+ valueChangeAttr = markoAttr;
+ } else if (markoAttr.default || markoAttr.name === "value") {
+ valueAttr = markoAttr;
+ }
+ }
+ return { valueAttr, valueChangeAttr };
+}
+
+// Syntactic scan for a provide-mode `` (used for self-referencing
+// `from=`, where the program's own flags are not final yet).
+function findProvideTag(node: t.Node): t.MarkoTag | undefined {
+ if (t.isMarkoTag(node)) {
+ if (
+ t.isStringLiteral(node.name, { value: "context" }) &&
+ !node.attributes.some(
+ (attr) => t.isMarkoAttribute(attr) && attr.name === "from",
+ ) &&
+ node.attributes.some(
+ (attr) =>
+ t.isMarkoAttribute(attr) && (attr.default || attr.name === "value"),
+ )
+ ) {
+ return node;
+ }
+ }
+
+ for (const child of t.isProgram(node) || t.isMarkoTagBody(node)
+ ? node.body
+ : t.isMarkoTag(node)
+ ? node.body.body
+ : []) {
+ const found = findProvideTag(child);
+ if (found) return found;
+ }
+}
+
+function hasValueChangeAttr(tag: t.MarkoTag) {
+ return tag.attributes.some(
+ (attr) => t.isMarkoAttribute(attr) && attr.name === "valueChange",
+ );
+}
+
+// Mirrors `changeAccessor` in dom/context.ts.
+function contextChangeAccessor() {
+ return (getAccessorPrefix().TagVariableChange +
+ getAccessorProp().ContextValue) as AccessorProp;
+}
+
+function buildAttrError(
+ tag: t.NodePath,
+ attr: t.MarkoAttribute,
+ message: string,
+) {
+ const attrPath = tag.get("attributes").find((a) => a.node === attr) as
+ t.NodePath | undefined;
+ return (attrPath ?? tag.get("name")).buildCodeFrameError(message);
+}
diff --git a/packages/runtime-tags/src/translator/core/for.ts b/packages/runtime-tags/src/translator/core/for.ts
index e249296b069..7a6b73e17e5 100644
--- a/packages/runtime-tags/src/translator/core/for.ts
+++ b/packages/runtime-tags/src/translator/core/for.ts
@@ -59,6 +59,7 @@ import { translateByTarget } from "../util/visitors";
import * as walks from "../util/walks";
import * as writer from "../util/writer";
import { kSkipEndTag } from "../visitors/tag/native-tag";
+import { buildContextReserve } from "./context";
type ForType = "in" | "of" | "to" | "until";
const kStatefulReason = Symbol(" stateful reason");
@@ -282,6 +283,17 @@ export default {
}
}
+ // A client-changing loop input can create fresh iteration branches;
+ // server-only contexts consumed beneath must serialize their box.
+ const reserveStatement = buildContextReserve(
+ tagSection,
+ statefulSerializeReason,
+ [bodySection],
+ );
+ if (reserveStatement) {
+ statements.push(reserveStatement);
+ }
+
statements.push(
t.expressionStatement(callRuntime(forTagHTMLRuntime, ...forTagArgs)),
);
diff --git a/packages/runtime-tags/src/translator/core/if.ts b/packages/runtime-tags/src/translator/core/if.ts
index 51a7f723508..fd204015035 100644
--- a/packages/runtime-tags/src/translator/core/if.ts
+++ b/packages/runtime-tags/src/translator/core/if.ts
@@ -61,6 +61,7 @@ import { translateByTarget } from "../util/visitors";
import * as walks from "../util/walks";
import * as writer from "../util/writer";
import { kSkipEndTag } from "../visitors/tag/native-tag";
+import { buildContextReserve } from "./context";
const kStatefulReason = Symbol(" stateful reason");
const BRANCHES_LOOKUP = new WeakMap<
@@ -279,6 +280,17 @@ export const IfTag = {
);
}
+ // A stateful condition can create branch content client-side; any
+ // server-only contexts consumed beneath must serialize their box.
+ const reserveStatement = buildContextReserve(
+ ifTagSection,
+ getSerializeReason(ifTagSection, kStatefulReason),
+ branches.map(([, branchBody]) => branchBody),
+ );
+ if (reserveStatement) {
+ nextTag.insertBefore(reserveStatement);
+ }
+
nextTag.insertBefore(statement!);
}
},
diff --git a/packages/runtime-tags/src/translator/core/index.ts b/packages/runtime-tags/src/translator/core/index.ts
index 6ed1119b7b8..c21ff8332d8 100644
--- a/packages/runtime-tags/src/translator/core/index.ts
+++ b/packages/runtime-tags/src/translator/core/index.ts
@@ -3,6 +3,7 @@ import AttrsTag from "./attrs";
import AwaitTag from "./await";
import ClientTag from "./client";
import ConstTag from "./const";
+import ContextTag from "./context";
import DebugTag from "./debug";
import DefineTag from "./define";
import EffectTag from "./effect";
@@ -31,6 +32,7 @@ export default {
"": AwaitTag,
"": ClientTag,
"": ConstTag,
+ "": ContextTag,
"": DebugTag,
"": DefineTag,
"": EffectTag,
diff --git a/packages/runtime-tags/src/translator/util/sections.ts b/packages/runtime-tags/src/translator/util/sections.ts
index a5a80ea7c12..6ed5c0a9610 100644
--- a/packages/runtime-tags/src/translator/util/sections.ts
+++ b/packages/runtime-tags/src/translator/util/sections.ts
@@ -290,6 +290,9 @@ export function getNodeContentType(
case "await":
case "try":
case "show":
+ case "context":
+ // The body renders inline exactly once; Dynamic is always safe
+ // (a boundary marker only at section edges).
return ContentType.Dynamic;
default:
return null;
@@ -494,6 +497,7 @@ function isNativeNode(tag: t.NodePath) {
case "html-script":
case "html-style":
case "show":
+ case "context":
return true;
case "style":
return tag.node.body.body.some((child) => t.isMarkoPlaceholder(child));
diff --git a/packages/runtime-tags/src/translator/visitors/program/index.ts b/packages/runtime-tags/src/translator/visitors/program/index.ts
index d493c56000d..fced8abb3e7 100644
--- a/packages/runtime-tags/src/translator/visitors/program/index.ts
+++ b/packages/runtime-tags/src/translator/visitors/program/index.ts
@@ -5,6 +5,13 @@ import {
resolveRelativePath,
} from "@marko/compiler/babel-utils";
+import {
+ addConsumedContext,
+ getConsumedContexts,
+ kProvideInfo,
+ kRecursiveRefs,
+ kSelfConsumes,
+} from "../../core/context";
import { addAssetImport } from "../../util/asset-imports";
import {
type BindingPropTree,
@@ -22,12 +29,20 @@ import {
import {
BindingType,
finalizeReferences,
+ kBranchSerializeReason,
trackParamsReferences,
} from "../../util/references";
import { resolveRelativeToEntry } from "../../util/resolve-relative-to-entry";
import { getCompatRuntimeFile, getRuntimePath } from "../../util/runtime";
import { startSection } from "../../util/sections";
-import { sectionHasSetupStatements } from "../../util/setup-statements";
+import {
+ addSerializeReason,
+ getSerializeSourcesForExpr,
+} from "../../util/serialize-reasons";
+import {
+ addSetupStatement,
+ sectionHasSetupStatements,
+} from "../../util/setup-statements";
import type { TemplateVisitor } from "../../util/visitors";
import programDOM from "./dom";
import programHTML from "./html";
@@ -99,7 +114,45 @@ export default {
programExtra.domExports!.params = getBindingPropTree(paramsBinding);
}
+ // Publish `` cross-template flags once, after
+ // `finalizeReferences()` (mutability reads finalized `sources`).
+ // Self-consumes are typed conservatively at analyze and pick their
+ // tier from this flag at translate.
+ const contextProvide = programExtra[kProvideInfo];
+ if (contextProvide) {
+ programExtra.providesContext = true;
+ programExtra.contextMutable =
+ contextProvide.writable ||
+ !!getSerializeSourcesForExpr(contextProvide.valueExtra);
+ if (contextProvide.writable) {
+ programExtra.contextWritable = true;
+ }
+ // Both tiers emit dom setup statements (a client-created provider
+ // stamps its branch and value).
+ addSetupStatement(contextProvide.section);
+ if (programExtra.contextMutable) {
+ // The mutable provider's runtime branch stamp needs the
+ // containing branch serialized.
+ addSerializeReason(
+ contextProvide.section,
+ true,
+ kBranchSerializeReason,
+ );
+ } else {
+ // Immutable self-consumes join the consumed-context reach sets
+ // now that the tier is final.
+ for (const selfSection of programExtra[kSelfConsumes] || []) {
+ addConsumedContext(selfSection, program.hub.file.metadata.marko.id);
+ }
+ }
+ }
+
const section = programExtra.section!;
+ // Self-recursive render sites see the template's own final set.
+ for (const recursiveSection of programExtra[kRecursiveRefs] || []) {
+ addConsumedContext(recursiveSection, getConsumedContexts(section));
+ }
+ programExtra.consumedContexts = getConsumedContexts(section) || new Set();
if (!section.hoistedTo && !sectionHasSetupStatements(section)) {
// The setup export will be a noop, letting parent templates skip
// importing and calling it (checked when this template translates).
diff --git a/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts b/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts
index c84978b943f..82859174ac0 100644
--- a/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts
+++ b/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts
@@ -15,6 +15,7 @@ import path from "path";
import { WalkCode } from "../../../common/types";
import type { LoadTrigger } from "../../../html/assets";
+import { addConsumedContext, kRecursiveRefs } from "../../core/context";
import { getBindingPropTree } from "../../util/binding-prop-tree";
import { generateUidIdentifier } from "../../util/generate-uid";
import { getTagName } from "../../util/get-tag-name";
@@ -29,10 +30,12 @@ import {
BindingType,
createBinding,
getScopeAccessorLiteral,
+ kBranchSerializeReason,
} from "../../util/references";
import { callRuntime } from "../../util/runtime";
import { createScopeReadExpression } from "../../util/scope-read";
import { getOrCreateSection } from "../../util/sections";
+import { addSerializeReason } from "../../util/serialize-reasons";
import { addSetupStatement } from "../../util/setup-statements";
import { addStatement, getSignal } from "../../util/signals";
import { createProgramState } from "../../util/state";
@@ -93,6 +96,29 @@ export default {
);
}
+ if (childExtra.providesContext && childExtra.contextMutable !== false) {
+ // A mutable provider stamps its containing branch's scope at
+ // runtime, which the reason analysis cannot otherwise see.
+ addSerializeReason(
+ getOrCreateSection(tag),
+ true,
+ kBranchSerializeReason,
+ );
+ }
+
+ // Server-only contexts consumed under this tag, for branch reach
+ // analysis. A mid-analysis child means recursion: a self reference
+ // resolves to this template's own set at the program exit hook (a
+ // mutual cycle is unknowable, like other dynamic content).
+ if (childExtra.consumedContexts !== undefined) {
+ addConsumedContext(
+ getOrCreateSection(tag),
+ childExtra.consumedContexts,
+ );
+ } else if (childProgram === getProgram().node) {
+ (programExtra[kRecursiveRefs] ??= []).push(getOrCreateSection(tag));
+ }
+
if (tagExtra.tagNameLoad || !childExtra.domExports?.setupEmpty) {
// The child's setup call lands in this section's setup unless the
// child template proved its setup export is a noop (a mid analysis
diff --git a/packages/runtime-tags/tags/context.d.marko b/packages/runtime-tags/tags/context.d.marko
new file mode 100644
index 00000000000..9514d9bbce1
--- /dev/null
+++ b/packages/runtime-tags/tags/context.d.marko
@@ -0,0 +1,7 @@
+/** File for types only, not actual implementation **/
+
+export interface Input {
+ value?: T;
+ valueChange?: ((newValue: T) => void) | false | null;
+ from?: string;
+}