solid-tag-runtime 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ARCHITECTURE.md CHANGED
@@ -42,7 +42,7 @@ The runtime should allow applications to:
42
42
  9. support normal JavaScript modules in the same graph
43
43
  10. keep the JSX compiler replaceable behind a narrow adapter
44
44
 
45
- ## 3. Non-goals for the first version
45
+ ## 3. Current non-goals
46
46
 
47
47
  The initial package does not attempt to provide:
48
48
 
@@ -54,9 +54,8 @@ The initial package does not attempt to provide:
54
54
  - circular runtime source-module support
55
55
  - transparent live replacement of already-held component references
56
56
  - full HMR propagation semantics
57
- - script-tag discovery/registration
58
57
 
59
- These may be added later without changing the module-first public model.
58
+ These may be added later without changing the module-first public model. HTML script discovery was added in `0.0.2` as a separate browser adapter rather than as a responsibility of the core module engine.
60
59
 
61
60
  ## 4. Package relationship
62
61
 
@@ -193,7 +192,270 @@ No JSX transformation. Imports are still resolved and rewritten through the runt
193
192
 
194
193
  This permits ordinary JavaScript and JSX modules to coexist in one graph.
195
194
 
196
- ## 8. Compiler adapter boundary
195
+
196
+ ## 8. HTML/browser adapter
197
+
198
+ `solid-tag-runtime/html` is an opt-in browser-facing adapter over the core module runtime.
199
+
200
+ `0.0.3` makes the adapter runtime-aware rather than treating document scripts as a single global pool.
201
+
202
+ Important architectural boundary:
203
+
204
+ ```text
205
+ DOM / HTML document
206
+ ↓
207
+ HTML runtime controller
208
+ ↓
209
+ runtime.define(...)
210
+ ↓
211
+ core module graph
212
+ ```
213
+
214
+ The core runtime remains DOM-independent. It does not scan documents, own HTML elements, run `MutationObserver`, or know about HTML attributes.
215
+
216
+ ### Public HTML API
217
+
218
+ The preferred API for non-trivial usage is a persistent controller:
219
+
220
+ ```ts
221
+ const html = createHTMLRuntime(runtime, {
222
+ scope: "preview",
223
+ root: document,
224
+ });
225
+
226
+ await html.register();
227
+ await html.observe({ registerExisting: false });
228
+ ```
229
+
230
+ The controller exposes:
231
+
232
+ ```ts
233
+ html.register(options?);
234
+ html.observe(options?);
235
+ html.flush(options?);
236
+ html.disconnect();
237
+
238
+ html.defineElement(element, options?);
239
+ html.registerElement(element, options?);
240
+ html.append(element, options?);
241
+ html.addModule(options);
242
+
243
+ html.owns(element);
244
+ html.getModuleId(element);
245
+ html.getElement(moduleId);
246
+ ```
247
+
248
+ The original convenience functions remain supported and are implemented on top of a cached default controller for each runtime:
249
+
250
+ ```ts
251
+ await registerHTML(runtime, options?);
252
+ await defineScript(runtime, element, options?);
253
+ const observer = await observeHTML(runtime, options?);
254
+ ```
255
+
256
+ This keeps the simple single-runtime API while giving multi-runtime applications an explicit ownership object.
257
+
258
+ ### Element ownership model
259
+
260
+ A module-level `WeakMap` is the authoritative in-memory ownership registry for runtime script elements.
261
+
262
+ Conceptually:
263
+
264
+ ```ts
265
+ WeakMap<HTMLScriptElement, ElementRecord>
266
+ ```
267
+
268
+ An element record stores at least:
269
+
270
+ ```text
271
+ owner controller
272
+ module ID
273
+ format
274
+ entry flag
275
+ source URL
276
+ registration state
277
+ pending registration promise
278
+ result/error
279
+ ```
280
+
281
+ This registry is shared by all HTML runtime controllers created by this package instance.
282
+
283
+ Important invariants:
284
+
285
+ 1. A runtime script element has at most one HTML-runtime owner.
286
+ 2. Ownership is claimed synchronously before asynchronous fetch/compile work.
287
+ 3. `append()` claims ownership before inserting the node into the DOM.
288
+ 4. Observer discovery, scans, explicit registration, and append/add helpers all use the same ownership record.
289
+ 5. Re-registering an element through the same controller is idempotent and waits on the same pending registration work.
290
+ 6. A different controller attempting to claim an owned element receives `HTMLModuleOwnershipError`.
291
+ 7. The observer must never redefine a module already being handled through `append()` or `registerElement()`.
292
+ 8. Distinct HTML elements cannot silently claim the same module ID in one HTML controller.
293
+ 9. Existing non-HTML runtime modules are not silently replaced by HTML registration.
294
+ 10. DOM removal does not implicitly delete a runtime module.
295
+
296
+ A `WeakSet` alone is insufficient because the adapter needs to distinguish ownership, pending work, success, and failure. The shared `WeakMap` also avoids retaining detached elements after all external references disappear.
297
+
298
+ ### Controller scope and multiple runtimes
299
+
300
+ A controller may declare a scope:
301
+
302
+ ```ts
303
+ const previewHTML = createHTMLRuntime(previewRuntime, {
304
+ scope: "preview",
305
+ });
306
+ ```
307
+
308
+ Owned elements are marked declaratively:
309
+
310
+ ```html
311
+ <script
312
+ type="solid-jsx"
313
+ data-solid-runtime="preview"
314
+ module="/App.jsx">
315
+ </script>
316
+ ```
317
+
318
+ `data-solid-runtime` is useful for declarative selection and DevTools visibility. It is not the authoritative ownership record; the `WeakMap` points to the actual controller object and prevents two controllers with the same string scope from owning the same element.
319
+
320
+ A scoped controller accepts matching scripts. It may also claim unscoped scripts when `acceptUnscoped` is enabled (default `true`). Setting `acceptUnscoped: false` is recommended when multiple scoped runtimes observe the same DOM root.
321
+
322
+ An unscoped controller does not silently claim a script carrying `data-solid-runtime` for another runtime.
323
+
324
+ ### User-created script elements
325
+
326
+ Applications may create the element themselves and explicitly associate it with one runtime:
327
+
328
+ ```ts
329
+ const script = document.createElement("script");
330
+ script.type = "solid-jsx";
331
+ script.setAttribute("module", "/Preview.jsx");
332
+ script.textContent = source;
333
+
334
+ await previewHTML.append(script);
335
+ ```
336
+
337
+ `append()` performs this ordering:
338
+
339
+ ```text
340
+ claim element
341
+ ↓
342
+ stamp data-solid-runtime (when scoped)
343
+ ↓
344
+ append to DOM
345
+ ↓
346
+ load/define module directly
347
+ ↓
348
+ observer later sees mutation and skips duplicate work
349
+ ```
350
+
351
+ This is deliberately not implemented as “append and wait for MutationObserver”. Explicit APIs are deterministic even when observation is disabled.
352
+
353
+ For an element already in the DOM:
354
+
355
+ ```ts
356
+ await previewHTML.registerElement(script);
357
+ ```
358
+
359
+ ### Controller-created script elements
360
+
361
+ `addModule()` creates and owns the script element for the caller:
362
+
363
+ ```ts
364
+ await previewHTML.addModule({
365
+ id: "/dynamic/Greeting.jsx",
366
+ source: `
367
+ export function Greeting(props) {
368
+ return <p>Hello {props.name}</p>;
369
+ }
370
+ `,
371
+ });
372
+ ```
373
+
374
+ It uses the same claim-before-append pipeline as `append()`.
375
+
376
+ ### One-shot registration and live observation
377
+
378
+ `register()` / `registerHTML()` perform one-shot discovery.
379
+
380
+ `observe()` / `observeHTML()` use `MutationObserver` for newly added scripts. Existing scripts are registered by default; `registerExisting: false` supports the pattern where a one-shot registration already ran.
381
+
382
+ Every discovered batch preserves the graph invariant:
383
+
384
+ ```text
385
+ discover complete batch
386
+ ↓
387
+ claim applicable elements
388
+ ↓
389
+ load all source
390
+ ↓
391
+ define all modules
392
+ ↓
393
+ execute newly discovered entries
394
+ ```
395
+
396
+ An entry may therefore import a dependency that appears later in the same discovery batch.
397
+
398
+ `flush()` waits for queued observer work and scans again, providing a deterministic synchronization point after external DOM mutations.
399
+
400
+ Observation in `0.0.3` remains addition-only: changing attributes/text of an already owned script or removing it does not implicitly update/remove the runtime module. Module replacement remains explicit through the core runtime update/invalidation APIs.
401
+
402
+ ### Supported script declarations
403
+
404
+ ```html
405
+ <script type="solid-jsx" module="/App.jsx">...</script>
406
+ <script type="solid-js" module="/utils.js">...</script>
407
+ <script type="solid-module" module="/App.jsx">...</script>
408
+ ```
409
+
410
+ `text/solid-jsx`, `text/solid-js`, and `text/solid-module` aliases are also accepted.
411
+
412
+ Format rules:
413
+
414
+ - `solid-jsx` → `jsx`
415
+ - `solid-js` → `js`
416
+ - `solid-module` → infer from `module`/`src` extension
417
+ - `language="js"` or `language="jsx"` overrides inference
418
+
419
+ ### `src` versus `module`
420
+
421
+ These attributes deliberately have different roles:
422
+
423
+ ```text
424
+ src = source provider / fetch location
425
+ module = identity in the runtime module graph
426
+ ```
427
+
428
+ Example:
429
+
430
+ ```html
431
+ <script
432
+ type="solid-jsx"
433
+ src="./source/Button.jsx"
434
+ module="/ui/Button.jsx">
435
+ </script>
436
+ ```
437
+
438
+ The source may move without changing imports of `/ui/Button.jsx`.
439
+
440
+ If a `src` script omits `module`, its resolved source URL becomes the module ID. Inline non-entry scripts require an explicit `module` identity. Inline entries may omit it and receive an anonymous runtime ID.
441
+
442
+ ### Entry execution
443
+
444
+ `entry` is an adapter concern, not a new runtime module kind.
445
+
446
+ ```html
447
+ <script type="solid-jsx" module="/App.jsx" entry>...</script>
448
+ ```
449
+
450
+ After all newly discovered modules in the batch are defined, the adapter imports each entry unless `executeEntries: false` is supplied.
451
+
452
+ ### Explicitly not HTML-as-JSX
453
+
454
+ The adapter does not transform arbitrary document HTML into JSX and does not currently assign semantics to `<template>` elements. JSX remains inside JavaScript module source.
455
+
456
+ This avoids introducing a second template language, implicit lexical scope rules, or ambiguous ownership of already-parsed DOM.
457
+
458
+ ## 9. Compiler adapter boundary
197
459
 
198
460
  The runtime engine itself does not depend on Acorn or the `solid-tag` AST.
199
461
 
@@ -233,7 +495,7 @@ Possible future adapters include:
233
495
 
234
496
  The module graph must remain independent of parser details.
235
497
 
236
- ## 9. Linking pipeline
498
+ ## 10. Linking pipeline
237
499
 
238
500
  For a source module `/features/Counter.jsx`:
239
501
 
@@ -266,7 +528,7 @@ Example graph:
266
528
  └── ../ui/Button.jsx → compiled virtual source URL
267
529
  ```
268
530
 
269
- ## 10. Resolution rules
531
+ ## 11. Resolution rules
270
532
 
271
533
  Resolution order is conceptually:
272
534
 
@@ -282,7 +544,7 @@ Relative virtual resolution uses POSIX-style paths regardless of the host operat
282
544
 
283
545
  This keeps module IDs stable in browsers and on Windows hosts.
284
546
 
285
- ## 11. Native ESM execution backend
547
+ ## 12. Native ESM execution backend
286
548
 
287
549
  The first execution backend deliberately relies on the JavaScript engine's native ESM evaluator rather than `new Function()`.
288
550
 
@@ -320,9 +582,9 @@ Benefits:
320
582
  - no `eval` / `new Function()` runtime wrapper
321
583
  - clean `toModule()` semantics
322
584
 
323
- ## 12. Circular dependency limitation
585
+ ## 13. Circular dependency limitation
324
586
 
325
- Virtual source module cycles are explicitly rejected in v0.0.1.
587
+ Virtual source module cycles are explicitly rejected through v0.0.2.
326
588
 
327
589
  Reason: the current linker must know the final URL of dependencies before it can create a module's immutable Blob/data URL. A cycle requires module identities to exist before final source URLs can be constructed.
328
590
 
@@ -341,7 +603,7 @@ Future options:
341
603
 
342
604
  Do not add ad-hoc cycle handling that breaks module semantics; change the backend deliberately.
343
605
 
344
- ## 13. Host module bridges
606
+ ## 14. Host module bridges
345
607
 
346
608
  `defineModule()` stores the namespace in a runtime-specific registry:
347
609
 
@@ -376,7 +638,7 @@ The ES module bridge captures each exported property value when the bridge modul
376
638
 
377
639
  To replace exports, call `defineModule()` again. This invalidates dependent runtime modules.
378
640
 
379
- ## 14. Scope injection design choice
641
+ ## 15. Scope injection design choice
380
642
 
381
643
  The runtime does not automatically turn unknown identifiers into scope lookups.
382
644
 
@@ -413,7 +675,7 @@ Reasons:
413
675
 
414
676
  A future convenience API may create scope modules automatically, but it should still compile down to module semantics.
415
677
 
416
- ## 15. Dependency graph
678
+ ## 16. Dependency graph
417
679
 
418
680
  Each record tracks:
419
681
 
@@ -431,7 +693,7 @@ When linking `/App.jsx` imports `/Button.jsx`:
431
693
 
432
694
  These edges power invalidation and tooling.
433
695
 
434
- ## 16. Caching
696
+ ## 17. Caching
435
697
 
436
698
  A source module caches:
437
699
 
@@ -444,7 +706,7 @@ Repeated imports use the same module URL/import promise until invalidated.
444
706
 
445
707
  Native ESM also caches imports by URL.
446
708
 
447
- ## 17. Invalidation and updates
709
+ ## 18. Invalidation and updates
448
710
 
449
711
  Updating a module invalidates:
450
712
 
@@ -469,7 +731,7 @@ const fresh = await runtime.import("/App.jsx");
469
731
 
470
732
  This is deliberate and matches the immutable identity of native evaluated modules.
471
733
 
472
- ## 18. `toModule()` semantics
734
+ ## 19. `toModule()` semantics
473
735
 
474
736
  `toModule(source)` is sugar for:
475
737
 
@@ -485,7 +747,7 @@ return module namespace
485
747
 
486
748
  It is asynchronous by design.
487
749
 
488
- ## 19. `toComponent()` semantics
750
+ ## 20. `toComponent()` semantics
489
751
 
490
752
  `toComponent()` calls `toModule()` and returns a selected callable export.
491
753
 
@@ -499,7 +761,7 @@ Named exports require an explicit `exportName`.
499
761
 
500
762
  The runtime does not guess between multiple component exports.
501
763
 
502
- ## 20. Security model
764
+ ## 21. Security model
503
765
 
504
766
  `solid-tag-runtime` is **not a sandbox**.
505
767
 
@@ -507,7 +769,7 @@ Dynamic source executes as JavaScript in the page/module environment and has wha
507
769
 
508
770
  Do not use this package to execute untrusted hostile code without a separate isolation mechanism such as a suitably sandboxed iframe/worker/process boundary.
509
771
 
510
- ## 21. CSP considerations
772
+ ## 22. CSP considerations
511
773
 
512
774
  The browser backend currently uses Blob module URLs by default.
513
775
 
@@ -519,7 +781,7 @@ Applications with strict Content Security Policy may need:
519
781
 
520
782
  The public module API should not depend on Blob URLs so this backend can change later.
521
783
 
522
- ## 22. Future directions
784
+ ## 23. Future directions
523
785
 
524
786
  Potential additions, in rough architectural order:
525
787
 
@@ -537,16 +799,6 @@ await runtime.load("/App.jsx");
537
799
 
538
800
  with configurable fetch/source providers.
539
801
 
540
- ### Script-tag module registration
541
-
542
- Possible declarative zero-build API:
543
-
544
- ```html
545
- <script type="solid-jsx" module="/Button.jsx">...</script>
546
- ```
547
-
548
- implemented as a thin source-provider layer over the same runtime graph.
549
-
550
802
  ### Runtime update/HMR helpers
551
803
 
552
804
  Graph invalidation already exists. Future APIs can add lifecycle hooks and entry re-rendering without changing module identity rules.
@@ -563,7 +815,7 @@ The runtime compiler boundary allows a future TSX-capable transformer without co
563
815
 
564
816
  If `solid-tag` later gains tagged-template → JSX transformation, runtime/editor tooling can expose source views without changing module execution semantics.
565
817
 
566
- ## 23. Decision log
818
+ ## 24. Decision log
567
819
 
568
820
  ### 2026-10-02 — Separate runtime package
569
821
 
@@ -606,3 +858,57 @@ Reason: the current immutable URL linker cannot assign stable module identities
606
858
  Decision: the runtime engine consumes `analyze()` and `transform()` rather than importing parser internals directly.
607
859
 
608
860
  Reason: preserve the option to add TSX, tagged-source, or alternative transformer backends later.
861
+ ### 2026-10-02 — HTML integration is an adapter subpath
862
+
863
+ Decision: add declarative script registration in `0.0.2` as `solid-tag-runtime/html`, not as methods on the core runtime and not as a separate npm package.
864
+
865
+ Reason: HTML discovery, DOM access, and fetching are browser concerns. Keeping them behind an export subpath preserves a DOM-free core while avoiding premature package fragmentation.
866
+
867
+ ### 2026-10-02 — HTML declares modules; it is not itself JSX
868
+
869
+ Decision: support JSX/JS source inside runtime `<script>` modules, but do not reinterpret arbitrary document HTML or `<template>` content as JSX in `0.0.2`.
870
+
871
+ Reason: module source already has clear lexical/import semantics. Treating document HTML as executable JSX would introduce separate scope, parsing, hydration, and DOM-ownership semantics.
872
+
873
+ ### 2026-10-02 — `src` and `module` are separate concepts
874
+
875
+ Decision: `src` identifies where source is fetched, while `module` identifies the module inside the runtime graph. When `module` is omitted for an external script, the resolved source URL is used as its identity.
876
+
877
+ Reason: source location and logical module identity should be independently controllable while still making zero-config relative URL module graphs ergonomic.
878
+
879
+ ### 2026-10-02 — Define all HTML modules before running entries
880
+
881
+ Decision: `registerHTML()` completes discovery/fetch/definition for all matching scripts before importing any module marked `entry`.
882
+
883
+ Reason: dependency availability must not depend on HTML document order.
884
+
885
+ ### 2026-10-02 — Live HTML discovery uses an addition-only observer
886
+
887
+ Decision: add `observeHTML()` to the existing `solid-tag-runtime/html` subpath in `0.0.2`. It uses `MutationObserver`, registers existing scripts by default, tracks seen element identities, and applies the same define-before-entry invariant to every newly discovered batch.
888
+
889
+ Reason: applications that dynamically insert runtime modules should not need to manually rescan the document, while keeping DOM observation separate from the core runtime module engine. Addition-only semantics avoid silently redefining modules because of text/attribute churn or trying to infer deletion semantics before the runtime has an explicit module-removal API.
890
+
891
+ ### 2026-10-02 — Persistent HTML runtime controllers own DOM/module bindings
892
+
893
+ Decision: `0.0.3` adds `createHTMLRuntime(runtime, options)` as the preferred advanced HTML API while keeping `registerHTML()`, `observeHTML()`, and `defineScript()` as compatibility conveniences.
894
+
895
+ Reason: once multiple runtimes, explicit append/register helpers, and observers coexist, ownership must be represented by a persistent object instead of implicit document-wide behavior.
896
+
897
+ ### 2026-10-02 — Runtime script ownership is coordinated through a shared WeakMap
898
+
899
+ Decision: all HTML ingestion paths use one module-level `WeakMap` from script element to ownership/registration record. Per-controller module-ID maps supplement element ownership.
900
+
901
+ Reason: a simple observer-local `WeakSet` cannot prevent races between manual registration and MutationObserver, distinguish different owners, retain a pending registration promise, or reject cross-runtime claims deterministically.
902
+
903
+ ### 2026-10-02 — Explicit append claims before DOM insertion
904
+
905
+ Decision: `html.append(element)` and `html.addModule()` claim ownership before mutating the DOM and register directly rather than relying on the observer callback.
906
+
907
+ Reason: ownership-before-insertion guarantees the observer can only observe already-owned work, eliminating duplicate compilation and making explicit APIs deterministic even when no observer is active.
908
+
909
+ ### 2026-10-02 — HTML runtime scope is declarative metadata, not ownership authority
910
+
911
+ Decision: scoped controllers stamp/use `data-solid-runtime="<scope>"` for discovery and debugging, while the in-memory WeakMap remains authoritative.
912
+
913
+ Reason: attributes are useful across declarative HTML and DevTools but cannot uniquely identify a controller instance or safely coordinate concurrent registration work.
914
+
package/README.md CHANGED
@@ -97,6 +97,187 @@ counterModule.Counter;
97
97
 
98
98
  The runtime compiles and links the dependency graph automatically.
99
99
 
100
+
101
+ ## Declarative HTML modules
102
+
103
+ `solid-tag-runtime/html` is the browser/DOM adapter. The core runtime remains DOM-independent.
104
+
105
+ For simple single-runtime use, the original helpers remain available:
106
+
107
+ ```ts
108
+ import {
109
+ registerHTML,
110
+ observeHTML,
111
+ } from "solid-tag-runtime/html";
112
+
113
+ await registerHTML(runtime);
114
+ ```
115
+
116
+ For applications that may have multiple runtimes or that create runtime scripts programmatically, `0.0.3` adds a persistent HTML runtime controller:
117
+
118
+ ```ts
119
+ import { createHTMLRuntime } from "solid-tag-runtime/html";
120
+
121
+ const html = createHTMLRuntime(runtime, {
122
+ scope: "main",
123
+ root: document,
124
+ });
125
+
126
+ await html.register();
127
+ await html.observe({ registerExisting: false });
128
+ ```
129
+
130
+ A scope is written to owned script elements as:
131
+
132
+ ```html
133
+ <script data-solid-runtime="main" ...></script>
134
+ ```
135
+
136
+ When multiple runtimes observe the same document, give each one a unique scope and normally set `acceptUnscoped: false`.
137
+
138
+ ### Declarative modules
139
+
140
+ ```html
141
+ <script
142
+ type="solid-jsx"
143
+ data-solid-runtime="main"
144
+ module="/ui/Button.jsx">
145
+ export function Button(props) {
146
+ return (
147
+ <button onClick={props.onClick}>
148
+ {props.children}
149
+ </button>
150
+ );
151
+ }
152
+ </script>
153
+ ```
154
+
155
+ Runtime modules import one another normally:
156
+
157
+ ```tsx
158
+ import { Button } from "./ui/Button.jsx";
159
+ ```
160
+
161
+ All newly discovered scripts in one batch are defined before any newly discovered `entry` module executes, so HTML document order does not require dependencies to appear first.
162
+
163
+ ### External source
164
+
165
+ ```html
166
+ <script
167
+ type="solid-jsx"
168
+ data-solid-runtime="main"
169
+ module="/ui/Button.jsx"
170
+ src="./components/Button.jsx">
171
+ </script>
172
+ ```
173
+
174
+ `src` answers **where source is loaded from**. `module` answers **what identity the source has in the runtime graph**.
175
+
176
+ ### User-created script elements
177
+
178
+ If application code creates a script itself, use the controller to associate it with the correct runtime:
179
+
180
+ ```ts
181
+ const script = document.createElement("script");
182
+ script.type = "solid-jsx";
183
+ script.setAttribute("module", "/dynamic/Greeting.jsx");
184
+ script.textContent = `
185
+ export function Greeting(props) {
186
+ return <p>Hello {props.name}</p>;
187
+ }
188
+ `;
189
+
190
+ await html.append(script);
191
+ ```
192
+
193
+ `append()` claims the element **before** inserting it in the DOM, stamps the controller scope, then registers it directly. If a `MutationObserver` is active, the later mutation callback sees that the element is already owned and does not compile it twice.
194
+
195
+ For an element that is already in the DOM:
196
+
197
+ ```ts
198
+ await html.registerElement(script);
199
+ ```
200
+
201
+ Same-controller repeated registration is idempotent. Trying to register an element already owned by another HTML controller throws `HTMLModuleOwnershipError`.
202
+
203
+ ### Let the controller create the script
204
+
205
+ ```ts
206
+ await html.addModule({
207
+ id: "/dynamic/Badge.jsx",
208
+ source: `
209
+ export function Badge(props) {
210
+ return <span>{props.children}</span>;
211
+ }
212
+ `,
213
+ });
214
+ ```
215
+
216
+ `addModule()` creates the `<script>` element, associates it with the controller, appends it, and registers it.
217
+
218
+ ### Observe scripts added by other code
219
+
220
+ ```ts
221
+ await html.observe({
222
+ registerExisting: false,
223
+ onError(error) {
224
+ console.error(error);
225
+ },
226
+ });
227
+
228
+ // Some unrelated code mutates the DOM directly.
229
+ const script = document.createElement("script");
230
+ script.type = "solid-jsx";
231
+ script.setAttribute("data-solid-runtime", "main");
232
+ script.setAttribute("module", "/observed/Thing.jsx");
233
+ script.textContent = `export const value = 42;`;
234
+ document.body.append(script);
235
+
236
+ await html.flush();
237
+ const module = await runtime.import("/observed/Thing.jsx");
238
+ ```
239
+
240
+ The observer is for DOM changes that happen **outside** the controller API. Prefer `html.append()` / `html.addModule()` when your code controls insertion because those operations are deterministic and do not depend on observer timing.
241
+
242
+ ### Ownership and deduplication
243
+
244
+ All HTML controllers share an internal `WeakMap` of script element → ownership/registration record.
245
+
246
+ That gives `0.0.3` these guarantees:
247
+
248
+ - one script element has at most one HTML-runtime owner;
249
+ - manual registration and observer registration cannot compile the same element twice;
250
+ - the same controller can register the same element repeatedly without redefining it;
251
+ - two distinct elements cannot silently define the same HTML-owned module ID;
252
+ - an HTML script cannot silently replace a module already defined outside that HTML controller;
253
+ - `data-solid-runtime` is declarative metadata while the in-memory owner record is authoritative.
254
+
255
+ The controller also exposes lightweight introspection:
256
+
257
+ ```ts
258
+ html.owns(script);
259
+ html.getModuleId(script);
260
+ html.getElement("/dynamic/Greeting.jsx");
261
+ ```
262
+
263
+ ### Script formats
264
+
265
+ ```text
266
+ solid-jsx / text/solid-jsx → JSX transformation
267
+ solid-js / text/solid-js → plain JavaScript module
268
+ solid-module → infer from module/src extension
269
+ ```
270
+
271
+ `language="js"` or `language="jsx"` may override inference.
272
+
273
+ ### Addition-only observation
274
+
275
+ Observation remains addition-only in `0.0.3`.
276
+
277
+ Changing the source/attributes of an already owned script or removing it from the DOM does not implicitly update/delete the corresponding runtime module. Use `runtime.update()` / `runtime.invalidate()` for explicit module lifecycle changes.
278
+
279
+ The HTML adapter does **not** reinterpret arbitrary document HTML as JSX and does not currently assign component semantics to `<template>`.
280
+
100
281
  ## `toModule()`
101
282
 
102
283
  For one-off source strings: