@velarscript/web 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +2 -2
  2. package/dist/analyzer.d.ts +164 -23
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +1254 -133
  5. package/dist/analyzer.js.map +1 -1
  6. package/dist/ast.d.ts +11 -0
  7. package/dist/ast.d.ts.map +1 -1
  8. package/dist/ast.js +22 -0
  9. package/dist/ast.js.map +1 -1
  10. package/dist/compiler.d.ts.map +1 -1
  11. package/dist/compiler.js +69 -44
  12. package/dist/compiler.js.map +1 -1
  13. package/dist/css-string.d.ts +32 -0
  14. package/dist/css-string.d.ts.map +1 -0
  15. package/dist/css-string.js +66 -0
  16. package/dist/css-string.js.map +1 -0
  17. package/dist/css-tokens.d.ts +16 -0
  18. package/dist/css-tokens.d.ts.map +1 -1
  19. package/dist/css-tokens.js +84 -0
  20. package/dist/css-tokens.js.map +1 -1
  21. package/dist/elements.d.ts +55 -0
  22. package/dist/elements.d.ts.map +1 -1
  23. package/dist/elements.js +152 -0
  24. package/dist/elements.js.map +1 -1
  25. package/dist/emitter.d.ts +10 -0
  26. package/dist/emitter.d.ts.map +1 -1
  27. package/dist/emitter.js +588 -159
  28. package/dist/emitter.js.map +1 -1
  29. package/dist/keyframes.d.ts +17 -0
  30. package/dist/keyframes.d.ts.map +1 -1
  31. package/dist/keyframes.js +28 -9
  32. package/dist/keyframes.js.map +1 -1
  33. package/dist/lexer.d.ts +21 -7
  34. package/dist/lexer.d.ts.map +1 -1
  35. package/dist/lexer.js +32 -12
  36. package/dist/lexer.js.map +1 -1
  37. package/dist/look-static.d.ts.map +1 -1
  38. package/dist/look-static.js +120 -20
  39. package/dist/look-static.js.map +1 -1
  40. package/dist/look.d.ts +15 -0
  41. package/dist/look.d.ts.map +1 -1
  42. package/dist/look.js +7 -2
  43. package/dist/look.js.map +1 -1
  44. package/dist/parser.d.ts.map +1 -1
  45. package/dist/parser.js +15 -20
  46. package/dist/parser.js.map +1 -1
  47. package/dist/runtime-foundation.d.ts.map +1 -1
  48. package/dist/runtime-foundation.js +321 -70
  49. package/dist/runtime-foundation.js.map +1 -1
  50. package/dist/runtime.d.ts.map +1 -1
  51. package/dist/runtime.js +320 -179
  52. package/dist/runtime.js.map +1 -1
  53. package/dist/stable-order.d.ts +16 -0
  54. package/dist/stable-order.d.ts.map +1 -0
  55. package/dist/stable-order.js +18 -0
  56. package/dist/stable-order.js.map +1 -0
  57. package/dist/types.d.ts +0 -1
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/types.js +0 -16
  60. package/dist/types.js.map +1 -1
  61. package/dist/websocket-runtime.d.ts.map +1 -1
  62. package/dist/websocket-runtime.js +9 -5
  63. package/dist/websocket-runtime.js.map +1 -1
  64. package/dist/worker-runtime.d.ts.map +1 -1
  65. package/dist/worker-runtime.js +9 -7
  66. package/dist/worker-runtime.js.map +1 -1
  67. package/package.json +2 -2
package/dist/emitter.js CHANGED
@@ -1,9 +1,11 @@
1
- import { cssPropertyName, LOOK_ARITHMETIC_HINT, LOOK_MEDIA_LENGTH_UNITS, LOOK_PROPERTIES } from "./look.js";
1
+ import { cssPropertyName, LOOK_ARITHMETIC_HINT, LOOK_MEDIA_LENGTH_UNITS, LOOK_PROPERTIES, LOOK_PROPERTY_KEYWORDS, LOOK_PROPERTY_VALUE_KINDS } from "./look.js";
2
+ import { isCssDeclarationValue } from "./css-tokens.js";
3
+ import { CSS_STRING_RUNTIME } from "./css-string.js";
2
4
  import { collectLookStaticValues, evaluateLookStaticExpression, isLookStaticValue, lookStaticCss } from "./look-static.js";
3
5
  import { keyframeCssValue, keyframesCanonical, keyframesName } from "./keyframes.js";
4
6
  import { JavaScriptEmitter, spanIdentity, VELAR_ERROR_NORMALIZATION_MODULE, VELAR_RUNTIME_REGISTRY_KEY } from "@velarscript/compiler/extension";
5
7
  import { WEB_RUNTIME_FOUNDATION, WEB_RUNTIME_FOUNDATION_SHARED_ERROR } from "./runtime-foundation.js";
6
- import { isWebExpression, isWebJsx, isWebKeyframes, isWebLook, isWebStatement, isWebUnit, webExpressionContainsDirectAwait, webStatementContainsDirectAwait, } from "./ast.js";
8
+ import { isWebExpression, isWebJsx, isWebKeyframes, isWebLook, isWebStatement, isWebUnit, webExpressionContainsDirectAwait, webStatementContainsDirectAwait, webWatchSubjectLabel, } from "./ast.js";
7
9
  const FILE_TYPE_RUNTIME = String.raw `
8
10
  function __velarFileTypeIs(value) {
9
11
  const descriptor = Object.getOwnPropertyDescriptor(globalThis, Symbol.for("velar.file.registry.v1"));
@@ -41,6 +43,8 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
41
43
  needsLookArithmeticRuntime = false;
42
44
  importedLookStaticValues;
43
45
  lookStaticValues = new Map();
46
+ /** CSS names of the closed-keyword properties this module styles, for the runtime guard. */
47
+ lookKeywordProperties = new Set();
44
48
  jsxId = 0;
45
49
  keyframeNames = new Map();
46
50
  constructor(hints, forcedFunctionExports = new Set(), resourceContents = new Map(), extensionImports = new Map(), options = {}) {
@@ -117,13 +121,26 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
117
121
  }
118
122
  webRuntimeHelpers() {
119
123
  if (!this.usesSharedRuntimeModules())
120
- return [webRuntime(WEB_RUNTIME_FOUNDATION)];
124
+ return [webRuntime(WEB_RUNTIME_FOUNDATION, this.lookKeywordTable())];
121
125
  this.requireRuntimeModule(VELAR_ERROR_NORMALIZATION_MODULE);
122
126
  return [
123
127
  `import { errorApply as __velarErrorApply, errorCode as __velarErrorCode, isError as __velarIsError, normalizeError as __velarNormalizeError } from ${JSON.stringify(VELAR_ERROR_NORMALIZATION_MODULE)};`,
124
- webRuntime(WEB_RUNTIME_FOUNDATION_SHARED_ERROR),
128
+ webRuntime(WEB_RUNTIME_FOUNDATION_SHARED_ERROR, this.lookKeywordTable()),
125
129
  ];
126
130
  }
131
+ /**
132
+ * The closed keyword sets of the properties this module styles, so a value
133
+ * the compiler could not read is still checked before it reaches the DOM.
134
+ * Only the properties written here ship: the whole table is 17 KiB, and a
135
+ * module pays for the properties it uses.
136
+ */
137
+ lookKeywordTable() {
138
+ const entries = [...this.lookKeywordProperties].sort()
139
+ .map((name) => ` ${JSON.stringify(cssPropertyName(name))}: ${JSON.stringify([...LOOK_PROPERTY_KEYWORDS.get(name) ?? []])},`);
140
+ return entries.length === 0
141
+ ? "const __velarLookKeywords = { __proto__: null };"
142
+ : `const __velarLookKeywords = {\n __proto__: null,\n${entries.join("\n")}\n};`;
143
+ }
127
144
  reactiveBridgeHelpers(needsJavaScriptCallBoundary, needsCollections) {
128
145
  if (this.usesSharedRuntimeModules())
129
146
  return super.reactiveBridgeHelpers(needsJavaScriptCallBoundary, needsCollections);
@@ -264,7 +281,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
264
281
  return this.emitComponent(statement, depth);
265
282
  if (statement.kind === "ExtensionStatement:web:state") {
266
283
  const indentation = " ".repeat(depth);
267
- return `${indentation}${statement.exported ? "export " : ""}const ${statement.name} = __velarState(${this.emitMappedExpression(statement.initializer)});`;
284
+ return `${indentation}${statement.exported ? "export " : ""}const ${statement.name} = __velarState(${this.emitMappedExpression(statement.initializer)}, ${JSON.stringify(statement.name)});`;
268
285
  }
269
286
  if (statement.kind === "ExtensionStatement:web:computed") {
270
287
  const indentation = " ".repeat(depth);
@@ -288,7 +305,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
288
305
  const indentation = " ".repeat(depth);
289
306
  const parameters = [statement.currentName, statement.previousName].filter((name) => name !== null).join(", ");
290
307
  const body = this.emitStatementLines(statement.body, depth + 1).join("\n");
291
- return `${indentation}__velarWatch(() => ${this.emitMappedExpression(statement.expression)}, (${parameters}) => {${body ? `\n${body}\n${indentation}` : ""}}, __velarGlobalScope);`;
308
+ return `${indentation}__velarWatch(() => ${this.emitMappedExpression(statement.expression)}, (${parameters}) => {${body ? `\n${body}\n${indentation}` : ""}}, __velarGlobalScope, ${JSON.stringify(webWatchSubjectLabel(statement.expression))});`;
292
309
  }
293
310
  }
294
311
  if (statement.kind === "AssignmentStatement") {
@@ -331,8 +348,6 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
331
348
  return "__velarMount";
332
349
  if (expression.name === "tick")
333
350
  return "__velarTick";
334
- if (expression.name === "cached")
335
- return "__velarRuntime.computed";
336
351
  const controlled = this.hints.extensionLiterals.get(spanIdentity(expression.span));
337
352
  if (controlled !== undefined)
338
353
  return JSON.stringify(controlled);
@@ -432,7 +447,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
432
447
  let cleanupBody = [];
433
448
  for (const item of statement.body) {
434
449
  if (item.kind === "ExtensionStatement:web:state") {
435
- lines.push(`${bodyIndent}const ${item.name} = __velarState(${this.emitMappedExpression(item.initializer)});`);
450
+ lines.push(`${bodyIndent}const ${item.name} = __velarState(${this.emitMappedExpression(item.initializer)}, ${JSON.stringify(item.name)});`);
436
451
  }
437
452
  else if (item.kind === "ExtensionStatement:web:computed") {
438
453
  lines.push(`${bodyIndent}const ${item.name} = __velarComputed(() => (${this.emitMappedExpression(item.initializer)}));`);
@@ -451,7 +466,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
451
466
  else if (item.kind === "ExtensionStatement:web:watch") {
452
467
  const parameters = [item.currentName, item.previousName].filter((name) => name !== null).join(", ");
453
468
  const watchLines = this.emitStatementLines(item.body, depth + 3).join("\n");
454
- lines.push(`${bodyIndent}__velarWatch(() => ${this.emitMappedExpression(item.expression)}, (${parameters}) => {${watchLines ? `\n${watchLines}\n${bodyIndent}` : ""}}, __velarComponentScope);`);
469
+ lines.push(`${bodyIndent}__velarWatch(() => ${this.emitMappedExpression(item.expression)}, (${parameters}) => {${watchLines ? `\n${watchLines}\n${bodyIndent}` : ""}}, __velarComponentScope, ${JSON.stringify(webWatchSubjectLabel(item.expression))});`);
455
470
  }
456
471
  else if (item.kind === "ExtensionStatement:web:expose") {
457
472
  expose ??= item.value;
@@ -492,7 +507,10 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
492
507
  lines.push(`${bodyIndent}const __velarHandle = ${expose ? `__velarComponentHandle(${this.emitMappedExpression(expose)}, ${JSON.stringify(statement.name)})` : "null"};`);
493
508
  lines.push(`${bodyIndent}if (__velarProps.class !== undefined) __velarClassBindRoot(__velarRoot, () => __velarProps.class, __velarComponentScope);`);
494
509
  lines.push(`${bodyIndent}if (__velarProps.look !== undefined) __velarLookBindRoot(__velarRoot, () => __velarProps.look, __velarComponentScope);`);
495
- lines.push(`${bodyIndent}if (__velarProps.__velarStyle !== undefined) __velarStyleBindRoot(__velarRoot, () => __velarProps.__velarStyle, __velarComponentScope);`);
510
+ // 'class' and 'look' are fields a component may declare and read, so they
511
+ // arrive as props. The 'style:' slot is not: it is bound to the instance
512
+ // root by whoever wrote it, at the instantiation site, for every component
513
+ // alike -- see __velarInstantiate.
496
514
  const mounted = this.emitStatementLines(mountedBody, depth + 3).join("\n");
497
515
  const cleanup = cleanupBody.map((child) => {
498
516
  if (["VariableDeclaration", "FunctionDeclaration", "ClassDeclaration", "TypeDeclaration", "EnumDeclaration"].includes(child.kind)) {
@@ -567,9 +585,11 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
567
585
  }
568
586
  // Children stay a thunk so the charter's evaluation order holds at
569
587
  // the runtime boundary: props left to right, then children, then the
570
- // component function.
588
+ // component function. The thunk takes the scope to build into, so the
589
+ // position that shows the slot owns what it built and destroys it when
590
+ // it stops showing it.
571
591
  const children = hasMeaningfulChildren(expression.children)
572
- ? `() => (${this.emitJsxChildren(expression.children, componentScope, namespace)})`
592
+ ? `(__velarChildrenScope = ${componentScope}) => (${this.emitJsxChildrenCode(expression.children, namespace)})`
573
593
  : "undefined";
574
594
  const ref = expression.attributes.find((attribute) => attribute.name === "ref")?.value;
575
595
  const refSetter = ref && typeof ref !== "string" && ref.kind === "IdentifierExpression"
@@ -679,6 +699,19 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
679
699
  const fragment = { kind: "ExtensionExpression:web:jsx", tag: "", tagSpan: { start: fragmentSpan.start, end: fragmentSpan.start }, attributes: [], children, span: fragmentSpan };
680
700
  return this.emitJsx(fragment, scope, true, namespace);
681
701
  }
702
+ // The slot body builds into whichever scope the consuming position hands it,
703
+ // so every observer, ref and cleanup it registers dies with that position
704
+ // rather than accumulating on the caller for the component's whole lifetime.
705
+ emitJsxChildrenCode(children, namespace) {
706
+ const previousScope = this.currentScope;
707
+ this.currentScope = "__velarChildrenScope";
708
+ try {
709
+ return this.emitJsxChildren(children, "__velarChildrenScope", namespace);
710
+ }
711
+ finally {
712
+ this.currentScope = previousScope;
713
+ }
714
+ }
682
715
  emitDynamicChild(parent, expression, scope, namespace) {
683
716
  const leaves = dynamicChildLeaves(expression);
684
717
  const previousScope = this.currentScope;
@@ -778,7 +811,24 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
778
811
  prepareLooks(program) {
779
812
  const rules = new Map();
780
813
  const keyframeRules = new Map();
814
+ const keyframeCanonicals = new Map();
781
815
  this.keyframeNames.clear();
816
+ this.lookKeywordProperties.clear();
817
+ // A token's stylesheet position used to be wherever it first appeared
818
+ // anywhere in the module, because a Map keeps first-insertion order. The
819
+ // sequence records that first appearance explicitly so emission can sort by
820
+ // condition rank first and fall back to declaration order, rather than
821
+ // letting an unrelated earlier look decide a later one's winner (LOK-U8).
822
+ let sequence = 0;
823
+ const addRule = (token, property, target, staticAtoms) => {
824
+ if (rules.has(token))
825
+ return;
826
+ rules.set(token, { token, property, target, staticAtoms, sequence: sequence += 1 });
827
+ };
828
+ const noteKeywordProperty = (name) => {
829
+ if (LOOK_PROPERTY_VALUE_KINDS.get(name) === "keyword")
830
+ this.lookKeywordProperties.add(name);
831
+ };
782
832
  const visit = (value) => {
783
833
  if (!value || typeof value !== "object")
784
834
  return;
@@ -786,14 +836,16 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
786
836
  if (record.kind === "ExtensionExpression:web:jsx") {
787
837
  const element = record;
788
838
  for (const attribute of element.attributes) {
789
- if (!attribute.name.startsWith("look:"))
839
+ if (!attribute.name.startsWith("look:") && !attribute.name.startsWith("style:"))
790
840
  continue;
791
- const name = attribute.name.slice("look:".length);
841
+ const name = attribute.name.slice(attribute.name.indexOf(":") + 1);
792
842
  if (!LOOK_PROPERTIES.has(name))
793
843
  continue;
844
+ noteKeywordProperty(name);
845
+ if (!attribute.name.startsWith("look:"))
846
+ continue;
794
847
  const property = cssPropertyName(name);
795
- const token = lookToken([], "", property);
796
- rules.set(token, { token, property, target: "", staticAtoms: [] });
848
+ addRule(lookToken([], "", property), property, "", []);
797
849
  }
798
850
  }
799
851
  if (record.kind === "ExtensionExpression:web:look") {
@@ -801,9 +853,9 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
801
853
  for (const entry of entries) {
802
854
  if (entry.kind === "LookProperty") {
803
855
  const property = cssPropertyName(entry.name);
856
+ noteKeywordProperty(entry.name);
804
857
  for (const context of contexts) {
805
- const token = lookToken(context.staticAtoms, target, property);
806
- rules.set(token, { token, property, target, staticAtoms: context.staticAtoms });
858
+ addRule(lookToken(context.staticAtoms, target, property), property, target, context.staticAtoms);
807
859
  }
808
860
  }
809
861
  else if (entry.kind === "LookIf") {
@@ -822,7 +874,16 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
822
874
  const canonical = keyframesCanonical(expression, this.lookStaticValues);
823
875
  const name = keyframesName(canonical);
824
876
  this.keyframeNames.set(spanIdentity(expression.span), name);
825
- if (!keyframeRules.has(name)) {
877
+ const reused = keyframeCanonicals.get(name);
878
+ // The name is a promise that equal structures share one rule. Reusing
879
+ // it for a structure that is not equal would make one animation play
880
+ // another's motion, so the reuse path proves the identity rather than
881
+ // trusting the digest (LOK-U11).
882
+ if (reused !== undefined && reused !== canonical) {
883
+ throw new Error(`Generated keyframes name ${name} collides between two different keyframe structures`);
884
+ }
885
+ if (reused === undefined) {
886
+ keyframeCanonicals.set(name, canonical);
826
887
  const stops = [...expression.stops]
827
888
  .sort((left, right) => Math.min(...left.offsets) - Math.min(...right.offsets))
828
889
  .map((stop) => {
@@ -831,7 +892,13 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
831
892
  .join(",");
832
893
  const declarations = stop.entries.map((entry) => {
833
894
  const css = keyframeCssValue(entry.value, this.lookStaticValues);
834
- return css === null ? "" : `${cssPropertyName(entry.name)}:${css}`;
895
+ // A value the lowering could not prove is one balanced
896
+ // declaration never reaches the concatenation: `}` in a stop
897
+ // closed the at-rule and turned the rest into author-owned CSS
898
+ // in the compiler-owned segment (LOK-U9). The analyzer already
899
+ // reports the same null as a diagnostic, so emission only has
900
+ // to stay structurally incapable of writing the escape.
901
+ return css === null || !isCssDeclarationValue(css) ? "" : `${cssPropertyName(entry.name)}:${css}`;
835
902
  }).filter(Boolean).join(";");
836
903
  return `${selectors}{${declarations}}`;
837
904
  }).join("");
@@ -846,16 +913,22 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
846
913
  }
847
914
  };
848
915
  visit(program);
849
- const lookCss = [];
850
- for (const rule of rules.values()) {
916
+ const lookCss = [...rules.values()]
917
+ .map((rule) => ({ rule, depth: lookConditionDepth(rule.staticAtoms) }))
918
+ // Rank decides, and declaration order only separates rules that share a
919
+ // rank. Emission used to follow the Map, so the sheet's byte order — and
920
+ // through it the per-module concatenation order the CLI sorts by
921
+ // filename — could pick the winner of a tie (LOK-U8, LOK-U10).
922
+ .sort((left, right) => left.depth - right.depth || left.rule.sequence - right.rule.sequence)
923
+ .map(({ rule, depth }) => {
851
924
  const hookAtoms = rule.staticAtoms.filter((atom) => atom.kind === "hook");
852
925
  const mediaAtoms = rule.staticAtoms.filter((atom) => atom.kind === "media" || atom.kind === "scheme" || atom.kind === "motion");
853
- const base = `[data-velar-look~=${JSON.stringify(rule.token)}]${rule.staticAtoms.length > 0 ? "[data-velar-look]" : ""}`;
926
+ const base = `[data-velar-look~=${JSON.stringify(rule.token)}]${"[data-velar-look]".repeat(depth)}`;
854
927
  const selectors = lookSelectors(base, hookAtoms, rule.target);
855
928
  const css = `${selectors.join(",")}{${lookDeclaration(rule.token, rule.property)}}`;
856
929
  const query = mediaAtoms.map(lookMediaQuery).join(" and ");
857
- lookCss.push(query ? `@media ${query}{${css}}` : css);
858
- }
930
+ return query ? `@media ${query}{${css}}` : css;
931
+ });
859
932
  const before = [];
860
933
  const after = [];
861
934
  for (const statement of program.body) {
@@ -935,6 +1008,14 @@ function lookConditionTerms(expression, negated = false, staticValues = new Map(
935
1008
  return [{ staticAtoms: [media], runtimeAtoms: [] }];
936
1009
  return [{ staticAtoms: [], runtimeAtoms: [{ expression, negated }] }];
937
1010
  }
1011
+ // A breakpoint is complementary the way the schemes and the motion preference
1012
+ // are: `not (width <= X)` is `width > X`, one condition with one media query.
1013
+ // The atom therefore folds the negation into its operator at construction, so
1014
+ // the two spellings reach `lookToken` as the same token instead of as two
1015
+ // rules that tie on specificity and are separated by source order.
1016
+ const LOOK_NEGATED_MEDIA_OPERATORS = new Map([
1017
+ ["<", ">="], ["<=", ">"], [">", "<="], [">=", "<"],
1018
+ ]);
938
1019
  function viewportAtom(expression, negated, staticValues) {
939
1020
  if (expression.kind !== "BinaryExpression" || !["<", "<=", ">", ">="].includes(expression.operator))
940
1021
  return null;
@@ -945,12 +1026,13 @@ function viewportAtom(expression, negated, staticValues) {
945
1026
  const threshold = evaluateLookStaticExpression(expression.right, staticValues);
946
1027
  if (threshold?.kind !== "unit" || !LOOK_MEDIA_LENGTH_UNITS.has(threshold.unit))
947
1028
  return null;
1029
+ const written = expression.operator;
948
1030
  return {
949
1031
  kind: "media",
950
1032
  name: expression.left.property,
951
- operator: expression.operator,
1033
+ operator: negated ? LOOK_NEGATED_MEDIA_OPERATORS.get(written) : written,
952
1034
  value: lookStaticCss(threshold),
953
- negated,
1035
+ negated: false,
954
1036
  };
955
1037
  }
956
1038
  // 'scheme.dark' / 'scheme.light' lower to prefers-color-scheme media atoms.
@@ -994,11 +1076,38 @@ function lookToken(atoms, target, property) {
994
1076
  return `scheme-${atom.name}`;
995
1077
  if (atom.kind === "motion")
996
1078
  return `motion-${atom.name}`;
997
- return `viewport-${atom.name}-${atom.negated ? "not-" : ""}${lookOperatorName(atom.operator)}-${atom.value}`;
1079
+ return `viewport-${atom.name}-${lookOperatorName(atom.operator)}-${atom.value}`;
998
1080
  }).sort();
999
1081
  const prefix = [target ? kebab(target) : "", conditions.length > 0 ? conditions.join("+") : "base"].filter(Boolean).join(":");
1000
1082
  return `${prefix}:${property}`;
1001
1083
  }
1084
+ /**
1085
+ * How many extra `[data-velar-look]` selectors a rule carries, so that the
1086
+ * winner between two Look rules is decided by the conditions they name rather
1087
+ * than by their position in the sheet.
1088
+ *
1089
+ * The bump used to be a single flat `+1` for any non-empty condition set, which
1090
+ * made every conditional rule specificity `(0,2,0)`: a state rule tied with a
1091
+ * media rule, and a two-condition refinement tied with the one-condition
1092
+ * fallback it refines. Ties then fell through to source order, and source order
1093
+ * is per-module concatenation order, which the CLI sorts by filename — so the
1094
+ * rendered colour could change when a file was renamed (LOK-U8, LOK-U10,
1095
+ * LOK-U12).
1096
+ *
1097
+ * The rank is base < media < state < media+state, and within a rank a rule that
1098
+ * names more conditions outranks one that names fewer. The per-rank span is
1099
+ * bounded so a pathological condition count cannot cross a rank boundary; rules
1100
+ * that saturate it fall back to declaration order, which is stable.
1101
+ */
1102
+ const LOOK_RANK_SPAN = 3;
1103
+ function lookConditionDepth(atoms) {
1104
+ if (atoms.length === 0)
1105
+ return 0;
1106
+ const hooks = atoms.filter((atom) => atom.kind === "hook").length;
1107
+ const media = atoms.length - hooks;
1108
+ const rank = hooks > 0 ? (media > 0 ? 2 : 1) : 0;
1109
+ return rank * LOOK_RANK_SPAN + Math.min(atoms.length, LOOK_RANK_SPAN);
1110
+ }
1002
1111
  function lookVariable(token) {
1003
1112
  return `--velar-look-${token.replace(/[^A-Za-z0-9_-]+/gu, "-")}`;
1004
1113
  }
@@ -1014,10 +1123,7 @@ function lookMediaQuery(atom) {
1014
1123
  return `(prefers-color-scheme: ${atom.name})`;
1015
1124
  if (atom.kind === "motion")
1016
1125
  return `(prefers-reduced-motion: ${atom.name})`;
1017
- const operator = atom.negated
1018
- ? atom.operator === "<" ? ">=" : atom.operator === "<=" ? ">" : atom.operator === ">" ? "<=" : "<"
1019
- : atom.operator;
1020
- return `(${atom.name} ${operator} ${atom.value})`;
1126
+ return `(${atom.name} ${atom.operator} ${atom.value})`;
1021
1127
  }
1022
1128
  function lookSelectors(base, atoms, target) {
1023
1129
  let selectors = [base];
@@ -1092,7 +1198,7 @@ function containsWebSyntax(value) {
1092
1198
  if (record.kind === "ExtensionStatement:web:component" || record.kind === "ExtensionStatement:web:expose" || record.kind === "ExtensionStatement:web:unsafe-css" || record.kind === "ExtensionExpression:web:look" || record.kind === "ExtensionExpression:web:keyframes" || record.kind === "ExtensionExpression:web:jsx"
1093
1199
  || record.kind === "ExtensionStatement:web:state" || record.kind === "ExtensionStatement:web:computed" || record.kind === "ExtensionStatement:web:resource" || record.kind === "ExtensionStatement:web:action" || record.kind === "ExtensionStatement:web:watch")
1094
1200
  return true;
1095
- if (record.kind === "IdentifierExpression" && (record.name === "mount" || record.name === "tick" || record.name === "cached"))
1201
+ if (record.kind === "IdentifierExpression" && (record.name === "mount" || record.name === "tick"))
1096
1202
  return true;
1097
1203
  return Object.values(record).some((child) => Array.isArray(child) ? child.some(containsWebSyntax) : containsWebSyntax(child));
1098
1204
  }
@@ -1213,61 +1319,6 @@ function __velarEventCall(value, name, nativeMethod) {
1213
1319
  return __velarEventReflectApply(method, value, []);
1214
1320
  }
1215
1321
 
1216
- // This scheduler has a twin inside the runtime registry (runtime.schedule in
1217
- // packages/web/src/runtime-foundation.ts) so registry-owned computed
1218
- // observers schedule correctly no matter which module stamped the registry.
1219
- // Both sides drain the same shared queues under the shared flushPending flag;
1220
- // their budgets and overflow behavior must stay identical.
1221
- function __velarSchedule(observer) {
1222
- const queue = observer.mode === "watch" ? __velarRuntime.watchQueue : __velarRuntime.domQueue;
1223
- if (!__velarGraphSetContains(queue, observer) && __velarGraphSetCount(queue) >= 100000) throw new RangeError("VelarScript reactive queues cannot exceed 100000 observers");
1224
- __velarGraphSetInsert(queue, observer);
1225
- if (!__velarRuntime.flushPending) {
1226
- __velarRuntime.flushPending = true;
1227
- __velarEnqueue(__velarFlush);
1228
- }
1229
- }
1230
-
1231
- // Both queues are drained live: an observer that re-schedules itself or another
1232
- // observer is picked up by the same walk. Two observers that invalidate each
1233
- // other therefore never leave this function, which froze the page with nothing
1234
- // on the error channel. The per-flush budget gives that case the same owned
1235
- // ending as the single-observer cap: stop the observers still queued, report
1236
- // once through velar/app, and let the turn finish.
1237
- function __velarFlushOverflow() {
1238
- const stalled = [];
1239
- for (const observer of __velarGraphSetItems(__velarRuntime.domQueue)) stalled[stalled.length] = observer;
1240
- for (const observer of __velarGraphSetItems(__velarRuntime.watchQueue)) stalled[stalled.length] = observer;
1241
- __velarGraphSetEmpty(__velarRuntime.domQueue);
1242
- __velarGraphSetEmpty(__velarRuntime.watchQueue);
1243
- for (let index = 0; index < stalled.length; index += 1) {
1244
- const observer = stalled[index];
1245
- if (typeof observer.stop === "function") observer.stop();
1246
- else observer.stopped = true;
1247
- }
1248
- __velarReport(new RangeError("Reactive updates cannot run more than 100000 observers in one flush"), "update", null);
1249
- }
1250
-
1251
- function __velarFlush() {
1252
- __velarRuntime.flushPending = false;
1253
- let budget = 100000;
1254
- for (const observer of __velarGraphSetItems(__velarRuntime.domQueue)) {
1255
- __velarGraphSetRemove(__velarRuntime.domQueue, observer);
1256
- if ((budget -= 1) < 0) { __velarFlushOverflow(); return; }
1257
- observer.run();
1258
- }
1259
- for (const observer of __velarGraphSetItems(__velarRuntime.watchQueue)) {
1260
- __velarGraphSetRemove(__velarRuntime.watchQueue, observer);
1261
- if ((budget -= 1) < 0) { __velarFlushOverflow(); return; }
1262
- observer.run();
1263
- }
1264
- if (__velarGraphSetCount(__velarRuntime.domQueue) || __velarGraphSetCount(__velarRuntime.watchQueue)) __velarScheduleFlush();
1265
- }
1266
-
1267
- function __velarScheduleFlush() {
1268
- if (!__velarRuntime.flushPending) { __velarRuntime.flushPending = true; __velarEnqueue(__velarFlush); }
1269
- }
1270
-
1271
1322
  function __velarTrack(subscribers) {
1272
1323
  __velarRuntime.trackSubscribers(subscribers);
1273
1324
  }
@@ -1289,7 +1340,12 @@ function __velarCleanupObserver(observer) {
1289
1340
  __velarRuntime.cleanupObserver(observer);
1290
1341
  }
1291
1342
 
1292
- function __velarObserver(read, mode, scope) {
1343
+ // D90 R21: "label" names the observer in a report -- a watch carries its
1344
+ // subject as the author spelled it -- and "sequence" is its registration
1345
+ // number, which is the order the flush runs the watch tier in. The counter is
1346
+ // application-wide rather than per module, so two modules' watches order by the
1347
+ // order the two modules initialized.
1348
+ function __velarObserver(read, mode, scope, label = "") {
1293
1349
  // The first run of a DOM observer executes while its JSX position is being
1294
1350
  // constructed, and construction is transactional: the failure must reach
1295
1351
  // the surrounding owner (the mount transaction at the root, the containing
@@ -1299,6 +1355,9 @@ function __velarObserver(read, mode, scope) {
1299
1355
  let initial = mode === "dom";
1300
1356
  const observer = {
1301
1357
  mode,
1358
+ label,
1359
+ sequence: __velarNextObserverSequence(),
1360
+ component: scope !== null && scope !== undefined && typeof scope.component === "string" ? scope.component : "",
1302
1361
  stopped: false,
1303
1362
  running: false,
1304
1363
  selfInvalidations: 0,
@@ -1328,7 +1387,12 @@ function __velarObserver(read, mode, scope) {
1328
1387
  } else {
1329
1388
  observer.selfInvalidations = 0;
1330
1389
  }
1331
- __velarSchedule(observer);
1390
+ // The registry owns the one scheduler: the emitted prelude used to carry
1391
+ // its own copy of the queue insert and the flush drain, and the two
1392
+ // definitions of one concept is what let a watch be classified three
1393
+ // different ways. The capability observers in packages/web/src/runtime.ts
1394
+ // have always scheduled this way.
1395
+ __velarRuntime.schedule(observer);
1332
1396
  },
1333
1397
  stop() { observer.stopped = true; __velarCleanupObserver(observer); },
1334
1398
  };
@@ -1374,8 +1438,8 @@ function __velarSetupEnd(value) {
1374
1438
  }
1375
1439
 
1376
1440
  // The framework's own reads on the author's behalf -- checking a prop is
1377
- // present, capturing the one-time snapshot a runtime component documents,
1378
- // reading a handler thunk. None of them freezes anything the author wrote: the
1441
+ // present, building the content a children slot holds, reading a handler
1442
+ // thunk. None of them freezes anything the author wrote: the
1379
1443
  // prop handle behind them stays live. Reporting them would be the false-positive
1380
1444
  // flood D70 rule 180 rejected, in its most literal form.
1381
1445
  function __velarInternalRead(read) {
@@ -1459,10 +1523,21 @@ function __velarFrozenText(value) {
1459
1523
  return typeof value === "string" ? __velarQuotedText(value) : __velarDomString(value);
1460
1524
  }
1461
1525
 
1462
- function __velarState(initial) {
1526
+ // "name" is the declared name an author "state" wrote, carried on the cell and
1527
+ // visible in the emitted source. The cells __velarResource and __velarAction
1528
+ // build for their own pending/error fields are created without one, because
1529
+ // they are the runtime's bookkeeping rather than state anyone declared.
1530
+ //
1531
+ // D90 R21: nothing reads it any more. It existed for the two watch referees --
1532
+ // only a cell with a declared name could be a declared write target -- and they
1533
+ // are gone with the clause. It is left in place rather than removed with them:
1534
+ // it is a fact about the cell, not a mechanism, and it is what a report naming
1535
+ // the state a runaway wrote would have to read.
1536
+ function __velarState(initial, name) {
1463
1537
  let value = __velarToRaw(initial);
1464
1538
  const subscribers = __velarGraphCreateSet();
1465
1539
  const cell = {
1540
+ velarStateName: name,
1466
1541
  // Named rather than shorthand so a captured stack shows a compiler frame
1467
1542
  // here: D70's report walks past its own frames to find the reading line,
1468
1543
  // and every JavaScript engine spells an anonymous getter differently.
@@ -1492,8 +1567,8 @@ function __velarState(initial) {
1492
1567
  }
1493
1568
 
1494
1569
  // D71 rule 182: a declared derived value reads bare, so it presents the same
1495
- // .get() face a state cell does. The cache underneath is the same one
1496
- // the cached reader returns; only the way it is read differs.
1570
+ // .get() face a state cell does. The cache underneath is the runtime's own
1571
+ // memo; only the face it is read through differs.
1497
1572
  function __velarComputed(read) {
1498
1573
  const access = __velarRuntime.computed(read);
1499
1574
  if (!__velarFrozenHooks) return __velarGraphFreeze({ get: access });
@@ -1658,19 +1733,31 @@ function __velarCleanupStep(run, scope) {
1658
1733
  } catch (error) { __velarReport(error, "cleanup", scope); }
1659
1734
  }
1660
1735
 
1661
- function __velarWatch(read, callback, scope) {
1736
+ // D90 R21: nothing here asks whether this watch writes. Execution order is
1737
+ // source order, so the only thing the flush needs of a watch is when it was
1738
+ // registered, and __velarObserver stamps that on it. The label is the subject
1739
+ // as the author spelled it, carried so a runaway flush can name the watches
1740
+ // that ran away.
1741
+ function __velarWatch(read, callback, scope, label = "") {
1662
1742
  let current;
1663
1743
  let currentVersion = 0;
1664
1744
  let initialized = false;
1665
- __velarObserver(() => {
1745
+ let observer = null;
1746
+ observer = __velarObserver(() => {
1666
1747
  const next = read();
1667
1748
  __velarRuntime.trackDeep(next);
1668
1749
  const nextVersion = __velarRuntime.versionOf(next);
1669
- if (initialized && (!__velarGraphSame(next, current) || nextVersion !== currentVersion)) callback(next, current);
1750
+ if (initialized && (!__velarGraphSame(next, current) || nextVersion !== currentVersion)) {
1751
+ // The body is the author's effect, not part of the watched expression:
1752
+ // its own reactive reads must never become dependencies of what is being
1753
+ // watched, or one write re-evaluates the expression twice and an
1754
+ // unwatched value re-runs the watch.
1755
+ __velarUntracked(() => callback(next, current));
1756
+ }
1670
1757
  current = next;
1671
1758
  currentVersion = nextVersion;
1672
1759
  initialized = true;
1673
- }, "watch", scope);
1760
+ }, "watch", scope, label);
1674
1761
  }
1675
1762
 
1676
1763
  function __velarComponentHandle(value, componentName) {
@@ -1702,6 +1789,15 @@ function __velarComponent(node, scope, mounted, cleanup, handleState) {
1702
1789
  let destroyed = false;
1703
1790
  const refCleanups = [];
1704
1791
  const ownedNodes = node && __velarDomNodeType(node) === 11 ? __velarDomChildNodes(node) : [node];
1792
+ // An enclosing component forwards to a nested component's host only when that
1793
+ // component's root sits at its own root level. Marking the nodes here is what
1794
+ // lets the enclosing scan walk past a nested host buried inside one of its
1795
+ // own elements instead of counting it as a second host of its own.
1796
+ for (let index = 0; index < ownedNodes.length; index += 1) {
1797
+ if (ownedNodes[index] && __velarDomNodeType(ownedNodes[index]) === 1) {
1798
+ __velarGraphDefine(ownedNodes[index], "__velarComponentNode", { value: true, enumerable: true, configurable: true });
1799
+ }
1800
+ }
1705
1801
  if (mounted) __velarAppendOwned(scope.mounts, mounted);
1706
1802
  return {
1707
1803
  __velarComponent: true,
@@ -1871,6 +1967,17 @@ function __velarAppend(parent, value, state = null) {
1871
1967
  throw new TypeError("JSX can render only text, finite numbers, bool, enums, WebNode values, and Lists of those values");
1872
1968
  }
1873
1969
 
1970
+ // The rendering region currently being constructed. A 'children' slot is built
1971
+ // by the position that shows it and must be destroyed with it, so the slot is
1972
+ // owned by this scope rather than by the caller's component scope, which would
1973
+ // keep every hidden build's observers alive for the component's whole lifetime.
1974
+ let __velarBuildScope = null;
1975
+
1976
+ function __velarChildrenNode(build, scope) {
1977
+ const owner = __velarBuildScope === null ? scope : __velarBuildScope;
1978
+ return __velarInternalRead(() => build(owner));
1979
+ }
1980
+
1874
1981
  function __velarDynamic(parent, read, scope, rootState = null) {
1875
1982
  const start = __velarDomCreateComment("velar:start");
1876
1983
  const end = __velarDomCreateComment("velar:end");
@@ -1882,11 +1989,14 @@ function __velarDynamic(parent, read, scope, rootState = null) {
1882
1989
  const nextScope = __velarScope(scope.component);
1883
1990
  const fragment = __velarDomCreateFragment();
1884
1991
  let nextHost = null;
1992
+ const previousBuildScope = __velarBuildScope;
1993
+ __velarBuildScope = nextScope;
1885
1994
  try {
1886
1995
  __velarAppend(fragment, read(nextScope));
1887
1996
  if (rootState) nextHost = __velarRootHost(fragment, "dynamic component");
1888
1997
  }
1889
1998
  catch (error) { __velarDestroyScope(nextScope); throw error; }
1999
+ finally { __velarBuildScope = previousBuildScope; }
1890
2000
  const nextNodes = __velarDomChildNodes(fragment);
1891
2001
  if (childScope) __velarDestroyScope(childScope);
1892
2002
  for (let index = 0; index < nodes.length; index += 1) __velarDomRemove(nodes[index]);
@@ -1943,9 +2053,25 @@ function __velarKeyed(parent, read, keyOf, render, scope) {
1943
2053
  if (!entry) {
1944
2054
  const childScope = __velarScope(scope.component);
1945
2055
  const fragment = __velarDomCreateFragment();
2056
+ const previousBuildScope = __velarBuildScope;
2057
+ __velarBuildScope = childScope;
1946
2058
  try { __velarAppend(fragment, render(trackedValue, childScope)); }
1947
2059
  catch (error) { __velarDestroyScope(childScope); throw error; }
1948
- entry = { value: rawValue, scope: childScope, nodes: __velarDomChildNodes(fragment), fragment };
2060
+ finally { __velarBuildScope = previousBuildScope; }
2061
+ // A row is held by its first and last top-level nodes, never by a
2062
+ // snapshot of the list between them: every dynamic construct brackets
2063
+ // itself with comments created once, so those two nodes are stable
2064
+ // while everything between them is replaced over time. Caching the
2065
+ // whole list put destroyed nodes back into the document on a later
2066
+ // reorder and stranded the live ones outside their own markers.
2067
+ const rowNodes = __velarDomChildNodes(fragment);
2068
+ entry = {
2069
+ value: rawValue,
2070
+ scope: childScope,
2071
+ first: rowNodes.length > 0 ? rowNodes[0] : null,
2072
+ last: rowNodes.length > 0 ? rowNodes[rowNodes.length - 1] : null,
2073
+ fragment,
2074
+ };
1949
2075
  created[created.length] = entry;
1950
2076
  }
1951
2077
  __velarGraphMapWrite(next, key, entry);
@@ -1958,7 +2084,13 @@ function __velarKeyed(parent, read, keyOf, render, scope) {
1958
2084
  const entry = __velarGraphMapRead(entries, key);
1959
2085
  if (__velarGraphMapRead(next, key) === entry) continue;
1960
2086
  __velarDestroyScope(entry.scope);
1961
- for (let index = 0; index < entry.nodes.length; index += 1) __velarDomRemove(entry.nodes[index]);
2087
+ let node = entry.first;
2088
+ while (node !== null && node !== end) {
2089
+ const following = __velarDomNextSibling(node);
2090
+ __velarDomRemove(node);
2091
+ if (node === entry.last) break;
2092
+ node = following;
2093
+ }
1962
2094
  }
1963
2095
  // A row already standing in its final position must not be detached and
1964
2096
  // reattached: that moves focus off a live <input>, ends IME composition,
@@ -1972,10 +2104,13 @@ function __velarKeyed(parent, read, keyOf, render, scope) {
1972
2104
  if (scope.mounted) __velarMountScope(entry.scope);
1973
2105
  continue;
1974
2106
  }
1975
- for (let index = 0; index < entry.nodes.length; index += 1) {
1976
- const node = entry.nodes[index];
2107
+ let node = entry.first;
2108
+ while (node !== null && node !== end) {
2109
+ const following = __velarDomNextSibling(node);
1977
2110
  if (node === cursor) cursor = __velarDomNextSibling(cursor);
1978
2111
  else __velarDomBefore(cursor === null ? end : cursor, node);
2112
+ if (node === entry.last) break;
2113
+ node = following;
1979
2114
  }
1980
2115
  }
1981
2116
  entries = next;
@@ -2011,6 +2146,55 @@ function __velarAttr(element, name, read, scope) {
2011
2146
  }, "dom", scope);
2012
2147
  }
2013
2148
 
2149
+ // The attributes whose value the user agent navigates or fetches. For these the
2150
+ // scheme is part of what the value means, so it is checked; every other
2151
+ // attribute takes its text unchanged.
2152
+ const __velarUrlAttributes = ["href", "src", "action", "formaction", "poster", "data", "xlink:href", "ping", "cite"];
2153
+ const __velarUrlSchemes = ["http", "https", "mailto", "tel", "blob"];
2154
+ // 'data:' carries its own payload, so it is admitted only for media types the
2155
+ // user agent cannot execute. 'image/svg+xml' is deliberately absent: an SVG
2156
+ // document can carry script.
2157
+ const __velarInertDataTypes = [
2158
+ "image/png", "image/jpeg", "image/gif", "image/webp", "image/avif", "image/bmp", "image/x-icon",
2159
+ "video/mp4", "video/webm", "video/ogg", "audio/mpeg", "audio/ogg", "audio/wav", "audio/webm",
2160
+ "font/woff", "font/woff2", "text/plain",
2161
+ ];
2162
+
2163
+ // 'javascript:' and 'vbscript:' are code, not locations. A value that arrived as
2164
+ // data must never become code because it reached an href, so an unknown scheme
2165
+ // is refused rather than passed through.
2166
+ function __velarUrlAttributeValue(value, name) {
2167
+ let scheme = "";
2168
+ for (let index = 0; index < value.length; index += 1) {
2169
+ const code = value.charCodeAt(index);
2170
+ // The user agent strips ASCII whitespace and control characters before it
2171
+ // parses the scheme, so "java\tscript:" reads as "javascript:" to it and
2172
+ // has to read that way here too.
2173
+ if (code <= 0x20 || code === 0x7f) continue;
2174
+ if (code === 58 && scheme.length > 0) {
2175
+ const lowered = scheme.toLowerCase();
2176
+ if (lowered === "data") {
2177
+ const payload = value.slice(index + 1).toLowerCase();
2178
+ for (let type = 0; type < __velarInertDataTypes.length; type += 1) {
2179
+ if (payload.startsWith(__velarInertDataTypes[type])) return value;
2180
+ }
2181
+ throw new TypeError("JSX attribute '" + name + "' rejected a 'data:' URL whose media type is not a known inert one");
2182
+ }
2183
+ if (__velarHasName(__velarUrlSchemes, lowered)) return value;
2184
+ throw new TypeError("JSX attribute '" + name + "' rejected the '" + lowered + ":' URL scheme");
2185
+ }
2186
+ const letter = (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
2187
+ if (letter || (scheme.length > 0 && ((code >= 48 && code <= 57) || code === 43 || code === 45 || code === 46))) {
2188
+ scheme += value[index];
2189
+ continue;
2190
+ }
2191
+ // Anything else this early means the value names no scheme: it is a
2192
+ // relative URL, which is always the application's own origin.
2193
+ return value;
2194
+ }
2195
+ return value;
2196
+ }
2197
+
2014
2198
  function __velarAttributeValue(value, name) {
2015
2199
  if (value === true) return __velarAriaState(name) ? "true" : "";
2016
2200
  if (typeof value === "number") {
@@ -2019,6 +2203,7 @@ function __velarAttributeValue(value, name) {
2019
2203
  }
2020
2204
  if (typeof value !== "string") throw new TypeError("JSX attribute '" + name + "' requires text, a finite number, bool, an enum, or null");
2021
2205
  if (value.length > 1024 * 1024) throw new RangeError("JSX attribute '" + name + "' cannot exceed 1 MiB");
2206
+ if (__velarHasName(__velarUrlAttributes, name)) return __velarUrlAttributeValue(value, name);
2022
2207
  return value;
2023
2208
  }
2024
2209
 
@@ -2048,23 +2233,86 @@ function __velarClass(element, name, read, scope) {
2048
2233
  __velarClassBind(element, () => read() ? name : null, scope);
2049
2234
  }
2050
2235
 
2051
- function __velarMergeRules(rules, source) {
2236
+ // The property a token carries. A token is '[target:]conditions:property' and
2237
+ // a CSS property name never contains ':', so the last segment is the property.
2238
+ function __velarLookProperty(token) {
2239
+ return token.slice(token.lastIndexOf(":") + 1);
2240
+ }
2241
+
2242
+ // The surface a token writes on: its pseudo-element target, if it has one, plus
2243
+ // its property. Composition overrides a property on the surface that owns it —
2244
+ // '@before: content' and a bare 'content' are two surfaces, not one property
2245
+ // written under two conditions.
2246
+ function __velarLookSurface(token) {
2247
+ const first = token.indexOf(":");
2248
+ const last = token.lastIndexOf(":");
2249
+ return (first === last ? "" : token.slice(0, first + 1)) + token.slice(last + 1);
2250
+ }
2251
+
2252
+ // Whether a token declares its property unconditionally: an unconditional
2253
+ // declaration spells its condition segment 'base', so the segment in front of
2254
+ // the property is what says a token stands on its own.
2255
+ function __velarLookUnconditional(token) {
2256
+ const last = token.lastIndexOf(":");
2257
+ return token.slice(token.lastIndexOf(":", last - 1) + 1, last) === "base";
2258
+ }
2259
+
2260
+ // Composition is property-level across sources and token-level inside one look
2261
+ // block. A later unconditional declaration wins its property outright, so it
2262
+ // drops every condition the earlier sources wrote it under: the caller of a
2263
+ // component could otherwise not reach a padding the component set behind its
2264
+ // own private breakpoint, and 'the caller wins every property both of them set'
2265
+ // was true only under the identical condition (LOK-U10). A later conditional
2266
+ // declaration refines rather than replaces — it overwrites the identical token
2267
+ // and leaves the earlier unconditional value standing, because a caller that
2268
+ // writes only 'if @hover: color' never mentioned the resting colour and must
2269
+ // not delete it. Declarations written in one block are one source, so a block's
2270
+ // own 'if scheme.dark:' and 'if @hover:' still coexist and the cascade decides
2271
+ // between them.
2272
+ function __velarMergeRules(rules, groups, source, group) {
2052
2273
  const names = __velarGraphOwnNames(source);
2274
+ const owned = __velarGraphOwnNames(rules);
2053
2275
  for (let index = 0; index < names.length; index += 1) {
2054
2276
  const descriptor = __velarGraphOwnDescriptor(source, names[index]);
2055
2277
  if (!descriptor || !("value" in descriptor)) continue;
2056
- if (descriptor.value == null) delete rules[names[index]];
2057
- else rules[names[index]] = descriptor.value;
2278
+ if (!__velarLookUnconditional(names[index])) continue;
2279
+ const surface = __velarLookSurface(names[index]);
2280
+ for (let other = 0; other < owned.length; other += 1) {
2281
+ const token = owned[other];
2282
+ if (groups[token] === group || __velarLookSurface(token) !== surface) continue;
2283
+ delete rules[token];
2284
+ delete groups[token];
2285
+ }
2286
+ }
2287
+ for (let index = 0; index < names.length; index += 1) {
2288
+ const descriptor = __velarGraphOwnDescriptor(source, names[index]);
2289
+ if (!descriptor || !("value" in descriptor)) continue;
2290
+ if (descriptor.value == null) { delete rules[names[index]]; delete groups[names[index]]; continue; }
2291
+ rules[names[index]] = descriptor.value;
2292
+ groups[names[index]] = group;
2058
2293
  }
2059
2294
  }
2060
2295
 
2061
2296
  function __velarLook(parts) {
2062
2297
  const rules = __velarGraphCreateRecord();
2298
+ const groups = __velarGraphCreateRecord();
2299
+ // A built Look is one source. A bare rules record is a run of declarations
2300
+ // written in one look block, so consecutive ones share a source and a spread
2301
+ // between them opens the next.
2302
+ let group = 0;
2303
+ let run = 0;
2063
2304
  const add = (part) => {
2064
2305
  if (part == null || part === false) return;
2065
2306
  if (__velarGraphIsList(part)) { for (let index = 0; index < part.length; index += 1) add(part[index]); return; }
2066
- if (part.__velarLook === true || (part.rules && typeof part.rules === "object")) {
2067
- __velarMergeRules(rules, part.rules);
2307
+ if (part.__velarLook === true) {
2308
+ group += 1;
2309
+ run = 0;
2310
+ __velarMergeRules(rules, groups, part.rules, group);
2311
+ return;
2312
+ }
2313
+ if (part.rules && typeof part.rules === "object") {
2314
+ if (run === 0) { group += 1; run = group; }
2315
+ __velarMergeRules(rules, groups, part.rules, run);
2068
2316
  return;
2069
2317
  }
2070
2318
  throw new TypeError("look composition accepts only Look, Look?, or lists of Look values");
@@ -2074,7 +2322,7 @@ function __velarLook(parts) {
2074
2322
  }
2075
2323
 
2076
2324
  function __velarKeyframesValue(name) {
2077
- if (typeof name !== "string" || !/^velar-kf-[0-9a-f]{8}$/.test(name)) throw new TypeError("Generated keyframes name is invalid");
2325
+ if (typeof name !== "string" || !/^velar-kf-[0-9a-f]{8,32}$/.test(name)) throw new TypeError("Generated keyframes name is invalid");
2078
2326
  return __velarGraphFreeze({ __velarKeyframes: true, name });
2079
2327
  }
2080
2328
 
@@ -2090,7 +2338,17 @@ function __velarLookValue(token, value) {
2090
2338
  }
2091
2339
  if (typeof value !== "string") throw new TypeError("Look properties require text, finite numbers, typed visual values, or null");
2092
2340
  if (value.length > 1024 * 1024) throw new RangeError("A Look property value cannot exceed 1 MiB");
2093
- if (token.endsWith(":content") && typeof value === "string" && value !== "none" && value !== "normal") return __velarQuotedText(value);
2341
+ if (token.endsWith(":content") && typeof value === "string" && value !== "none" && value !== "normal") return __velarCssString(value);
2342
+ // A closed keyword set is the property's whole string vocabulary, so a value
2343
+ // the compiler could not read — a prop, a record field, a call result — is
2344
+ // checked here instead. Without this a 'display' bound to "grdi" reached the
2345
+ // browser as a declaration it silently discards (LOK-U14).
2346
+ const keywords = __velarLookKeywords[__velarLookProperty(token)];
2347
+ if (keywords !== undefined && !__velarHasName(keywords, value.trim())) {
2348
+ let expected = "";
2349
+ for (let index = 0; index < keywords.length; index += 1) expected += (expected === "" ? "" : ", ") + keywords[index];
2350
+ throw new TypeError("Look property '" + __velarLookProperty(token) + "' does not accept '" + value + "'; use one of " + expected);
2351
+ }
2094
2352
  return value;
2095
2353
  }
2096
2354
 
@@ -2279,33 +2537,80 @@ function __velarElementState(element, name, create) {
2279
2537
  function __velarApplyLooks(element) {
2280
2538
  const sources = __velarGraphWeakMapRead(__velarRuntime.lookSources, element);
2281
2539
  const merged = __velarGraphCreateRecord();
2540
+ const groups = __velarGraphCreateRecord();
2282
2541
  if (sources) {
2542
+ // Each attached source is one Look, so a source that sets a property
2543
+ // unconditionally drops the property's other conditions from the sources
2544
+ // before it: the caller of a component composes after the component's own
2545
+ // host look and wins every property both of them set, whatever condition
2546
+ // either wrote it under. A source that writes the property only under a
2547
+ // condition refines that condition alone — the identical token is
2548
+ // overwritten and the earlier unconditional value stays, so the caller does
2549
+ // not delete a resting value it never mentioned.
2550
+ //
2283
2551
  // A null rule keeps its token: the token drives the generated selector and
2284
2552
  // only the custom property behind it disappears.
2553
+ let group = 0;
2285
2554
  for (const source of __velarGraphSetItems(sources)) {
2555
+ group += 1;
2286
2556
  const names = __velarGraphOwnNames(source.rules);
2557
+ const owned = __velarGraphOwnNames(merged);
2558
+ for (let index = 0; index < names.length; index += 1) {
2559
+ // A rule the assignment pass below refuses to read must not clear the
2560
+ // earlier sources either, or the property disappears with nothing put
2561
+ // in its place — the same 'a source deletes a value it never replaces'
2562
+ // shape the conditional guard above closes. '__velarMergeRules' spells
2563
+ // the test the same way.
2564
+ const descriptor = __velarGraphOwnDescriptor(source.rules, names[index]);
2565
+ if (!descriptor || !("value" in descriptor)) continue;
2566
+ if (!__velarLookUnconditional(names[index])) continue;
2567
+ const surface = __velarLookSurface(names[index]);
2568
+ for (let other = 0; other < owned.length; other += 1) {
2569
+ if (groups[owned[other]] === group || __velarLookSurface(owned[other]) !== surface) continue;
2570
+ delete merged[owned[other]];
2571
+ delete groups[owned[other]];
2572
+ }
2573
+ }
2287
2574
  for (let index = 0; index < names.length; index += 1) {
2288
2575
  const descriptor = __velarGraphOwnDescriptor(source.rules, names[index]);
2289
- if (descriptor && "value" in descriptor) merged[names[index]] = descriptor.value;
2576
+ if (!descriptor || !("value" in descriptor)) continue;
2577
+ merged[names[index]] = descriptor.value;
2578
+ groups[names[index]] = group;
2290
2579
  }
2291
2580
  }
2292
2581
  }
2293
- const state = __velarElementState(element, "__velarLookTokens", () => __velarGraphFreeze({ tokens: __velarGraphCreateSet() }));
2582
+ // The last value written per token, so a reactive change to one dynamic
2583
+ // property costs one DOM write instead of one per token plus a full attribute
2584
+ // rewrite (LOK-U15).
2585
+ const state = __velarElementState(element, "__velarLookTokens", () => __velarGraphFreeze({
2586
+ tokens: __velarGraphCreateSet(), written: { record: __velarGraphCreateRecord(), attribute: undefined },
2587
+ }));
2588
+ const written = state.written.record;
2294
2589
  const tokens = __velarGraphOwnNames(merged);
2295
2590
  const next = __velarGraphCreateSet(tokens);
2296
2591
  for (const token of __velarGraphSetItems(state.tokens)) {
2297
- if (!__velarGraphSetContains(next, token)) __velarDomStyleClear(element, __velarLookVariable(token));
2592
+ if (__velarGraphSetContains(next, token)) continue;
2593
+ __velarDomStyleClear(element, __velarLookVariable(token));
2594
+ delete written[token];
2298
2595
  }
2299
2596
  let attribute = "";
2300
2597
  for (let index = 0; index < tokens.length; index += 1) {
2301
2598
  const token = tokens[index];
2302
2599
  const value = __velarGraphOwnDescriptor(merged, token)?.value;
2303
- if (value == null) __velarDomStyleClear(element, __velarLookVariable(token));
2304
- else __velarDomStyleWrite(element, __velarLookVariable(token), __velarLookValue(token, value));
2600
+ const text = value == null ? null : __velarLookValue(token, value);
2601
+ if (!(token in written) || written[token] !== text) {
2602
+ if (text === null) __velarDomStyleClear(element, __velarLookVariable(token));
2603
+ else __velarDomStyleWrite(element, __velarLookVariable(token), text);
2604
+ written[token] = text;
2605
+ }
2305
2606
  attribute = attribute === "" ? token : attribute + " " + token;
2306
2607
  }
2307
- if (tokens.length > 0) __velarDomSetAttribute(element, "data-velar-look", attribute);
2308
- else __velarDomRemoveAttribute(element, "data-velar-look");
2608
+ const desired = tokens.length === 0 ? null : attribute;
2609
+ if (state.written.attribute !== desired) {
2610
+ if (desired === null) __velarDomRemoveAttribute(element, "data-velar-look");
2611
+ else __velarDomSetAttribute(element, "data-velar-look", desired);
2612
+ state.written.attribute = desired;
2613
+ }
2309
2614
  __velarGraphSetEmpty(state.tokens);
2310
2615
  for (let index = 0; index < tokens.length; index += 1) __velarGraphSetInsert(state.tokens, tokens[index]);
2311
2616
  }
@@ -2346,27 +2651,72 @@ function __velarApplyExternalLook(element, value) {
2346
2651
 
2347
2652
  __velarRuntime.installLook(__velarApplyExternalLook);
2348
2653
 
2349
- function __velarRootHost(root, capability) {
2350
- if (root == null) throw new TypeError("A component with multiple roots must mark exactly one native element with 'host'");
2351
- if (__velarDomNodeType(root) === 1) return root;
2654
+ // One resolution per root, shared by every capability bound to it: look, class
2655
+ // and style used to pay a fresh O(subtree) walk each.
2656
+ const __velarRootHosts = __velarGraphCreateWeakMap();
2657
+
2658
+ function __velarRootHostResolve(root) {
2352
2659
  const elements = [];
2353
2660
  const children = __velarDomChildNodes(root);
2354
2661
  for (let index = 0; index < children.length; index += 1) {
2355
2662
  if (__velarDomNodeType(children[index]) === 1) __velarAppendOwned(elements, children[index]);
2356
2663
  }
2357
2664
  const explicit = [];
2665
+ const forwarded = [];
2358
2666
  for (let index = 0; index < elements.length; index += 1) {
2359
2667
  const element = elements[index];
2360
- if (__velarDomOwnData(element, "__velarHost") === true) __velarAppendOwned(explicit, element);
2361
- const descendants = __velarDomCollectionSnapshot(__velarDomQuerySelectorAll(element, "*"), "Element.querySelectorAll");
2362
- for (let child = 0; child < descendants.length; child += 1) {
2363
- if (__velarDomOwnData(descendants[child], "__velarHost") === true) __velarAppendOwned(explicit, descendants[child]);
2668
+ // A nested component's own, perfectly legal host is that component's, not
2669
+ // this one's, at the root level exactly as below it. Every root-level node
2670
+ // of a nested component carries the marker and this component's own nodes
2671
+ // do not yet construction marks them after a caller's look binds — so
2672
+ // the marker is what separates the two. Reading 'host' first counted a
2673
+ // nested component's marked root as a second host of the enclosing one and
2674
+ // collapsed the whole region, which is the buried-host defect standing at
2675
+ // the root level instead of below it. Such a node is not this component's
2676
+ // host but the host this component forwards to when it declares none of
2677
+ // its own, so it is collected apart rather than dropped.
2678
+ if (__velarDomOwnData(element, "__velarComponentNode") === true) {
2679
+ if (__velarDomOwnData(element, "__velarHost") === true) __velarAppendOwned(forwarded, element);
2680
+ continue;
2681
+ }
2682
+ if (__velarDomOwnData(element, "__velarHost") === true) { __velarAppendOwned(explicit, element); continue; }
2683
+ // A walk that refuses to enter a marked node stops at a nested component's
2684
+ // boundary whatever depth its host sits at; a flat 'querySelectorAll' scan
2685
+ // could only skip the marked node itself and still counted a host one level
2686
+ // below it.
2687
+ const pending = [element];
2688
+ for (let cursor = 0; cursor < pending.length; cursor += 1) {
2689
+ const nodes = __velarDomChildNodes(pending[cursor]);
2690
+ for (let child = 0; child < nodes.length; child += 1) {
2691
+ const node = nodes[child];
2692
+ if (__velarDomNodeType(node) !== 1) continue;
2693
+ if (__velarDomOwnData(node, "__velarComponentNode") === true) continue;
2694
+ if (__velarDomOwnData(node, "__velarHost") === true) __velarAppendOwned(explicit, node);
2695
+ __velarAppendOwned(pending, node);
2696
+ }
2364
2697
  }
2365
2698
  }
2366
2699
  if (explicit.length === 1) return explicit[0];
2367
- if (explicit.length > 1) throw new TypeError("A component can declare only one host element");
2700
+ if (explicit.length > 1) return "A component can declare only one host element";
2701
+ // Nothing of this component's own is marked, so a single nested component
2702
+ // root hands its host on. Two of them leave nothing to forward to, and the
2703
+ // component has to mark a native element of its own to settle it.
2704
+ if (forwarded.length === 1) return forwarded[0];
2705
+ if (forwarded.length > 1) return "A component with multiple roots must mark exactly one native element with 'host'";
2368
2706
  if (elements.length === 1) return elements[0];
2369
- throw new TypeError("A component with multiple roots must mark exactly one native element with 'host'");
2707
+ return "A component with multiple roots must mark exactly one native element with 'host'";
2708
+ }
2709
+
2710
+ function __velarRootHost(root, capability) {
2711
+ if (root == null) throw new TypeError("A component with multiple roots must mark exactly one native element with 'host'");
2712
+ if (__velarDomNodeType(root) === 1) return root;
2713
+ let resolved = __velarGraphWeakMapRead(__velarRootHosts, root);
2714
+ if (resolved === undefined) {
2715
+ resolved = __velarRootHostResolve(root);
2716
+ __velarGraphWeakMapWrite(__velarRootHosts, root, resolved);
2717
+ }
2718
+ if (typeof resolved === "string") throw new TypeError(resolved);
2719
+ return resolved;
2370
2720
  }
2371
2721
 
2372
2722
  function __velarLookBindRoot(root, read, scope) {
@@ -2494,7 +2844,13 @@ function __velarHtml(element, read, scope) {
2494
2844
  function __velarOn(element, event, read, scope, modifiers = []) {
2495
2845
  if (typeof __velarInternalRead(read) !== "function") throw new TypeError("Event '" + event + "' requires a function");
2496
2846
  const capture = __velarHasName(modifiers, "capture");
2497
- const options = { capture, once: __velarHasName(modifiers, "once") };
2847
+ const once = __velarHasName(modifiers, "once");
2848
+ let removed = false;
2849
+ const remove = () => {
2850
+ if (removed) return;
2851
+ removed = true;
2852
+ __velarDomRemoveListener(element, event, listener, capture);
2853
+ };
2498
2854
  const listener = (value) => {
2499
2855
  try {
2500
2856
  if (__velarHasName(modifiers, "self")) {
@@ -2502,6 +2858,13 @@ function __velarOn(element, event, read, scope, modifiers = []) {
2502
2858
  if (target === __velarEventMissingField) throw new TypeError("DOM event does not expose a native target");
2503
2859
  if (target !== element) return;
2504
2860
  }
2861
+ // 'once' is spent here rather than through the native option, so the
2862
+ // documented order -- self, then prevent, then stop, then the handler --
2863
+ // holds for it too. The native option removes the registration on the
2864
+ // first dispatch that merely reaches the element, which for 'self' is
2865
+ // exactly the dispatch the handler must not see: 'on:click.self.once'
2866
+ // could then never run at all.
2867
+ if (once) remove();
2505
2868
  if (__velarHasName(modifiers, "prevent")) __velarEventCall(value, "preventDefault", __velarEventPreventDefault);
2506
2869
  if (__velarHasName(modifiers, "stop")) __velarEventCall(value, "stopPropagation", __velarEventStopPropagation);
2507
2870
  // The handler expression is re-read per dispatch so handlers routed
@@ -2512,8 +2875,8 @@ function __velarOn(element, event, read, scope, modifiers = []) {
2512
2875
  __velarObservePromise(result, (error) => __velarReportEvent(error, scope, event));
2513
2876
  } catch (error) { __velarReportEvent(error, scope, event); }
2514
2877
  };
2515
- __velarDomAddListener(element, event, listener, options);
2516
- __velarAppendOwned(scope.cleanups, () => __velarDomRemoveListener(element, event, listener, capture));
2878
+ __velarDomAddListener(element, event, listener, { capture });
2879
+ __velarAppendOwned(scope.cleanups, remove);
2517
2880
  }
2518
2881
 
2519
2882
  function __velarBindValue(element, state, scope, numeric = false, parse = null) {
@@ -2589,8 +2952,17 @@ function __velarBindChecked(element, state, scope) {
2589
2952
  // Prop handles give a component body live reads over its props store. The
2590
2953
  // component function still runs exactly once per instance; only reads race
2591
2954
  // ahead, so state initializers can never re-run on a prop update.
2955
+ function __velarPropProvided(props, name) {
2956
+ // A children slot is rendered content rather than a value thunk. Asking for
2957
+ // its value to decide whether the required slot exists would build the slot
2958
+ // once here and again at the JSX position that owns it. Its own property is
2959
+ // the presence proof; the first value read remains the one rendering read.
2960
+ if (name === "children") return __velarGraphOwnDescriptor(props, name) !== undefined;
2961
+ return __velarInternalRead(() => props[name]) !== undefined;
2962
+ }
2963
+
2592
2964
  function __velarRequiredProp(props, name, component) {
2593
- if (__velarInternalRead(() => props[name]) === undefined) throw new TypeError("Component " + component + " requires prop " + name);
2965
+ if (!__velarPropProvided(props, name)) throw new TypeError("Component " + component + " requires prop " + name);
2594
2966
  return __velarGraphFreeze({
2595
2967
  get() {
2596
2968
  const value = props[name];
@@ -2601,7 +2973,7 @@ function __velarRequiredProp(props, name, component) {
2601
2973
  }
2602
2974
 
2603
2975
  function __velarProp(props, name, fallback) {
2604
- const fallbackValue = __velarInternalRead(() => props[name]) === undefined ? fallback() : undefined;
2976
+ const fallbackValue = __velarPropProvided(props, name) ? undefined : fallback();
2605
2977
  return __velarGraphFreeze({
2606
2978
  get() {
2607
2979
  const value = props[name];
@@ -2610,10 +2982,6 @@ function __velarProp(props, name, fallback) {
2610
2982
  });
2611
2983
  }
2612
2984
 
2613
- // Instantiates a component with a live props store: each prop thunk runs in
2614
- // its own observer that writes a reactive cell, and the props object exposes
2615
- // tracked getters over those cells. The component call itself is untracked so
2616
- // construction can never subscribe an enclosing dynamic region to prop reads.
2617
2985
  function __velarBindComponentRef(instance, setRef) {
2618
2986
  if (setRef === undefined) return instance;
2619
2987
  if (!instance || typeof instance.__bindRef !== "function") throw new TypeError("This component does not expose a Handle");
@@ -2621,32 +2989,74 @@ function __velarBindComponentRef(instance, setRef) {
2621
2989
  return instance;
2622
2990
  }
2623
2991
 
2992
+ // Holds every prop's cache open for the length of one construction. The graph
2993
+ // records a dependency for whichever observer is running, so forcing the props
2994
+ // under this one keeps them clean while the component function runs: a read
2995
+ // during construction is served from the value the force produced, and a
2996
+ // tracked read still subscribes the observer that made it. Releasing the hold
2997
+ // hands each prop over to whatever actually observes it, which is the second
2998
+ // half of the rule -- recomputed on demand, cached while observed.
2999
+ function __velarConstructionHold() {
3000
+ return {
3001
+ mode: "computed",
3002
+ stopped: false,
3003
+ running: false,
3004
+ selfInvalidations: 0,
3005
+ dependencies: __velarGraphCreateSet(),
3006
+ spareDependencies: null,
3007
+ notify() {},
3008
+ run() {},
3009
+ };
3010
+ }
3011
+
3012
+ // Instantiates a component with a live props store: each prop is a cached
3013
+ // derived value the props object exposes as a tracked getter, so a prop
3014
+ // expression that nothing reads costs nothing after construction. The component
3015
+ // call itself is untracked so construction can never subscribe an enclosing
3016
+ // dynamic region to prop reads.
2624
3017
  function __velarInstantiate(component, thunks, children, scope, namespace, setRef) {
2625
- if (component != null && component.__velarSnapshotProps === true) {
2626
- // Runtime-implemented components (Head, Router, Link, NavLink) consume a
2627
- // one-time plain snapshot so their strict record validation still holds.
2628
- const snapshot = {};
2629
- const styleRead = thunks.__velarStyle;
2630
- const snapshotNames = __velarGraphOwnNames(thunks);
2631
- for (let index = 0; index < snapshotNames.length; index += 1) {
2632
- if (snapshotNames[index] !== "__velarStyle") snapshot[snapshotNames[index]] = __velarInternalRead(thunks[snapshotNames[index]]);
2633
- }
2634
- if (children !== undefined) snapshot.children = __velarInternalRead(children);
2635
- const instance = __velarUntracked(() => component(snapshot, namespace));
2636
- if (styleRead !== undefined) __velarStyleBindRoot(instance.node, styleRead, scope);
2637
- return __velarBindComponentRef(instance, setRef);
2638
- }
2639
3018
  const props = {};
3019
+ // 'style:' on a component host is the caller decorating the instance's root,
3020
+ // the way it decorates a native element. The slot the compiler inserts for it
3021
+ // is not a field the component declares, so it is bound here rather than
3022
+ // handed inward -- a component the runtime implements validates its props
3023
+ // against the fields it does declare and would refuse an unknown one.
3024
+ const styleRead = thunks.__velarStyle;
2640
3025
  const propNames = __velarGraphOwnNames(thunks);
3026
+ const accesses = [];
2641
3027
  for (let index = 0; index < propNames.length; index += 1) {
2642
3028
  const name = propNames[index];
2643
- const read = thunks[name];
2644
- const cell = __velarState(undefined);
2645
- __velarObserver(() => cell.set(read()), "dom", scope);
2646
- __velarGraphDefine(props, name, { enumerable: true, get: () => cell.get() });
3029
+ if (name === "__velarStyle") continue;
3030
+ // A prop is a derived value, not an eagerly pushed one: an unconditional
3031
+ // observer per prop re-ran every prop expression on every dependency
3032
+ // change, including the expensive ones behind props the component never
3033
+ // reads. The cache is the same one a declared derived value uses.
3034
+ const access = __velarComputed(thunks[name]);
3035
+ accesses[accesses.length] = access;
3036
+ __velarGraphDefine(props, name, { enumerable: true, get: () => access.get() });
2647
3037
  }
2648
- if (children !== undefined) __velarGraphDefine(props, "children", { enumerable: true, value: __velarInternalRead(children) });
2649
- return __velarBindComponentRef(__velarUntracked(() => component(props, namespace)), setRef);
3038
+ // 'children' follows Vel's ordinary rules: it is rendered content owned by
3039
+ // the position that shows it, rebuilt whenever that position renders again.
3040
+ // As a one-shot fragment value it could be shown exactly once, and the first
3041
+ // time a conditional position hid it the content was destroyed for good.
3042
+ if (children !== undefined) __velarGraphDefine(props, "children", { enumerable: true, get: () => __velarChildrenNode(children, scope) });
3043
+ const instance = __velarUntracked(() => {
3044
+ if (accesses.length === 0) return component(props, namespace);
3045
+ // Component JSX evaluates the way the charter says a JavaScript object
3046
+ // literal does: every prop expression runs once, in the order the caller
3047
+ // wrote it, and only then does the component function run. 'children' is
3048
+ // not one of them -- it is rendered content owned by the position that
3049
+ // shows it, so a position that never shows it never builds it.
3050
+ const hold = __velarConstructionHold();
3051
+ try {
3052
+ __velarRuntime.runTracked(hold, () => {
3053
+ for (let index = 0; index < accesses.length; index += 1) accesses[index].get();
3054
+ });
3055
+ return component(props, namespace);
3056
+ } finally { __velarCleanupObserver(hold); }
3057
+ });
3058
+ if (styleRead !== undefined) __velarStyleBindRoot(instance.node, styleRead, scope);
3059
+ return __velarBindComponentRef(instance, setRef);
2650
3060
  }
2651
3061
 
2652
3062
  // A component element in child position: one stable instance whose prop
@@ -2668,10 +3078,26 @@ function __velarChild(component, thunks, children, scope, namespace, setRef) {
2668
3078
  }
2669
3079
  }
2670
3080
 
2671
- function __velarSettled() {
3081
+ // tick() promises the settled flush, so it drains the reactive queues instead
3082
+ // of hopping one microtask and hoping. Each round runs the pending flush and
3083
+ // yields, so work an observer queued asynchronously is picked up too; the round
3084
+ // count is bounded for the same reason the flush budget is, and the flush's own
3085
+ // budget is what ends a runaway cycle.
3086
+ function __velarSettledStep() {
2672
3087
  return __velarManagedAsyncCreate((resolve) => __velarEnqueue(resolve));
2673
3088
  }
2674
3089
 
3090
+ function __velarSettled(rounds = 10000) {
3091
+ return __velarManagedAsyncThen(__velarSettledStep(), () => {
3092
+ if (__velarRuntime.flushPending) __velarFlush();
3093
+ if (rounds > 1 && (__velarGraphSetCount(__velarRuntime.domQueue)
3094
+ || __velarGraphSetCount(__velarRuntime.watchQueue) || __velarRuntime.flushPending)) {
3095
+ return __velarSettled(rounds - 1);
3096
+ }
3097
+ return null;
3098
+ });
3099
+ }
3100
+
2675
3101
  function __velarTakeUnhandledFailure() {
2676
3102
  for (const failure of __velarGraphSetItems(__velarRuntime.unhandledFailures)) {
2677
3103
  __velarGraphSetRemove(__velarRuntime.unhandledFailures, failure);
@@ -2693,7 +3119,10 @@ function __velarTick() {
2693
3119
  });
2694
3120
  }
2695
3121
  `.trim();
2696
- function webRuntime(foundation) {
2697
- return `${foundation}\n${WEB_RUNTIME_BODY}`;
3122
+ function webRuntime(foundation, lookKeywords) {
3123
+ // `content` is written as a CSS string, which is not a JSON string, so the
3124
+ // runtime carries the serializer css-string.ts publishes rather than a second
3125
+ // spelling of it (LOK-U13).
3126
+ return `${foundation}\n${CSS_STRING_RUNTIME.trim()}\n${lookKeywords}\n${WEB_RUNTIME_BODY}`;
2698
3127
  }
2699
3128
  //# sourceMappingURL=emitter.js.map