@nisli/core 0.56.0 → 0.57.1

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 (114) hide show
  1. package/README.md +70 -9
  2. package/dist/bundler/component.js +409 -0
  3. package/dist/bundler/component.js.map +7 -0
  4. package/dist/bundler/context.js +27 -0
  5. package/dist/bundler/context.js.map +7 -0
  6. package/dist/bundler/diagnostics.js +55 -0
  7. package/dist/bundler/diagnostics.js.map +7 -0
  8. package/dist/bundler/disposable.js +9 -0
  9. package/dist/bundler/disposable.js.map +7 -0
  10. package/dist/bundler/element-context.js +47 -0
  11. package/dist/bundler/element-context.js.map +7 -0
  12. package/dist/bundler/emitter.js +70 -0
  13. package/dist/bundler/emitter.js.map +7 -0
  14. package/dist/bundler/hmr/registry.js +72 -0
  15. package/dist/bundler/hmr/registry.js.map +7 -0
  16. package/dist/bundler/hmr/transform.js +30 -0
  17. package/dist/bundler/hmr/transform.js.map +7 -0
  18. package/dist/bundler/index.js +103 -0
  19. package/dist/bundler/index.js.map +7 -0
  20. package/dist/bundler/injector.js +75 -0
  21. package/dist/bundler/injector.js.map +7 -0
  22. package/dist/bundler/lifecycle.js +56 -0
  23. package/dist/bundler/lifecycle.js.map +7 -0
  24. package/dist/bundler/projection.js +92 -0
  25. package/dist/bundler/projection.js.map +7 -0
  26. package/dist/bundler/query.js +302 -0
  27. package/dist/bundler/query.js.map +7 -0
  28. package/dist/bundler/ref.js +16 -0
  29. package/dist/bundler/ref.js.map +7 -0
  30. package/dist/bundler/resource.js +99 -0
  31. package/dist/bundler/resource.js.map +7 -0
  32. package/dist/bundler/settle.js +43 -0
  33. package/dist/bundler/settle.js.map +7 -0
  34. package/dist/bundler/signal.js +403 -0
  35. package/dist/bundler/signal.js.map +7 -0
  36. package/dist/bundler/template-audit.js +162 -0
  37. package/dist/bundler/template-audit.js.map +7 -0
  38. package/dist/bundler/template.js +922 -0
  39. package/dist/bundler/template.js.map +7 -0
  40. package/dist/bundler/view-transition.js +20 -0
  41. package/dist/bundler/view-transition.js.map +7 -0
  42. package/dist/bundler/vite-hmr/plugin.js +32 -0
  43. package/dist/bundler/vite-hmr/plugin.js.map +7 -0
  44. package/dist/bundler/vite-hmr/runtime.js +8 -0
  45. package/dist/bundler/vite-hmr/runtime.js.map +7 -0
  46. package/dist/index.js +2682 -47
  47. package/dist/index.js.map +7 -1
  48. package/dist/index.production.js +2 -0
  49. package/dist/index.production.js.map +6 -0
  50. package/dist/{diagnostics.d.ts → types/diagnostics.d.ts} +8 -15
  51. package/dist/{template-audit.d.ts → types/template-audit.d.ts} +4 -5
  52. package/dist/{template.d.ts → types/template.d.ts} +2 -1
  53. package/package.json +15 -10
  54. package/dist/component.js +0 -633
  55. package/dist/component.js.map +0 -1
  56. package/dist/context.js +0 -55
  57. package/dist/context.js.map +0 -1
  58. package/dist/diagnostics.js +0 -118
  59. package/dist/diagnostics.js.map +0 -1
  60. package/dist/disposable.js +0 -8
  61. package/dist/disposable.js.map +0 -1
  62. package/dist/element-context.js +0 -108
  63. package/dist/element-context.js.map +0 -1
  64. package/dist/emitter.js +0 -91
  65. package/dist/emitter.js.map +0 -1
  66. package/dist/hmr/registry.js +0 -136
  67. package/dist/hmr/registry.js.map +0 -1
  68. package/dist/hmr/transform.js +0 -52
  69. package/dist/hmr/transform.js.map +0 -1
  70. package/dist/injector.js +0 -118
  71. package/dist/injector.js.map +0 -1
  72. package/dist/lifecycle.js +0 -117
  73. package/dist/lifecycle.js.map +0 -1
  74. package/dist/projection.js +0 -179
  75. package/dist/projection.js.map +0 -1
  76. package/dist/query.js +0 -429
  77. package/dist/query.js.map +0 -1
  78. package/dist/ref.js +0 -43
  79. package/dist/ref.js.map +0 -1
  80. package/dist/resource.js +0 -129
  81. package/dist/resource.js.map +0 -1
  82. package/dist/settle.js +0 -106
  83. package/dist/settle.js.map +0 -1
  84. package/dist/signal.js +0 -645
  85. package/dist/signal.js.map +0 -1
  86. package/dist/template-audit.js +0 -246
  87. package/dist/template-audit.js.map +0 -1
  88. package/dist/template.js +0 -1244
  89. package/dist/template.js.map +0 -1
  90. package/dist/view-transition.js +0 -98
  91. package/dist/view-transition.js.map +0 -1
  92. package/dist/vite-hmr/plugin.js +0 -62
  93. package/dist/vite-hmr/plugin.js.map +0 -1
  94. package/dist/vite-hmr/runtime.js +0 -17
  95. package/dist/vite-hmr/runtime.js.map +0 -1
  96. /package/dist/{component.d.ts → types/component.d.ts} +0 -0
  97. /package/dist/{context.d.ts → types/context.d.ts} +0 -0
  98. /package/dist/{disposable.d.ts → types/disposable.d.ts} +0 -0
  99. /package/dist/{element-context.d.ts → types/element-context.d.ts} +0 -0
  100. /package/dist/{emitter.d.ts → types/emitter.d.ts} +0 -0
  101. /package/dist/{hmr → types/hmr}/registry.d.ts +0 -0
  102. /package/dist/{hmr → types/hmr}/transform.d.ts +0 -0
  103. /package/dist/{index.d.ts → types/index.d.ts} +0 -0
  104. /package/dist/{injector.d.ts → types/injector.d.ts} +0 -0
  105. /package/dist/{lifecycle.d.ts → types/lifecycle.d.ts} +0 -0
  106. /package/dist/{projection.d.ts → types/projection.d.ts} +0 -0
  107. /package/dist/{query.d.ts → types/query.d.ts} +0 -0
  108. /package/dist/{ref.d.ts → types/ref.d.ts} +0 -0
  109. /package/dist/{resource.d.ts → types/resource.d.ts} +0 -0
  110. /package/dist/{settle.d.ts → types/settle.d.ts} +0 -0
  111. /package/dist/{signal.d.ts → types/signal.d.ts} +0 -0
  112. /package/dist/{view-transition.d.ts → types/view-transition.d.ts} +0 -0
  113. /package/dist/{vite-hmr → types/vite-hmr}/plugin.d.ts +0 -0
  114. /package/dist/{vite-hmr → types/vite-hmr}/runtime.d.ts +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/signal.ts"],
4
+ "sourcesContent": ["/**\n * signal.ts \u2014 Reactive primitives: signal(), computed(), effect()\n *\n * Push-pull hybrid model:\n * - Writes push dirty flags up the dependency graph in two flavors: a signal\n * write pushes Dirty (the value definitely changed), a computed propagates\n * MaybeDirty (recompute to find out). Flags never downgrade.\n * - Reads pull fresh values lazily (computed only recalculates when read)\n * - Multiple synchronous writes coalesce into one microtask flush\n *\n * Scheduler invariants (ADR 0030.2 T5):\n * - An effect re-runs iff a value it read changed. Before a MaybeDirty\n * effect runs, its computed sources are polled (recomputed via the\n * node\u2192impl back-pointer); if no source's change-epoch advanced past the\n * effect's last completed run, the run is skipped. A computed's\n * change-epoch also bumps on every error-state TRANSITION (enter and\n * exit), so effects never skip across error entry or equal-value recovery\n * (issue 0003 containment happens at the effect's own read).\n * - Pending effects flush in creation order (monotonic index), so identical\n * writes produce identical run order. Documented cost: a later-created\n * writer effect costs one deterministic extra run vs. topological order.\n * - The loop guard is clock-free: an effect that re-schedules ITSELF on\n * MAX_EFFECT_RERUNS consecutive runs is diagnosed (N301) and disposed.\n *\n * Dependency tracking is automatic: reading a signal inside a computed\n * or effect registers it as a dependency. Dependencies are re-tracked\n * on every execution (dynamic/conditional tracking).\n */\n\n// \u2500\u2500 Late-bound context hook for auto-disposal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Avoids circular dependency: context.ts imports nothing from signal.ts,\n// but signal.ts needs to check for component context to auto-dispose effects.\n// component.ts wires this up at import time via `setContextHook()`.\n\nlet contextHook: (() => ((fn: () => void) => void) | null) | null = null;\n\n/**\n * Register the context hook. Called by component.ts at module init.\n * The hook returns an `addDisposer` function if inside setup context, null otherwise.\n */\nexport function setContextHook(hook: () => ((fn: () => void) => void) | null): void {\n contextHook = hook;\n}\n\n// \u2500\u2500 Brand symbol for signal detection in templates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const SIGNAL_BRAND = Symbol.for('backlog.signal');\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Readable reactive container. */\nexport interface ReadonlySignal<T> {\n readonly [SIGNAL_BRAND]: true;\n readonly value: T;\n /** Subscribe to value changes. Returns unsubscribe function. */\n subscribe(fn: (value: T) => void): Disposer;\n /**\n * Read the current value WITHOUT registering a dependency.\n * Equivalent to reading inside `untrack()`. On a computed this still\n * pulls a fresh value (and rethrows a cached error) \u2014 it only skips\n * dependency registration.\n */\n peek(): T;\n}\n\n/** Read-write reactive container. */\nexport interface Signal<T> extends ReadonlySignal<T> {\n value: T;\n}\n\n// \u2500\u2500 Internal tracking state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * The currently executing reactive context (computed or effect).\n * When non-null, any signal read during execution is recorded as a dependency.\n */\nlet activeObserver: ReactiveNode | null = null;\n\n/**\n * The effect whose fn frame is currently executing. Unlike activeObserver\n * (which switches to a computed during its recompute, and to null under\n * untrack()), this stays pinned to the running effect \u2014 it exists solely to\n * attribute writes to the effect that performed them (N302).\n */\nlet runningEffect: EffectNode | null = null;\n\n/**\n * Global epoch counter. Incremented on every observable change: a signal\n * write, a computed recomputing to a different value, and a computed's\n * error-state transitions (enter and exit). A node's `lastChanged` epoch is\n * compared against an observer's last-validated/last-run epoch to decide\n * staleness.\n */\nlet globalEpoch = 0;\n\n/** Monotonic effect creation index \u2014 pending effects flush in this order. */\nlet nextEffectOrder = 0;\n\n// \u2500\u2500 Dev diagnostics \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Reporting goes through the diagnostics leaf (its dev gate governs);\n// guard BEHAVIOR (loop disposal, rejection containment) is never gated \u2014\n// only the reporting. Assigned codes: N301 effect loop, N302\n// write-in-own-effect, N303 flush/tick cap, N310 effect(async).\n\nimport { diag as emitDiag, isDev } from './diagnostics.js';\nimport { disposable, type Disposer } from './disposable.js';\n\nexport type { Disposer } from './disposable.js';\n\nfunction diag(code: string, message: string, detail?: object): void {\n if (!isDev()) return;\n emitDiag(code, message, detail as Record<string, unknown> | undefined);\n}\n\n// \u2500\u2500 Effect scheduling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst pendingEffects = new Set<EffectNode>();\nlet flushScheduled = false;\n\nfunction scheduleFlush(): void {\n if (!flushScheduled) {\n flushScheduled = true;\n queueMicrotask(flushPendingEffects);\n }\n}\n\nfunction flushPendingEffects(): void {\n flushScheduled = false;\n // Copy to avoid mutation during iteration; sort by creation index so\n // identical writes produce identical run order forever (ADR 0030.2 T5).\n const effects = [...pendingEffects].sort((a, b) => a.order - b.order);\n pendingEffects.clear();\n for (const effect of effects) {\n if (!effect.disposed) {\n runEffect(effect);\n }\n }\n}\n\n// \u2500\u2500 Dependency graph node types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst enum NodeState {\n Clean = 0,\n MaybeDirty = 1, // A computed source may have changed \u2014 poll before acting\n Dirty = 2, // A signal source definitely changed \u2014 must recompute/run\n}\n\n/** Notification flavor: Dirty from signal writes, MaybeDirty through computeds. */\ntype DirtyFlavor = NodeState.MaybeDirty | NodeState.Dirty;\n\nconst NO_COMPUTED_ERROR = Symbol('no-computed-error');\n\ninterface ReactiveNode {\n state: NodeState;\n /** Signals/computeds this node reads from */\n sources: Set<SignalNode<unknown> | ComputedNode<unknown>>;\n /** Called when a source changes. Never downgrades an existing dirtier state. */\n notify(flavor: DirtyFlavor): void;\n}\n\n// \u2500\u2500 Signal (writable) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface SignalNode<T> {\n value: T;\n lastChanged: number;\n observers: Set<ReactiveNode>;\n}\n\nclass SignalImpl<T> implements Signal<T> {\n readonly [SIGNAL_BRAND] = true as const;\n /** @internal */\n _node: SignalNode<T>;\n\n constructor(initialValue: T) {\n this._node = {\n value: initialValue,\n lastChanged: globalEpoch,\n observers: new Set(),\n };\n }\n\n get value(): T {\n // Track dependency if inside a reactive context\n if (activeObserver) {\n activeObserver.sources.add(this._node);\n this._node.observers.add(activeObserver);\n }\n return this._node.value;\n }\n\n set value(newValue: T) {\n if (Object.is(this._node.value, newValue)) return;\n // N302: write-in-own-sources \u2014 the running effect wrote a signal it also\n // reads this run, so this write re-triggers it. Attribution-only (never\n // disposes; the loop guard owns disposal). Placed AFTER the Object.is\n // cutoff so certified converging writers \u2014 registry reflect effects,\n // each() item-signal reuse \u2014 never warn on equal-value writes.\n if (\n process.env.NODE_ENV !== 'production' && isDev() &&\n runningEffect !== null &&\n runningEffect.sources.has(this._node as SignalNode<unknown>)\n ) {\n diag(\n 'N302',\n 'Effect wrote to a signal it also reads \u2014 the write re-schedules the ' +\n 'effect. If this does not converge to an equal value it is an ' +\n 'infinite loop. Derive the value with computed() instead.',\n runningEffect.createdAt ? { effect: runningEffect.createdAt } : undefined,\n );\n }\n this._node.value = newValue;\n this._node.lastChanged = ++globalEpoch;\n notifyObservers(this._node.observers, NodeState.Dirty);\n }\n\n peek(): T {\n return this._node.value;\n }\n\n subscribe(fn: (value: T) => void): Disposer {\n return effect(() => {\n const value = this.value;\n // A subscription depends on THIS signal only. Reads performed by the\n // consumer callback are observational work, not reactive dependencies\n // of the subscription itself (issue 0004).\n untrack(() => fn(value));\n });\n }\n}\n\nfunction notifyObservers(observers: Set<ReactiveNode>, flavor: DirtyFlavor): void {\n for (const observer of observers) {\n observer.notify(flavor);\n }\n}\n\n// \u2500\u2500 Computed (derived, lazy, cached) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface ComputedNode<T> {\n value: T;\n lastChanged: number;\n observers: Set<ReactiveNode>;\n /**\n * Back-pointer into the owning ComputedImpl (ADR 0030.2 \u00A78 T5): recompute\n * this computed if stale, WITHOUT dependency tracking. Lets an effect's\n * pre-run poll refresh a computed source's change-epoch. Throws the\n * computed's (possibly cached) error.\n */\n pull(): void;\n}\n\nclass ComputedImpl<T> implements ReadonlySignal<T> {\n readonly [SIGNAL_BRAND] = true as const;\n /** @internal */\n _node: ComputedNode<T>;\n\n private compute: () => T;\n private state: NodeState = NodeState.Dirty; // Start dirty so first read computes\n private sources = new Set<SignalNode<unknown> | ComputedNode<unknown>>();\n private computing = false;\n private error: unknown = NO_COMPUTED_ERROR;\n /**\n * globalEpoch snapshot at the end of the last update()/validation pass.\n * A MaybeDirty computed recomputes only if some source's lastChanged\n * advanced past this \u2014 otherwise it revalidates to Clean for free.\n */\n private lastValidated = 0;\n\n constructor(fn: () => T) {\n this.compute = fn;\n this._node = {\n value: undefined as T,\n lastChanged: 0,\n observers: new Set(),\n pull: () => { this.peek(); },\n };\n }\n\n get value(): T {\n if (this.computing) {\n throw new Error('Circular dependency detected in computed()');\n }\n\n // Track dependency if inside a reactive context\n if (activeObserver) {\n activeObserver.sources.add(this._node as ComputedNode<unknown>);\n this._node.observers.add(activeObserver);\n }\n\n // Pull: recalculate if (maybe) dirty\n if (this.state !== NodeState.Clean) {\n this.update();\n }\n\n if (this.error !== NO_COMPUTED_ERROR) {\n throw this.error;\n }\n\n return this._node.value;\n }\n\n peek(): T {\n if (this.computing) {\n throw new Error('Circular dependency detected in computed()');\n }\n // Same pull as `get value`, minus dependency registration.\n if (this.state !== NodeState.Clean) {\n this.update();\n }\n if (this.error !== NO_COMPUTED_ERROR) {\n throw this.error;\n }\n return this._node.value;\n }\n\n /**\n * MaybeDirty validation: pull each source (computeds recompute lazily) and\n * report whether any source's change-epoch advanced past our last\n * validation. A pulled source may throw \u2014 its error-state transition\n * already bumped its change-epoch, so the epoch comparison decides.\n */\n private sourcesChanged(): boolean {\n for (const source of this.sources) {\n if ('pull' in source) {\n try { source.pull(); } catch (_) { /* transition bumped lastChanged */ }\n }\n if (source.lastChanged > this.lastValidated) return true;\n }\n return false;\n }\n\n private update(): void {\n // MaybeDirty means \"an upstream computed MAY have changed\". Validate\n // first: if no polled source advanced, revalidate to Clean without\n // recomputing (the half of push-pull that was previously unimplemented).\n if (this.state === NodeState.MaybeDirty && !this.sourcesChanged()) {\n this.state = NodeState.Clean;\n this.lastValidated = globalEpoch;\n return;\n }\n\n // Unsubscribe from previous sources (for dynamic dependency tracking)\n for (const source of this.sources) {\n source.observers.delete(this as unknown as ReactiveNode);\n }\n this.sources.clear();\n\n // Run compute with tracking\n const prevObserver = activeObserver;\n activeObserver = this as unknown as ReactiveNode;\n this.computing = true;\n const wasError = this.error !== NO_COMPUTED_ERROR;\n try {\n const newValue = this.compute();\n this.error = NO_COMPUTED_ERROR;\n if (!Object.is(this._node.value, newValue)) {\n this._node.value = newValue;\n this._node.lastChanged = ++globalEpoch;\n // NOTE: no notifyObservers() here. This update() is a *lazy* recompute\n // triggered by a read; every downstream observer was already marked\n // dirty / scheduled eagerly when the source signal changed (notify()\n // propagates through the computed chain). Re-notifying here would\n // re-schedule the very effect performing this read \u2014 a spurious\n // double-run that only surfaced once flush() began draining cascades.\n } else if (wasError) {\n // Error-state TRANSITION (exit) recovering to an EQUAL value: the\n // epoch must still advance, or downstream effects would skip the\n // recovery in their pre-run poll (ADR 0030.2 \u00A78 T5 error rule).\n this._node.lastChanged = ++globalEpoch;\n }\n } catch (error) {\n this.error = error;\n if (!wasError) {\n // Error-state TRANSITION (enter) counts as a change: polls must see\n // the epoch advance so the effect re-runs and its own read rethrows\n // into the effect's containment (issue 0003 semantics).\n this._node.lastChanged = ++globalEpoch;\n }\n throw error;\n } finally {\n this.computing = false;\n activeObserver = prevObserver;\n // A failed computed remains in an error state, but it must look clean to\n // notify(): the next dependency write then marks it dirty and propagates\n // to downstream observers so the computation can recover (issue 0003).\n this.state = NodeState.Clean;\n this.lastValidated = globalEpoch;\n }\n }\n\n /** @internal \u2014 called by the ReactiveNode interface when a source changes */\n notify(flavor: DirtyFlavor): void {\n // Never downgrade: a Dirty mark (definite signal change) survives a\n // later MaybeDirty from a parallel computed path.\n if (flavor > this.state) {\n const wasClean = this.state === NodeState.Clean;\n this.state = flavor;\n // Propagate once per dirty episode. Downstream observers always get\n // MaybeDirty \u2014 through a computed, \"changed\" is only knowable after a\n // recompute, which stays lazy (pull-based).\n if (wasClean) {\n notifyObservers(this._node.observers, NodeState.MaybeDirty);\n }\n }\n }\n\n subscribe(fn: (value: T) => void): Disposer {\n return effect(() => {\n const value = this.value;\n untrack(() => fn(value));\n });\n }\n\n // ReactiveNode interface \u2014 used by dependency tracking\n get sources_(): Set<SignalNode<unknown> | ComputedNode<unknown>> {\n return this.sources;\n }\n}\n\n// Make ComputedImpl satisfy ReactiveNode for the dependency tracker\nObject.defineProperty(ComputedImpl.prototype, 'sources', {\n enumerable: false,\n});\n\n// \u2500\u2500 Effect (side-effect, auto-tracks, auto-disposes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Maximum CONSECUTIVE self-re-schedules before we assume an infinite loop.\n * An effect that writes to a signal it reads re-schedules itself on every\n * run; when that happens MAX_EFFECT_RERUNS times in a row (the counter\n * resets whenever a run does NOT re-schedule the effect), the effect is\n * diagnosed (N301) and disposed. Clock-free \u2014 deterministic under CI and\n * fake timers, and convergent clamps pass because their second run writes\n * an equal value (Object.is cutoff \u2192 no re-schedule \u2192 counter reset).\n * See ADR 0008 Gap 2 / ADR 0009 / ADR 0030.2 T5.\n */\nconst MAX_EFFECT_RERUNS = 100;\n\ninterface EffectNode extends ReactiveNode {\n fn: () => void | (() => void);\n cleanup: (() => void) | null;\n disposed: boolean;\n sources: Set<SignalNode<unknown> | ComputedNode<unknown>>;\n /** List of dispose callbacks registered by the component */\n disposers: (() => void)[];\n /** Monotonic creation index \u2014 flush order (deterministic scheduler) */\n order: number;\n /** globalEpoch snapshot at the end of the last completed run */\n lastRunEpoch: number;\n /** Consecutive runs that re-scheduled this effect (clock-free loop guard) */\n selfReschedules: number;\n /** Dev-captured creation-site stack \u2014 effect identity for N301/N302 */\n createdAt?: string;\n /** N310 (async effect) already reported for this effect */\n asyncWarned?: boolean;\n}\n\nfunction createEffectNode(fn: () => void | (() => void)): EffectNode {\n return {\n state: NodeState.Dirty,\n fn,\n cleanup: null,\n disposed: false,\n sources: new Set(),\n disposers: [],\n order: nextEffectOrder++,\n lastRunEpoch: 0,\n selfReschedules: 0,\n notify(flavor: DirtyFlavor) {\n if (this.disposed) return;\n // Never downgrade Dirty (definite signal change) to MaybeDirty.\n if (flavor > this.state) this.state = flavor;\n pendingEffects.add(this);\n scheduleFlush();\n },\n };\n}\n\n/**\n * Pre-run poll (ADR 0030.2 T5): the effect was notified MaybeDirty, meaning\n * only computed sources MAY have changed. Pull each computed source (via the\n * node back-pointer) so its change-epoch is fresh, then compare every\n * source's epoch against the effect's last completed run. A pull may throw:\n * the computed's error-state transition already bumped its epoch, so the\n * comparison decides \u2014 a NEW error re-runs the effect (whose own read\n * rethrows into its containment, issue 0003), while an error the effect\n * already observed does not.\n */\nfunction effectSourcesChanged(node: EffectNode): boolean {\n for (const source of node.sources) {\n if ('pull' in source) {\n try { source.pull(); } catch (_) { /* transition bumped lastChanged */ }\n }\n if (source.lastChanged > node.lastRunEpoch) return true;\n }\n return false;\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\nfunction runEffect(node: EffectNode): void {\n if (node.disposed) return;\n\n // MaybeDirty: poll sources \u2014 skip the run when no value the effect read\n // actually changed (equal recomputes through a computed chain stop here).\n if (node.state === NodeState.MaybeDirty && !effectSourcesChanged(node)) {\n node.state = NodeState.Clean;\n return;\n }\n\n // Run cleanup from previous execution\n if (node.cleanup) {\n try { node.cleanup(); } catch (_) { /* cleanup errors are swallowed */ }\n node.cleanup = null;\n }\n\n // Unsubscribe from previous sources\n for (const source of node.sources) {\n source.observers.delete(node);\n }\n node.sources.clear();\n\n // Reset state BEFORE running: a notify fired by the effect's own writes\n // must survive into the next flush pass, not be clobbered afterwards.\n node.state = NodeState.Clean;\n\n // Run effect with tracking\n const prevObserver = activeObserver;\n const prevRunning = process.env.NODE_ENV !== 'production' ? runningEffect : null;\n activeObserver = node;\n if (process.env.NODE_ENV !== 'production') runningEffect = node;\n try {\n const result: unknown = node.fn();\n if (typeof result === 'function') {\n node.cleanup = result as () => void;\n } else if (isThenable(result)) {\n // effect(async \u2026) \u2014 rejected at the type level, guarded at runtime for\n // JS callers (ADR 0030.2 \u00A73). The Promise is NOT a cleanup; route its\n // rejection into the effect error path so nothing goes unhandled.\n if (process.env.NODE_ENV !== 'production' && isDev() && !node.asyncWarned) {\n node.asyncWarned = true;\n diag(\n 'N310',\n 'effect() callback returned a Promise \u2014 effects must be synchronous. ' +\n 'Async state belongs in resource(). The returned Promise is not a ' +\n 'cleanup function; its rejection is contained by the effect error path.',\n node.createdAt ? { effect: node.createdAt } : undefined,\n );\n }\n result.then(undefined, (err: unknown) => {\n console.error('Effect error:', err);\n });\n }\n } catch (err) {\n // Effect errors: log but don't crash the system.\n // The effect is NOT disposed \u2014 it may succeed on next signal change.\n console.error('Effect error:', err);\n } finally {\n activeObserver = prevObserver;\n if (process.env.NODE_ENV !== 'production') runningEffect = prevRunning;\n // Everything observable up to now \u2014 including epochs bumped by this\n // run's own lazy computed pulls \u2014 counts as SEEN by this run.\n node.lastRunEpoch = globalEpoch;\n }\n\n // Clock-free loop guard: this run re-scheduled the effect iff the effect\n // is back in the pending set (only its own writes can do that mid-run).\n if (pendingEffects.has(node)) {\n if (++node.selfReschedules > MAX_EFFECT_RERUNS) {\n if (process.env.NODE_ENV !== 'production' && isDev()) diag(\n 'N301',\n `Effect exceeded maximum re-run limit (${MAX_EFFECT_RERUNS}). ` +\n `This usually means the effect writes to a signal it reads. ` +\n `The effect has been disposed to prevent a UI freeze.`,\n node.createdAt ? { effect: node.createdAt } : undefined,\n );\n // Dispose as today: unsubscribe everywhere and drop from the queue.\n node.disposed = true;\n for (const source of node.sources) {\n source.observers.delete(node);\n }\n node.sources.clear();\n pendingEffects.delete(node);\n }\n } else {\n node.selfReschedules = 0;\n }\n}\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Create a writable reactive signal.\n *\n * ```ts\n * const count = signal(0);\n * count.value; // read (auto-tracks in reactive contexts)\n * count.value = 5; // write (notifies dependents)\n * count.peek(); // read WITHOUT tracking\n * ```\n */\nexport function signal<T>(initialValue: T): Signal<T> {\n return new SignalImpl(initialValue);\n}\n\n/**\n * Create a derived, lazy, cached computed signal.\n * Re-evaluates only when dependencies change AND the value is read.\n *\n * ```ts\n * const doubled = computed(() => count.value * 2);\n * doubled.value; // lazy evaluation\n * ```\n */\nexport function computed<T>(fn: () => T): ReadonlySignal<T> {\n return new ComputedImpl(fn);\n}\n\n/**\n * Type-level rejection of async callbacks: a Promise-returning fn makes the\n * call require an impossible second argument, so `effect(async () => {})`\n * fails to compile. Async work belongs in resource() (ADR 0030.2 \u00A73; N310).\n */\ntype SyncEffectGuard<R> = [R] extends [Promise<unknown>]\n ? [error: 'effect() callbacks must be synchronous \u2014 async work belongs in resource() (nisli N310)']\n : [];\n\n/**\n * Create a side-effect that re-runs when its dependencies change.\n * Returns a dispose function to stop the effect.\n *\n * The effect function may return a cleanup callback that runs\n * before each re-execution and on disposal. It must be SYNCHRONOUS \u2014\n * async functions are rejected at the type level and diagnosed at\n * runtime (N310); async state belongs in resource().\n *\n * ```ts\n * const dispose = effect(() => {\n * console.log('count is', count.value);\n * return () => { // cleanup\n * console.log('cleaning up');\n * };\n * });\n * dispose(); // stop the effect\n * ```\n */\nexport function effect<R>(fn: () => R, ..._rejectAsync: SyncEffectGuard<R>): Disposer {\n const node = createEffectNode(fn as unknown as () => void | (() => void));\n if (process.env.NODE_ENV !== 'production' && isDev()) {\n // Dev-captured effect identity: N301/N302 diagnostics name this site.\n node.createdAt = new Error('effect() created here').stack;\n }\n // Run immediately to establish initial dependencies\n runEffect(node);\n\n const dispose = () => {\n node.disposed = true;\n // Run final cleanup\n if (node.cleanup) {\n try { node.cleanup(); } catch (_) { /* swallow */ }\n node.cleanup = null;\n }\n // Unsubscribe from all sources\n for (const source of node.sources) {\n source.observers.delete(node);\n }\n node.sources.clear();\n pendingEffects.delete(node);\n };\n\n // Auto-dispose when inside component setup context\n if (contextHook) {\n const addDisposer = contextHook();\n if (addDisposer) {\n addDisposer(dispose);\n }\n }\n\n return disposable(dispose, dispose);\n}\n\n/**\n * Check if a value is a signal (writable or computed).\n * Used by the template engine to detect signals in expression slots.\n */\nexport function isSignal(value: unknown): value is ReadonlySignal<unknown> {\n return (\n value !== null &&\n typeof value === 'object' &&\n SIGNAL_BRAND in (value as Record<symbol, unknown>)\n );\n}\n\n// \u2500\u2500 Observer isolation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Run a function with activeObserver set to null, preventing any\n * signal reads inside `fn` from being tracked by an outer effect,\n * and return the function's result.\n *\n * Used by component.ts to isolate connectedCallback: child component\n * setup signal reads must not leak into a parent effect's dependency\n * set. See ADR 0008 Gap 1.\n */\nexport function untrack<T>(fn: () => T): T {\n const prev = activeObserver;\n activeObserver = null;\n try {\n return fn();\n } finally {\n activeObserver = prev;\n }\n}\n\n// \u2500\u2500 Synchronous flush \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Synchronously execute all pending effects.\n *\n * In normal usage, effects run automatically on the next microtask.\n * Use flush() only when you need effects to have executed before\n * the next line \u2014 e.g., imperative DOM measurement after state change.\n *\n * flush() is idempotent: calling it with no pending effects is a no-op.\n */\nexport function flush(): void {\n // Drain cascades: an effect may write a signal that schedules more effects\n // (e.g. A computes \u2192 B renders). Loop until the queue is empty so a single\n // flush() settles the whole synchronous cascade \u2014 no double-flush idiom.\n // Bounded to mirror the effect-loop guard; a runaway is a bug, not silence.\n let guard = 0;\n while (pendingEffects.size > 0) {\n flushPendingEffects();\n if (++guard > 100000) {\n if (process.env.NODE_ENV !== 'production' && isDev()) diag(\n 'N303',\n 'flush() cascade cap reached (100000 passes) \u2014 effects keep ' +\n 'scheduling more effects (a cross-effect write cycle?). Breaking out ' +\n 'to avoid a freeze; pending work remains unflushed.',\n );\n break;\n }\n }\n}\n\n/** Backward compat alias for tests. */\nexport { flush as flushEffects };\n\n/**\n * Settle the synchronous effect cascade AND all MICROTASK-scheduled work\n * pending at await time (content-projection sweeps, query/command initial\n * passes \u2014 at any queueMicrotask depth), plus the effects that work\n * schedules. Replaces the hand-rolled `flush(); await Promise.resolve();\n * flush()` and double-`flushEffects()` idioms.\n *\n * Bounded contract \u2014 deliberately NOT awaited: timers and other future\n * TASKS scheduled by that microtask work (e.g. a microtask calling\n * setTimeout) may fire after tick() resolves; awaiting arbitrary future\n * tasks is unbounded. Self-perpetuating schedulers are cut off at the\n * iteration cap (loudly \u2014 N303).\n *\n * ```ts\n * host.setProp('x', 1);\n * await tick();\n * expect(el.textContent).toBe('1');\n * ```\n */\nexport async function tick(): Promise<void> {\n flush(); // drain the synchronous effect cascade first\n // Each iteration crosses a MACROTASK boundary (setTimeout 0). Doing so runs\n // the ENTIRE microtask queue to completion first \u2014 including microtasks a\n // microtask schedules, at any depth \u2014 so a 3-deep `queueMicrotask` chain is\n // fully drained before the timer fires. After the drain we flush the effects\n // that work scheduled. When a full iteration schedules no effects, the queue\n // is quiescent and we return. Contract: microtask-scheduled work + effect\n // cascades pending at await time settle; TIMER-scheduled work does not\n // (see the doc above \u2014 bounded by design). The iteration cap is a\n // documented backstop for self-perpetuating schedulers, not a silent hang.\n for (let i = 0; i < 50; i++) {\n await new Promise<void>((resolve) => setTimeout(resolve, 0));\n if (pendingEffects.size === 0) return; // the microtask drain scheduled nothing\n flush();\n }\n if (process.env.NODE_ENV !== 'production' && isDev()) diag(\n 'N303',\n 'tick() iteration cap (50) reached with effects still pending \u2014 ' +\n 'something keeps scheduling reactive work every task (a ' +\n 'self-perpetuating scheduler?). Returning with work still queued.',\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["effect", "NodeState"]
7
+ }
@@ -0,0 +1,162 @@
1
+ import { diag as emitDiag, isDev } from "./diagnostics.js";
2
+ let enabledOverride = null;
3
+ function setTemplateAuditEnabled(enabled) {
4
+ if (process.env.NODE_ENV !== "production") enabledOverride = enabled;
5
+ }
6
+ function isTemplateAuditEnabled() {
7
+ return process.env.NODE_ENV !== "production" && (enabledOverride ?? isDev());
8
+ }
9
+ function diag(code, message, detail) {
10
+ if (!isTemplateAuditEnabled()) return;
11
+ emitDiag(code, message, detail === void 0 ? void 0 : { detail });
12
+ }
13
+ const callsites = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new WeakMap() : void 0;
14
+ function captureCallsite(strings) {
15
+ if (!isTemplateAuditEnabled() || callsites.has(strings)) return;
16
+ const stack = new Error().stack ?? "";
17
+ const frame = stack.split("\n").slice(1).find((line) => line.includes("at ") && !/template(-audit)?\.(ts|js|mjs|cjs)/.test(line));
18
+ callsites.set(strings, frame ? frame.trim() : "(unknown callsite)");
19
+ }
20
+ const audited = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new WeakSet() : void 0;
21
+ const KNOWN_MODIFIERS = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new Set(["stop", "prevent", "once", "enter", "escape", "space", "tab"]) : void 0;
22
+ const COMPONENT_EVENT_PREFIXES = ["ui-", "nisli-"];
23
+ const NATIVE_EVENTS_WITHOUT_IDL = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new Set(["focusin", "focusout"]) : void 0;
24
+ const GLOBAL_ATTRS = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new Set([
25
+ "id",
26
+ "class",
27
+ "style",
28
+ "slot",
29
+ "title",
30
+ "role",
31
+ "tabindex",
32
+ "name",
33
+ "part",
34
+ "hidden",
35
+ "dir",
36
+ "lang",
37
+ "is"
38
+ ]) : void 0;
39
+ function parseDirectives(content) {
40
+ const directives = { off: false, allowUndefined: /* @__PURE__ */ new Set() };
41
+ for (const node of content.childNodes) {
42
+ if (node.nodeType === Node.TEXT_NODE) {
43
+ if ((node.textContent ?? "").trim() === "") continue;
44
+ break;
45
+ }
46
+ if (node.nodeType === Node.COMMENT_NODE) {
47
+ const body = (node.textContent ?? "").trim();
48
+ if (!body.startsWith("nisli-audit")) break;
49
+ const rest = body.slice("nisli-audit".length).trim();
50
+ if (rest === "off") {
51
+ directives.off = true;
52
+ } else if (rest.startsWith("allow-undefined")) {
53
+ const tags = rest.slice("allow-undefined".length).replace(/^[:\s]+/, "").split(/[\s,]+/).filter(Boolean);
54
+ for (const tag of tags) directives.allowUndefined.add(tag.toLowerCase());
55
+ }
56
+ }
57
+ break;
58
+ }
59
+ return directives;
60
+ }
61
+ function checkDashTag(tag, site) {
62
+ let registry;
63
+ try {
64
+ registry = customElements;
65
+ } catch {
66
+ return;
67
+ }
68
+ if (!registry || registry.get(tag)) return;
69
+ let defined = false;
70
+ try {
71
+ registry.whenDefined(tag).then(
72
+ () => {
73
+ defined = true;
74
+ },
75
+ () => {
76
+ }
77
+ );
78
+ } catch {
79
+ diag("N101", `<${tag}> is not a valid custom element name \u2014 at ${site}`);
80
+ return;
81
+ }
82
+ queueMicrotask(() => {
83
+ if (defined) return;
84
+ setTimeout(() => {
85
+ try {
86
+ if (!isTemplateAuditEnabled() || defined || registry.get(tag)) return;
87
+ diag(
88
+ "N101",
89
+ `<${tag}> is used in a template but never defined (customElements.get returned undefined after grace) \u2014 at ${site}. If this tag is a lazily-loaded island, suppress with a leading <!-- nisli-audit allow-undefined: ${tag} --> comment.`
90
+ );
91
+ } catch {
92
+ }
93
+ }, 0);
94
+ });
95
+ }
96
+ function checkEvent(el, attrName, site) {
97
+ const parts = attrName.slice(1).split(".");
98
+ const eventName = parts[0] ?? "";
99
+ const modifiers = parts.slice(1);
100
+ const isNative = `on${eventName}` in el || NATIVE_EVENTS_WITHOUT_IDL.has(eventName);
101
+ const isComponentEvent = COMPONENT_EVENT_PREFIXES.some((p) => eventName.startsWith(p));
102
+ if (eventName && !isNative && !isComponentEvent) {
103
+ diag(
104
+ "N102",
105
+ `@${eventName} on <${el.tagName.toLowerCase()}> is not a known native event (and not an allowlisted ${COMPONENT_EVENT_PREFIXES.join("/")} component event) \u2014 at ${site}`
106
+ );
107
+ }
108
+ for (const mod of modifiers) {
109
+ if (!KNOWN_MODIFIERS.has(mod)) {
110
+ diag(
111
+ "N103",
112
+ `unknown event modifier ".${mod}" in "${attrName}" \u2014 known: ${[...KNOWN_MODIFIERS].join(", ")} \u2014 at ${site}`
113
+ );
114
+ }
115
+ }
116
+ }
117
+ function checkComponentAttr(tag, name, observed, site) {
118
+ if (name.startsWith("data-") || name.startsWith("aria-")) return;
119
+ if (GLOBAL_ATTRS.has(name) || observed.includes(name)) return;
120
+ diag(
121
+ "N104",
122
+ `<${tag}> declares attributes [${observed.join(", ")}] but the template sets unknown "${name}" \u2014 at ${site}. Undeclared names route through _setProp and are silently dead as attributes.`
123
+ );
124
+ }
125
+ function auditTemplate(strings, template) {
126
+ if (!isTemplateAuditEnabled() || audited.has(strings)) return;
127
+ audited.add(strings);
128
+ const site = callsites.get(strings) ?? "(unknown callsite)";
129
+ const content = template.content;
130
+ const directives = parseDirectives(content);
131
+ if (directives.off) return;
132
+ for (const el of content.querySelectorAll("*")) {
133
+ const tag = el.tagName.toLowerCase();
134
+ const isDashTag = tag.includes("-");
135
+ if (isDashTag && !directives.allowUndefined.has(tag)) {
136
+ checkDashTag(tag, site);
137
+ }
138
+ let observed;
139
+ if (isDashTag) {
140
+ const ctor = customElements.get(tag);
141
+ const list = ctor?.observedAttributes;
142
+ if (Array.isArray(list) && list.length > 0) observed = list;
143
+ }
144
+ for (const attr of [...el.attributes]) {
145
+ const name = attr.name;
146
+ if (name.startsWith("@")) {
147
+ checkEvent(el, name, site);
148
+ continue;
149
+ }
150
+ if (name.startsWith("class:") || name === "ref" || name === "html:inner") continue;
151
+ if (observed) checkComponentAttr(tag, name, observed, site);
152
+ }
153
+ }
154
+ }
155
+ export {
156
+ auditTemplate,
157
+ captureCallsite,
158
+ diag,
159
+ isTemplateAuditEnabled,
160
+ setTemplateAuditEnabled
161
+ };
162
+ //# sourceMappingURL=template-audit.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/template-audit.ts"],
4
+ "sourcesContent": ["/**\n * template-audit.ts \u2014 First-parse static template audit (ADR 0030.2 T4).\n *\n * Attached to the parse-once cache in template.ts: runs ONCE per `html`\n * callsite (keyed by TemplateStringsArray identity) at the first parse, and\n * walks the parsed-but-never-mounted template element checking its STATIC\n * parts. The silent-static-typo class \u2014 the no-build equivalent of a compile\n * error \u2014 dies in the engine, amortized free.\n *\n * Checks (per ADR 0030.2 \u00A72 T4 as amended by \u00A78 \"T4 (template)\"):\n * - N101 \u2014 a dash-tag with no customElements definition. Races\n * `customElements.whenDefined` against a microtask + macrotask grace so\n * ordinary lazy definition (module import order) never warns.\n * - N102 \u2014 an `@event` name that is neither a known native event nor an\n * allowlisted component-dispatched event (`ui-*`, `nisli-*`).\n * - N103 \u2014 an unknown `@event.modifier`.\n * - N104 \u2014 an attribute on a component tag that declares attributes\n * (non-empty `static observedAttributes`) which is neither declared nor a\n * global HTML attribute. Components WITHOUT declared attributes get NO\n * attribute audit: pre-schema (T3) there is no truth source \u2014 auto-resolved\n * props are deliberately not attributes (\u00A78).\n *\n * Suppression escape (designed for lazy islands, \u00A78): a comment directive as\n * the template's FIRST node (leading whitespace allowed):\n *\n * html`<!-- nisli-audit off --><my-island>\u2026</my-island>`\n * \u2014 disables the whole audit for this template.\n *\n * html`<!-- nisli-audit allow-undefined: my-island other-tag --><\u2026>`\n * \u2014 suppresses only N101 for the named tags (other checks stay live).\n *\n * The directive comment is static content: it survives into the mounted DOM\n * (and SSG output) as an inert HTML comment. Documented, accepted for the\n * prototype; the design gate may choose to strip it at parse.\n *\n * Calls AND module allocations are statically gated (ADR 0048). The runtime\n * override can suppress or restore available audits, never restore removed code.\n * No SSG/happy-dom detection is attempted; production rendering uses NODE_ENV\n * or the native production artifact.\n */\n\n// Reporting goes through the diagnostics leaf. The audit keeps its own\n// enable override (tests, SSG environments) and otherwise follows the\n// leaf's dev gate \u2014 under NODE_ENV=production builds it is silent.\nimport { diag as emitDiag, isDev } from './diagnostics.js';\n\nlet enabledOverride: boolean | null = null;\n\n/**\n * Enable/disable the first-parse audit (and its callsite capture);\n * `null` restores the dev-gate default.\n */\nexport function setTemplateAuditEnabled(enabled: boolean | null): void {\n if (process.env.NODE_ENV !== 'production') enabledOverride = enabled;\n}\n\n/** Current audit gate state (exposed for template.ts and tests). */\nexport function isTemplateAuditEnabled(): boolean {\n return process.env.NODE_ENV !== 'production' && (enabledOverride ?? isDev());\n}\n\n/** Emit a coded audit diagnostic through the diagnostics leaf (audit-gated). */\nexport function diag(code: string, message: string, detail?: unknown): void {\n if (!isTemplateAuditEnabled()) return;\n emitDiag(code, message, detail === undefined ? undefined : { detail });\n}\n\n// \u2500\u2500 Callsite capture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** First-seen authoring callsite per template identity (dev only). */\nconst callsites = process.env.NODE_ENV !== 'production'\n ? new WeakMap<TemplateStringsArray, string>() : undefined;\n\n/**\n * Remember the authoring callsite of an `html` template the first time its\n * strings identity is seen. Must be called from `html()` itself \u2014 by the\n * time the first parse happens (first mount), the authoring frame is gone\n * from the stack. No-op when the audit is disabled or already captured.\n */\nexport function captureCallsite(strings: TemplateStringsArray): void {\n if (!isTemplateAuditEnabled() || callsites!.has(strings)) return;\n const stack = new Error().stack ?? '';\n const frame = stack\n .split('\\n')\n .slice(1)\n .find(line => line.includes('at ') && !/template(-audit)?\\.(ts|js|mjs|cjs)/.test(line));\n callsites!.set(strings, frame ? frame.trim() : '(unknown callsite)');\n}\n\n// \u2500\u2500 Audit \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst audited = process.env.NODE_ENV !== 'production'\n ? new WeakSet<TemplateStringsArray>() : undefined;\n\n/** Modifiers understood by bindEvent (template.ts). */\nconst KNOWN_MODIFIERS = process.env.NODE_ENV !== 'production'\n ? new Set(['stop', 'prevent', 'once', 'enter', 'escape', 'space', 'tab']) : undefined;\n\n/** Component-dispatched event prefixes exempt from the native-event check (\u00A78). */\nconst COMPONENT_EVENT_PREFIXES = ['ui-', 'nisli-'];\n\n/** Real native events with no `on*` IDL attribute (the `in`-check misses them). */\nconst NATIVE_EVENTS_WITHOUT_IDL = process.env.NODE_ENV !== 'production'\n ? new Set(['focusin', 'focusout']) : undefined;\n\n/**\n * Global HTML attributes legal on ANY component tag. Mirrors (and must stay a\n * superset of) template.ts's auto-resolution exclusion list \u2014 these reach the\n * element as plain attributes, never `_setProp`.\n */\nconst GLOBAL_ATTRS = process.env.NODE_ENV !== 'production' ? new Set([\n 'id', 'class', 'style', 'slot', 'title', 'role', 'tabindex', 'name',\n 'part', 'hidden', 'dir', 'lang', 'is',\n]) : undefined;\n\ninterface AuditDirectives {\n off: boolean;\n allowUndefined: Set<string>;\n}\n\n/** Parse the leading `<!-- nisli-audit \u2026 -->` directive comment, if any. */\nfunction parseDirectives(content: DocumentFragment): AuditDirectives {\n const directives: AuditDirectives = { off: false, allowUndefined: new Set() };\n for (const node of content.childNodes) {\n if (node.nodeType === Node.TEXT_NODE) {\n if ((node.textContent ?? '').trim() === '') continue; // leading whitespace\n break;\n }\n if (node.nodeType === Node.COMMENT_NODE) {\n const body = (node.textContent ?? '').trim();\n if (!body.startsWith('nisli-audit')) break;\n const rest = body.slice('nisli-audit'.length).trim();\n if (rest === 'off') {\n directives.off = true;\n } else if (rest.startsWith('allow-undefined')) {\n const tags = rest\n .slice('allow-undefined'.length)\n .replace(/^[:\\s]+/, '')\n .split(/[\\s,]+/)\n .filter(Boolean);\n for (const tag of tags) directives.allowUndefined.add(tag.toLowerCase());\n }\n }\n break; // only the FIRST non-whitespace node may carry the directive\n }\n return directives;\n}\n\n/**\n * N101 \u2014 dash-tag with no definition. Rather than warning synchronously (the\n * defining module may simply import later in the same task), race\n * `customElements.whenDefined(tag)` against a microtask + macrotask grace;\n * warn only if the tag is still undefined after the grace elapses.\n */\nfunction checkDashTag(tag: string, site: string): void {\n let registry: CustomElementRegistry | undefined;\n try {\n registry = customElements;\n } catch {\n return; // no registry in this environment \u2014 nothing to check\n }\n if (!registry || registry.get(tag)) return;\n\n let defined = false;\n try {\n registry.whenDefined(tag).then(\n () => { defined = true; },\n () => { /* invalid name \u2014 the synchronous throw below already covers it */ },\n );\n } catch {\n diag('N101', `<${tag}> is not a valid custom element name \u2014 at ${site}`);\n return;\n }\n\n // Grace: one microtask hop (whenDefined settlements for definitions made\n // earlier in this task) + one macrotask hop (definitions made later in the\n // same task, e.g. module evaluation order). Under fake timers the macrotask\n // may never fire \u2014 the warning is simply skipped, never wrong.\n queueMicrotask(() => {\n if (defined) return;\n setTimeout(() => {\n try {\n if (!isTemplateAuditEnabled() || defined || registry!.get(tag)) return;\n diag(\n 'N101',\n `<${tag}> is used in a template but never defined (customElements.get returned ` +\n `undefined after grace) \u2014 at ${site}. If this tag is a lazily-loaded island, ` +\n `suppress with a leading <!-- nisli-audit allow-undefined: ${tag} --> comment.`,\n );\n } catch { /* environment torn down during grace \u2014 nothing to report */ }\n }, 0);\n });\n}\n\n/** N102/N103 \u2014 event-name and modifier checks for one `@event.mods` attribute. */\nfunction checkEvent(el: Element, attrName: string, site: string): void {\n const parts = attrName.slice(1).split('.');\n const eventName = parts[0] ?? '';\n const modifiers = parts.slice(1);\n\n const isNative = `on${eventName}` in el || NATIVE_EVENTS_WITHOUT_IDL!.has(eventName);\n const isComponentEvent = COMPONENT_EVENT_PREFIXES.some(p => eventName.startsWith(p));\n if (eventName && !isNative && !isComponentEvent) {\n diag(\n 'N102',\n `@${eventName} on <${el.tagName.toLowerCase()}> is not a known native event ` +\n `(and not an allowlisted ${COMPONENT_EVENT_PREFIXES.join('/')} component event) \u2014 at ${site}`,\n );\n }\n for (const mod of modifiers) {\n if (!KNOWN_MODIFIERS!.has(mod)) {\n diag(\n 'N103',\n `unknown event modifier \".${mod}\" in \"${attrName}\" \u2014 known: ` +\n `${[...KNOWN_MODIFIERS!].join(', ')} \u2014 at ${site}`,\n );\n }\n }\n}\n\n/** N104 \u2014 undeclared attribute on a component that DECLARES attributes. */\nfunction checkComponentAttr(tag: string, name: string, observed: string[], site: string): void {\n if (name.startsWith('data-') || name.startsWith('aria-')) return;\n if (GLOBAL_ATTRS!.has(name) || observed.includes(name)) return;\n diag(\n 'N104',\n `<${tag}> declares attributes [${observed.join(', ')}] but the template sets ` +\n `unknown \"${name}\" \u2014 at ${site}. Undeclared names route through _setProp and are ` +\n `silently dead as attributes.`,\n );\n}\n\n/**\n * Run the one-time static audit over a freshly parsed template. Called by\n * template.ts's parse-once cache on cache miss; idempotent per template\n * identity, no-op when the audit is disabled. Read-only over the cached\n * template element \u2014 mounting state is never touched.\n */\nexport function auditTemplate(strings: TemplateStringsArray, template: HTMLTemplateElement): void {\n if (!isTemplateAuditEnabled() || audited!.has(strings)) return;\n audited!.add(strings);\n\n const site = callsites!.get(strings) ?? '(unknown callsite)';\n const content = template.content;\n const directives = parseDirectives(content);\n if (directives.off) return;\n\n for (const el of content.querySelectorAll('*')) {\n const tag = el.tagName.toLowerCase();\n const isDashTag = tag.includes('-');\n\n if (isDashTag && !directives.allowUndefined.has(tag)) {\n checkDashTag(tag, site);\n }\n\n // Attribute audit ONLY for components with declared attrs (\u00A78): a\n // non-empty static observedAttributes is the single pre-T3 truth source.\n let observed: string[] | undefined;\n if (isDashTag) {\n const ctor = customElements.get(tag) as { observedAttributes?: unknown } | undefined;\n const list = ctor?.observedAttributes;\n if (Array.isArray(list) && list.length > 0) observed = list as string[];\n }\n\n for (const attr of [...el.attributes]) {\n const name = attr.name;\n if (name.startsWith('@')) {\n checkEvent(el, name, site);\n continue;\n }\n // Directive positions with their own semantics \u2014 never attributes.\n if (name.startsWith('class:') || name === 'ref' || name === 'html:inner') continue;\n if (observed) checkComponentAttr(tag, name, observed, site);\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }