@abide/abide 0.101.0 → 0.102.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 (157) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/abide.ts +223 -164
  3. package/docs/REGISTRY.md +726 -0
  4. package/docs/RULEBOOK.md +2753 -0
  5. package/docs/abide.md +12878 -0
  6. package/package.json +5 -3
  7. package/scaffold/AGENTS.md +31 -0
  8. package/scaffold/CLAUDE.md +1 -0
  9. package/scaffold/src/layout.abide +18 -0
  10. package/src/compiler/Lowering.ts +78 -9
  11. package/src/compiler/ShadowMap.ts +2 -2
  12. package/src/compiler/TypeSession.ts +262 -67
  13. package/src/compiler/argsSchemaOf.ts +1 -1
  14. package/src/compiler/checkApp.ts +43 -15
  15. package/src/compiler/checkedShadow.ts +39 -5
  16. package/src/compiler/classifyRead.ts +6 -4
  17. package/src/compiler/classifyTemplate.ts +1 -1
  18. package/src/compiler/compileApp.ts +433 -193
  19. package/src/compiler/compiledEntry.ts +43 -39
  20. package/src/compiler/componentGraph.ts +58 -71
  21. package/src/compiler/declaredExports.ts +24 -7
  22. package/src/compiler/emitClient.ts +141 -71
  23. package/src/compiler/emitComponent.ts +37 -8
  24. package/src/compiler/emitSource.ts +106 -13
  25. package/src/compiler/handlersUnder.ts +106 -59
  26. package/src/compiler/index.ts +7 -0
  27. package/src/compiler/mountsOf.ts +138 -0
  28. package/src/compiler/nearest.ts +14 -0
  29. package/src/compiler/parseAbide.ts +157 -20
  30. package/src/compiler/projectShadow.ts +213 -7
  31. package/src/compiler/reactiveTypes.ts +20 -7
  32. package/src/compiler/readSources.ts +15 -2
  33. package/src/compiler/readsIn.ts +61 -7
  34. package/src/compiler/renderMarkup.ts +3 -3
  35. package/src/compiler/scaffold.ts +30 -11
  36. package/src/compiler/serveLanguage.ts +125 -34
  37. package/src/compiler/sourceMapOf.ts +193 -0
  38. package/src/compiler/splitImports.ts +205 -83
  39. package/src/compiler/syncTypes.ts +32 -0
  40. package/src/compiler/tokenize.ts +14 -0
  41. package/src/compiler/writeDefaultProject.ts +1 -1
  42. package/src/compiler/writeTypes.ts +82 -0
  43. package/src/server/CLIENT_BUILD.ts +11 -13
  44. package/src/server/DEFAULT_PORT.ts +1 -1
  45. package/src/server/LAUNCHER_FILES.ts +9 -0
  46. package/src/server/MODULE_QUERIES.ts +18 -0
  47. package/src/server/TABLES.ts +7 -0
  48. package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
  49. package/src/server/abideModules.ts +391 -0
  50. package/src/server/answerFailure.ts +16 -6
  51. package/src/server/answerRequest.ts +235 -106
  52. package/src/server/answerRpc.ts +102 -30
  53. package/src/server/answerSocket.ts +65 -20
  54. package/src/server/answerValue.ts +22 -9
  55. package/src/server/appDirectories.ts +1 -1
  56. package/src/server/appPlugins.ts +1 -1
  57. package/src/server/appTree.ts +179 -0
  58. package/src/server/argsFor.ts +2 -2
  59. package/src/server/assistant.ts +2 -2
  60. package/src/server/bridgeRelay.ts +6 -8
  61. package/src/server/claudeCode.ts +0 -6
  62. package/src/server/clientBuildOptions.ts +8 -3
  63. package/src/server/coerceArgs.ts +1 -1
  64. package/src/server/commands.ts +37 -37
  65. package/src/server/componentModules.d.ts +3 -1
  66. package/src/server/compressAnswer.ts +4 -4
  67. package/src/server/config.ts +25 -29
  68. package/src/server/configFields.ts +31 -25
  69. package/src/server/createApp.ts +59 -73
  70. package/src/server/devWorker.ts +114 -181
  71. package/src/server/executableName.ts +4 -0
  72. package/src/server/fenceServer.ts +37 -35
  73. package/src/server/generatedSurfaces.ts +99 -66
  74. package/src/server/hooks.ts +124 -52
  75. package/src/server/iconAccent.ts +191 -0
  76. package/src/server/index.ts +11 -7
  77. package/src/server/installSourceMaps.ts +88 -0
  78. package/src/server/launcherOptions.ts +34 -31
  79. package/src/server/launcherPage.ts +107 -64
  80. package/src/server/manifestOf.ts +16 -13
  81. package/src/server/namesUnder.ts +2 -2
  82. package/src/server/nativeMenu.ts +5 -5
  83. package/src/server/negotiateEncoding.ts +1 -1
  84. package/src/server/principal.ts +10 -5
  85. package/src/server/realOrAsGiven.ts +10 -0
  86. package/src/server/refuseAddressCollision.ts +21 -14
  87. package/src/server/remote.ts +49 -52
  88. package/src/server/render.ts +65 -94
  89. package/src/server/renderComponent.ts +5 -0
  90. package/src/server/reportHealth.ts +2 -2
  91. package/src/server/requestScope.ts +12 -1
  92. package/src/server/runBuild.ts +24 -0
  93. package/src/server/runCompiled.ts +5 -5
  94. package/src/server/runDesktop.ts +52 -27
  95. package/src/server/serveBuild.ts +43 -52
  96. package/src/server/serveDev.ts +73 -37
  97. package/src/server/shell.ts +7 -22
  98. package/src/server/sourceMappings.ts +139 -0
  99. package/src/server/surfaceOf.ts +18 -3
  100. package/src/server/testBuild.ts +1 -19
  101. package/src/server/withAbideSources.ts +117 -0
  102. package/src/shared/ADDRESSES.ts +7 -18
  103. package/src/shared/APP_META.ts +7 -0
  104. package/src/shared/CopyOptions.ts +1 -1
  105. package/src/shared/DEBUG_META.ts +1 -1
  106. package/src/shared/RESERVED_DIRECTORIES.ts +15 -0
  107. package/src/shared/cookies.ts +2 -2
  108. package/src/shared/csp.ts +1 -1
  109. package/src/shared/escapeText.ts +1 -1
  110. package/src/shared/filledPath.ts +85 -0
  111. package/src/shared/framing.ts +79 -6
  112. package/src/shared/headKeyOf.ts +2 -0
  113. package/src/shared/index.ts +1 -1
  114. package/src/shared/log.ts +27 -13
  115. package/src/shared/matchRoute.ts +23 -4
  116. package/src/shared/menuItem.ts +3 -3
  117. package/src/shared/reactive/Keyed.ts +2 -2
  118. package/src/shared/reactive/NotLanded.ts +1 -1
  119. package/src/shared/reactive/Reactive.ts +43 -43
  120. package/src/shared/reactive/argsContract.ts +4 -4
  121. package/src/shared/reactive/canonicalKey.ts +4 -4
  122. package/src/shared/reactive/chunksOfStream.ts +6 -0
  123. package/src/shared/reactive/index.ts +1 -1
  124. package/src/shared/reactive/recordWrites.ts +103 -5
  125. package/src/shared/reactive/structural.ts +1 -1
  126. package/src/shared/reactive/tracking.ts +1 -1
  127. package/src/shared/reactive/watch.ts +3 -3
  128. package/src/shared/refuse.ts +1 -1
  129. package/src/shared/routePattern.ts +82 -46
  130. package/src/shared/rpc.ts +37 -8
  131. package/src/shared/rpcRequest.ts +27 -3
  132. package/src/shared/url.ts +4 -44
  133. package/src/shared/writePath.ts +1 -1
  134. package/src/ui/LAYER_NAMES.ts +1 -1
  135. package/src/ui/List.ts +3 -3
  136. package/src/ui/MOUNTING_LEVEL.ts +18 -0
  137. package/src/ui/Mismatch.ts +1 -1
  138. package/src/ui/RawRun.ts +1 -1
  139. package/src/ui/Router.ts +38 -31
  140. package/src/ui/Scope.ts +1 -1
  141. package/src/ui/TryRegion.ts +2 -2
  142. package/src/ui/applyHead.ts +33 -0
  143. package/src/ui/index.ts +1 -1
  144. package/src/ui/menuBridge.ts +1 -1
  145. package/src/ui/moveNode.ts +1 -1
  146. package/src/ui/reconcile.ts +2 -2
  147. package/src/ui/reportMenu.ts +2 -2
  148. package/src/ui/routing.ts +2 -2
  149. package/src/ui/setAttribute.ts +1 -1
  150. package/src/ui/setText.ts +1 -1
  151. package/src/ui/socketWire.ts +9 -7
  152. package/src/ui/wire.ts +1 -1
  153. package/scaffold/src/server/app.ts +0 -6
  154. package/scaffold/src/shared/.gitkeep +0 -0
  155. package/scaffold/src/ui/app.html +0 -8
  156. package/src/server/componentImports.ts +0 -56
  157. /package/scaffold/src/{ui/pages/page.abide → page.abide} +0 -0
@@ -10,7 +10,7 @@
10
10
  //
11
11
  // Every field `Cell` declares is written in its constructor, in one order, `undefined`
12
12
  // included — a shape that grows a field on a later path is two hidden classes and every
13
- // read downstream of it deoptimises.
13
+ // read downstream of it deoptimizes.
14
14
 
15
15
  import { type Failed, isFailed, isFailedNamed } from '../Failed.ts'
16
16
  import { namedLogger } from '../log.ts'
@@ -139,7 +139,7 @@ export type ReactiveOptions<
139
139
  /**
140
140
  * Shapes a settled value on the way in, or refuses it by returning a `Failed`.
141
141
  *
142
- * RULEBOOK 4.6, 4.7: runs untracked, on the settled value, once per materialisation,
142
+ * RULEBOOK 4.6, 4.7: runs untracked, on the settled value, once per materialization,
143
143
  * and refuses by RETURNING a `Failed` rather than by throwing. A throw here would be
144
144
  * indistinguishable from the body having failed, and 4.9 has a refused write mint
145
145
  * nothing where a failed producer (2.3) may still have something to say.
@@ -180,7 +180,7 @@ export type ReactiveOptions<
180
180
  * What tells one array element from another.
181
181
  *
182
182
  * RULEBOOK 5.22, 32.32: what tells one element from another. The property-name form is the one
183
- * that crosses a wire (18.4) — a function has no serialisation.
183
+ * that crosses a wire (18.4) — a function has no serialization.
184
184
  */
185
185
  by?:
186
186
  | (keyof ElementOf<Stored> & string)
@@ -374,7 +374,7 @@ type Persisted = {
374
374
  * A STREAMING PRODUCER'S OWN STATE (9.1, 9.2, 9.6), and `null` on every value whose body is
375
375
  * not an async generator — the shape `cap` and `adoption` already use.
376
376
  *
377
- * THE ACCUMULATION IS PUSHED INTO AND MATERIALISED ON THE READ. A chunk is one push and one
377
+ * THE ACCUMULATION IS PUSHED INTO AND MATERIALIZED ON THE READ. A chunk is one push and one
378
378
  * cascade; the copy a reader is handed is taken by the read that follows, so a hundred
379
379
  * chunks between two reads are one copy rather than a hundred (9.2). The copy is FRESH per
380
380
  * chunk count and has to be: a reader holding an earlier snapshot still holds what it read,
@@ -401,13 +401,13 @@ type Stream = {
401
401
  *
402
402
  * A version rather than a chunk count because a patch changes the accumulation without
403
403
  * changing its length (9.13), and a write stands at the version it was made at until the
404
- * accumulation moves past it (9.11). -1 where nothing has been materialised.
404
+ * accumulation moves past it (9.11). -1 where nothing has been materialized.
405
405
  */
406
- materialisedAt: number
406
+ materializedAt: number
407
407
  /**
408
- * Whether `value` is a write rather than a materialisation.
408
+ * Whether `value` is a write rather than a materialization.
409
409
  *
410
- * WHETHER `value` IS A WRITE rather than a materialisation, while `materialisedAt` stands. A value
410
+ * WHETHER `value` IS A WRITE rather than a materialization, while `materializedAt` stands. A value
411
411
  * forwarding this stream serves the write the way it would have served a production (11.59).
412
412
  */
413
413
  written: boolean
@@ -416,7 +416,7 @@ type Stream = {
416
416
  *
417
417
  * HOW MANY CHUNKS A READER HAS BEEN TOLD OF. Every chunk as it lands, without a cap; with one,
418
418
  * only what the window has released (5.18) — the rest are in `chunks` for a cursor (5.10) and
419
- * out of what a read materialises until the release.
419
+ * out of what a read materializes until the release.
420
420
  */
421
421
  visible: number
422
422
  /** The run in flight, told to return when a newer run supersedes it. */
@@ -470,7 +470,7 @@ function newStream(source: Cell | null): Stream {
470
470
  chunks: EMPTY_RING,
471
471
  version: 0,
472
472
  visible: 0,
473
- materialisedAt: -1,
473
+ materializedAt: -1,
474
474
  written: false,
475
475
  iterator: null,
476
476
  run: 0,
@@ -498,7 +498,7 @@ function becomeStream(cell: Cell, source: Cell | null): Stream {
498
498
  * every other producer's. A failed or superseded run never reaches here, so it starts no clock.
499
499
  */
500
500
  function closeRun(cell: Cell, stream: Stream): void {
501
- // Materialised here so 9.8's gates run on the accumulation the run closed with.
501
+ // Materialized here so 9.8's gates run on the accumulation the run closed with.
502
502
  if (heldValue(cell) === undefined) return
503
503
  stream.closed = true
504
504
  const expiry = cell.expiry
@@ -739,7 +739,7 @@ class Cell {
739
739
  * Whether a read may ever take the field straight, fixed at construction.
740
740
  *
741
741
  * WHETHER A READ MAY EVER TAKE THE FIELD STRAIGHT, fixed at construction: no expiry to
742
- * check and no accumulation to materialise. `setDirect` reads this one field where it
742
+ * check and no accumulation to materialize. `setDirect` reads this one field where it
743
743
  * would otherwise read one per mechanism that needs the read to go a frame in.
744
744
  */
745
745
  declare plain: boolean
@@ -965,7 +965,7 @@ function capFor(
965
965
  *
966
966
  * So the framework takes the cheap, predictable default — `===` is what JavaScript
967
967
  * means by "the same value" — and the app names `identity: structural` on the values
968
- * where re-materialisation is the common case. 5.5's "not equal wherever it cannot
968
+ * where re-materialization is the common case. 5.5's "not equal wherever it cannot
969
969
  * decide" is unchanged; it is just no longer the default reading.
970
970
  *
971
971
  * `identity` is discriminated by ARITY (5.3), once, at construction — not per compare.
@@ -1102,7 +1102,7 @@ export function flushSync(): void {
1102
1102
  * is never direct, so every read of it goes a frame in to `readIndirect` — which is
1103
1103
  * exactly where 6.5 and 6.6 are answered, and is why a value declaring no `ttl`
1104
1104
  * never reaches a clock. See `Expiry.ts`. A stream is never direct either, for the same
1105
- * shape of reason: its read is where the accumulation is materialised (9.1).
1105
+ * shape of reason: its read is where the accumulation is materialized (9.1).
1106
1106
  */
1107
1107
  function setDirect(cell: Cell, when: boolean): void {
1108
1108
  cell.direct = when && cell.plain
@@ -1438,7 +1438,7 @@ function drainPending(): void {
1438
1438
  // derived CELL on 2.13's aborted-body path, and for one of those `state` is not a
1439
1439
  // reminder to run — it IS the pull the next read takes (`state !== CLEAN` is what sends
1440
1440
  // a read a frame in). Forced back to `CLEAN` it is not deaf, it is settled: the read
1441
- // path skips `resolve`, the aborted body is never re-run and the `ready()` guard that
1441
+ // path skips `resolve`, the aborted body is never rerun and the `ready()` guard that
1442
1442
  // queued it answers false for the life of the page — undoing, on any throw anywhere in
1443
1443
  // the drain, exactly what that push was added to repair.
1444
1444
  for (let index = pendingAt; index < pendingCount; index += 1) {
@@ -1747,7 +1747,7 @@ function openWindow(cell: Cell, cap: Cap): void {
1747
1747
  *
1748
1748
  * THE WAY IN, AND THE ORDER IS 4.3 THEN 4.4: the schema gates, and what it RETURNS is
1749
1749
  * what `transform` receives — not the original value. A transform reading the raw input
1750
- * where a schema had normalised it is a second answer to what this value holds.
1750
+ * where a schema had normalized it is a second answer to what this value holds.
1751
1751
  * What it hands back is the ordinal, or the refusal a gate answered with (4.18) — `unknown`
1752
1752
  * because the one caller that cares tells the two apart by type.
1753
1753
  */
@@ -1769,7 +1769,7 @@ function accept(cell: Cell, value: unknown): unknown {
1769
1769
  if (transform === null) return produce(cell, checked)
1770
1770
  // 4.7 — A `transform` RUNS UNTRACKED: a write can be made from inside a tracked body
1771
1771
  // (`watch(() => target.set(…))`, a recompute that writes), and run tracked, the transform's
1772
- // reads were attributed to that reader — which then re-ran on a value it never read, with
1772
+ // reads were attributed to that reader — which then reran on a value it never read, with
1773
1773
  // those reads spliced into its cursor-ordered `sources`.
1774
1774
  const shaped = untracked(transform, checked, undefined)
1775
1775
  // 4.6 AND 4.9: a `transform` that returned a refusal stops the write here — nothing is
@@ -1844,7 +1844,7 @@ function writeStream(cell: Cell, value: unknown): unknown {
1844
1844
  function standWrite(cell: Cell, stream: Stream, value: unknown): number {
1845
1845
  // 7.9, as `produce` settles it.
1846
1846
  if (cell.owes === OWES_RELOAD) cell.owes = OWES_NOTHING
1847
- // 5.2 against what is served now, which on a stream may not have been materialised yet.
1847
+ // 5.2 against what is served now, which on a stream may not have been materialized yet.
1848
1848
  if (cell.servable) {
1849
1849
  const previous = heldValue(cell)
1850
1850
  if (
@@ -1855,7 +1855,7 @@ function standWrite(cell: Cell, stream: Stream, value: unknown): number {
1855
1855
  return cell.ordinal
1856
1856
  }
1857
1857
  cell.value = value
1858
- stream.materialisedAt = stream.version
1858
+ stream.materializedAt = stream.version
1859
1859
  stream.written = true
1860
1860
  const moved = !cell.servable || cell.failure !== undefined
1861
1861
  landChunks(cell)
@@ -2030,13 +2030,13 @@ function follow(cell: Cell, stream: Stream, inner: Cell): Error | undefined {
2030
2030
  if (standing !== null) unfollow(cell)
2031
2031
  // What `standWrite`'s identity gate compares against is what is served now.
2032
2032
  heldValue(cell)
2033
- stream.materialisedAt = stream.version
2033
+ stream.materializedAt = stream.version
2034
2034
  stream.written = true
2035
2035
  const stop = watchOver([inner], cell, (): undefined => {
2036
2036
  // The accumulation moved past the write. The drop is `wakeStreams`'s or the read's, not
2037
2037
  // this one's: a mirror runs inside the mark walk over `inner.readers`, and detaching here
2038
2038
  // would splice the list that walk is iterating.
2039
- if (!stream.written || stream.materialisedAt !== stream.version)
2039
+ if (!stream.written || stream.materializedAt !== stream.version)
2040
2040
  return undefined
2041
2041
  if (inner.owes !== OWES_NOTHING) settleOwed(inner)
2042
2042
  // 11.16: the followed value has nothing to serve, so neither has this.
@@ -2075,7 +2075,7 @@ function unserve(cell: Cell): void {
2075
2075
  }
2076
2076
 
2077
2077
  function mirror(cell: Cell, inner: Cell): void {
2078
- // 7.13 through the adoption: the re-settle a re-run of the adopter makes reaches a reload the
2078
+ // 7.13 through the adoption: the re-settle a rerun of the adopter makes reaches a reload the
2079
2079
  // adopted value owes, rather than finding it unservable and giving up.
2080
2080
  if (inner.owes !== OWES_NOTHING) settleOwed(inner)
2081
2081
  const adopted = inner.stream
@@ -2136,7 +2136,7 @@ function dropAdoption(cell: Cell): void {
2136
2136
  * Makes an adopter forward an adopted stream's buffer, ring and ordinals.
2137
2137
  *
2138
2138
  * 11.15 FOR A STREAM: the adopter takes the adopted value's buffer, ring and ordinals by
2139
- * reference and materialises from them on its own reads, through its own gates (11.14, 9.8).
2139
+ * reference and materializes from them on its own reads, through its own gates (11.14, 9.8).
2140
2140
  * Mirroring the value instead copied the accumulation once per wake whether or not anything
2141
2141
  * read it, and handed a cursor over the adopter whole accumulations where it wanted chunks.
2142
2142
  */
@@ -2151,8 +2151,8 @@ function forwardStream(cell: Cell, inner: Cell, adopted: Stream): void {
2151
2151
  stream.chunks = adopted.chunks
2152
2152
  stream.version = adopted.version
2153
2153
  stream.visible = adopted.visible
2154
- if (adopted.written && adopted.materialisedAt === adopted.version) {
2155
- stream.materialisedAt = stream.version
2154
+ if (adopted.written && adopted.materializedAt === adopted.version) {
2155
+ stream.materializedAt = stream.version
2156
2156
  stream.written = true
2157
2157
  gateInto(cell, inner.value)
2158
2158
  }
@@ -2188,9 +2188,9 @@ function stopForwarding(cell: Cell): void {
2188
2188
  * THE ABORT. A body that reads a value which has not landed throws `NotLanded`, and
2189
2189
  * this catches it and returns — the body is ABANDONED, not retried in place. The read
2190
2190
  * that threw has already registered this cell as a reader of it (tracking happens
2191
- * before the throw), so the source landing is what re-runs the body.
2191
+ * before the throw), so the source landing is what reruns the body.
2192
2192
  *
2193
- * "Throw a sentinel and re-run" was refused once on three grounds. Two are answered
2193
+ * "Throw a sentinel and rerun" was refused once on three grounds. Two are answered
2194
2194
  * by the reach starting the work (1.7) — the loads are in flight before the body
2195
2195
  * runs, so the abort costs a re-entry rather than a round trip. The third, that
2196
2196
  * nothing on a server wakes an abandoned body, is answered by the sink rather than
@@ -2532,7 +2532,7 @@ function acceptChunk(cell: Cell, stream: Stream, chunk: unknown): void {
2532
2532
  /**
2533
2533
  * Tells readers of every chunk landed so far, waking them at most once per task.
2534
2534
  *
2535
- * What a reader is told of: every chunk landed so far becomes what a read materialises, and the
2535
+ * What a reader is told of: every chunk landed so far becomes what a read materializes, and the
2536
2536
  * wake goes out at most once per task (9.9).
2537
2537
  */
2538
2538
  function releaseChunks(cell: Cell, stream: Stream): void {
@@ -2546,7 +2546,7 @@ function releaseChunks(cell: Cell, stream: Stream): void {
2546
2546
  * The streams holding a wake, all drained by one task.
2547
2547
  *
2548
2548
  * THE STREAMS HOLDING A WAKE, and one task drains all of them — never a task per stream.
2549
- * The pull is untouched: a read taken before the task runs materialises what has landed
2549
+ * The pull is untouched: a read taken before the task runs materializes what has landed
2550
2550
  * (12.12), so what waits is only the telling.
2551
2551
  */
2552
2552
  const WAKING: Cell[] = []
@@ -2621,18 +2621,18 @@ function landChunks(cell: Cell): void {
2621
2621
  }
2622
2622
 
2623
2623
  /**
2624
- * Materialises a stream's visible chunks into `value`, through both gates.
2624
+ * Materializes a stream's visible chunks into `value`, through both gates.
2625
2625
  *
2626
2626
  * 9.1 AND 9.8: the read that asks is where the accumulation becomes a `Stored`, through both
2627
2627
  * gates, once per chunk count however many reads ask.
2628
2628
  */
2629
- function materialise(cell: Cell, stream: Stream): void {
2630
- if (stream.materialisedAt === stream.version || !cell.servable) return
2629
+ function materialize(cell: Cell, stream: Stream): void {
2630
+ if (stream.materializedAt === stream.version || !cell.servable) return
2631
2631
  // 9.11: the accumulation moved past a write following a `Reactive`, which lets go of it here
2632
2632
  // where no chunk's wake did — a patch, or a run that closed empty. Only an own stream follows;
2633
2633
  // a forwarding one's `adoption` is what it forwards.
2634
2634
  if (cell.adoption !== null && stream.source === null) unfollow(cell)
2635
- stream.materialisedAt = stream.version
2635
+ stream.materializedAt = stream.version
2636
2636
  stream.written = false
2637
2637
  gateInto(cell, stream.chunks.slice(0, stream.visible))
2638
2638
  }
@@ -2677,7 +2677,7 @@ function gateThrough(cell: Cell, raw: unknown): void {
2677
2677
  */
2678
2678
  function heldValue(cell: Cell): unknown {
2679
2679
  const stream = cell.stream
2680
- if (stream !== null) materialise(cell, stream)
2680
+ if (stream !== null) materialize(cell, stream)
2681
2681
  return cell.value
2682
2682
  }
2683
2683
 
@@ -2840,7 +2840,7 @@ function readIndirect(cell: Cell): unknown {
2840
2840
  // 6.5: THE READ THAT FOLLOWS. Every value carrying an expiry arrives here and no
2841
2841
  // other value does, which is the whole of what `setDirect` buys.
2842
2842
  if (cell.expiry !== null) expire(cell)
2843
- if (cell.stream !== null) materialise(cell, cell.stream)
2843
+ if (cell.stream !== null) materialize(cell, cell.stream)
2844
2844
  if (!cell.servable) {
2845
2845
  // 2.3: a failed producer with nothing landed is the one case holding no value
2846
2846
  // and a refusal that explains the absence.
@@ -2866,7 +2866,7 @@ const EMPTY_RING = Object.freeze([]) as unknown as unknown[]
2866
2866
  * so a shared subgraph is re-walked once per path that reaches it, and a 26-cell DAG
2867
2867
  * with 130 edges cost 19,660 edge visits and 4,010 hash operations per probe read.
2868
2868
  *
2869
- * The stamp MEMOISES, and it is sound for one reason worth stating: within a pass,
2869
+ * The stamp MEMOIZES, and it is sound for one reason worth stating: within a pass,
2870
2870
  * "already visited while the walk is still running" IMPLIES "not busy", because a busy
2871
2871
  * node short-circuits the whole walk at its first visit. So a second arrival at a
2872
2872
  * stamped node answers false without descending.
@@ -2883,7 +2883,7 @@ let PROBE_PASS = 0
2883
2883
  * Propagation, derived on the read that asks (11.22) rather than held as a
2884
2884
  * subscription per source. 11.20: a value is busy where a source it read is busy —
2885
2885
  * and the premise is what `busy` MEANS, not which trigger produced it, so a refresh
2886
- * over a landed source reports through here even though the body never re-ran.
2886
+ * over a landed source reports through here even though the body never reran.
2887
2887
  */
2888
2888
  function walkBusy(cell: Cell, pass: number): boolean {
2889
2889
  if (cell.loading) return true
@@ -2967,7 +2967,7 @@ function busyNow(cell: Cell): boolean {
2967
2967
  * serving the figure it had computed before — right by its own lights, and a lie about the
2968
2968
  * data. Nothing in the output says so, which is why this is a walk and not a field.
2969
2969
  *
2970
- * The stamp MEMOISES the same way `walkBusy`'s does and is sound for the mirrored reason:
2970
+ * The stamp MEMOIZES the same way `walkBusy`'s does and is sound for the mirrored reason:
2971
2971
  * this walk short-circuits on NOT ready, so a second arrival at a node the walk is still
2972
2972
  * inside is a node that has not refused, and answering `true` there is answering what the
2973
2973
  * first visit will.
@@ -3053,7 +3053,7 @@ function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
3053
3053
  const waiter: Subscriber = newSubscriber([cell], null)
3054
3054
  waiter.notify = (): void => {
3055
3055
  // THE WAITER PULLS. A value built from a source that has just landed is marked and
3056
- // not yet re-run — nothing else reads it — so asking whether it is busy before
3056
+ // not yet rerun — nothing else reads it — so asking whether it is busy before
3057
3057
  // pulling it answers from the run that aborted, and the await never settles.
3058
3058
  if (cell.state !== CLEAN) resolve(cell)
3059
3059
  if (untilStreaming && cell.stream !== null) {
@@ -3093,7 +3093,7 @@ function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
3093
3093
  * than discovered — and one source given is what `s.watch` means. What was here before was
3094
3094
  * a second copy of the subscriber, the disposer, the re-entrancy guard and 12.8, which is
3095
3095
  * exactly the divergence 12.9 forbids: the copy had no `catch` at all, so a throwing effect
3096
- * re-ran into the same throw on every write where `watch` stops.
3096
+ * reran into the same throw on every write where `watch` stops.
3097
3097
  *
3098
3098
  * The value is handed over rather than read, so the effect never reaches the tracked path
3099
3099
  * and never throws `NotLanded` from this source.
@@ -3721,7 +3721,7 @@ const TAGGED = new Map<string, Set<WeakRef<Cell>>>()
3721
3721
  * O(1) delete on eviction; here an evicted value drops its `ref` instead, and a handle whose value
3722
3722
  * no longer points back at it is dead, the same as one whose value was collected. Because a bare
3723
3723
  * sweep may never run, the filing that finds the array past `compactProducersAt` compacts it, and
3724
- * the bar doubles past what survives, so the walk is amortised over the filings between two.
3724
+ * the bar doubles past what survives, so the walk is amortized over the filings between two.
3725
3725
  */
3726
3726
  let filedProducers: WeakRef<Cell>[] = []
3727
3727
  let compactProducersAt = 1024
@@ -3736,7 +3736,7 @@ let compactProducersAt = 1024
3736
3736
  const SIGNALS = new Map<string, Tracked>()
3737
3737
  const SIGNALS_BY_TAG = new Map<string, Tracked[]>()
3738
3738
  /**
3739
- * The bare selection's one signal. Not a key in `SIGNALS`: `{ tags: [] }` canonicalises to the
3739
+ * The bare selection's one signal. Not a key in `SIGNALS`: `{ tags: [] }` canonicalizes to the
3740
3740
  * empty set and matches nothing, where the bare selection matches every filed load in reach.
3741
3741
  */
3742
3742
  const EVERY_PRODUCER = newTracked()
@@ -4171,7 +4171,7 @@ export function isStreaming(value: unknown): boolean {
4171
4171
  *
4172
4172
  * 16.32 — THE CHUNKS A STREAMING VALUE HAS TOLD ITS READERS OF PAST `from`, for a writer sending each
4173
4173
  * once. Ungated they are read out of the accumulation in place, to `visible` rather than its length
4174
- * (5.18): a read materialises a copy of the whole of it (9.1), which for a writer woken per chunk is
4174
+ * (5.18): a read materializes a copy of the whole of it (9.1), which for a writer woken per chunk is
4175
4175
  * n²/2 elements copied to send n. A schema or a transform has to see the whole accumulation (9.8),
4176
4176
  * so a gated value answers from what a read would.
4177
4177
  */
@@ -4,7 +4,7 @@
4
4
  // through `contractOf` so a transport reads it off whichever face it was handed.
5
5
  //
6
6
  // DEFAULTS BEFORE THE KEY, IN ONE PLACE. 11.27: a call that left a member to the parameter and one
7
- // that wrote it are one entry, so every reach, call and lookup takes the key through `normalised`
7
+ // that wrote it are one entry, so every reach, call and lookup takes the key through `normalized`
8
8
  // — a caller that keyed the args as passed would file the same call twice, right in the value and
9
9
  // wrong in the work.
10
10
  //
@@ -54,7 +54,7 @@ export function gateOf(
54
54
  * `args` itself where the face has no defaults or nothing was missing, so a caller that passed
55
55
  * every member allocates nothing and a second application is free.
56
56
  */
57
- export function normalised<Args>(
57
+ export function normalized<Args>(
58
58
  face: Pick<Gated<unknown>, typeof PARAMETER_DEFAULTS>,
59
59
  args: Args,
60
60
  ): Args {
@@ -62,12 +62,12 @@ export function normalised<Args>(
62
62
  return defaults === null ? args : withDefaults(args, defaults)
63
63
  }
64
64
 
65
- /** The entry key a call with `args` is filed under: `normalised`, then 11.24's canonical form. */
65
+ /** The entry key a call with `args` is filed under: `normalized`, then 11.24's canonical form. */
66
66
  export function keyOf(
67
67
  face: Pick<Gated<unknown>, typeof PARAMETER_DEFAULTS>,
68
68
  args: unknown,
69
69
  ): string {
70
- return canonicalKey(normalised(face, args))
70
+ return canonicalKey(normalized(face, args))
71
71
  }
72
72
 
73
73
  // ABSENT ARGS ARE `{}`: a wire writes the two alike (an empty query), so read apart they are one
@@ -6,12 +6,12 @@ import { putOwn } from '../putOwn.ts'
6
6
  * RULEBOOK 11.24: members sorted by name, `undefined` members dropped, `Date` written ISO.
7
7
  *
8
8
  * 11.26: the key is taken BEFORE any gate runs, because a browser holds no copy of the gate — a
9
- * key taken after normalising is computed one way on a server and another in a browser, and every
10
- * handler with a normalising schema would miss its seed buffer and re-issue the call on hydration.
9
+ * key taken after normalizing is computed one way on a server and another in a browser, and every
10
+ * handler with a normalizing schema would miss its seed buffer and re-issue the call on hydration.
11
11
  *
12
12
  * 11.64: WHAT THE WIRE FORM CANNOT WRITE IS REFUSED AT THE REACH. `JSON.stringify` does not fail
13
13
  * on any of it — a `Set`, a `Map` and a class instance each have no own enumerable keys and
14
- * canonicalise to `{}`, and a non-finite number is written `null` — so two different reaches
14
+ * canonicalize to `{}`, and a non-finite number is written `null` — so two different reaches
15
15
  * produced one key and 1.8 joined the second to the first's standing work. The value was right
16
16
  * for the first caller and wrong for every other one, with nothing to see.
17
17
  */
@@ -55,7 +55,7 @@ function canonical(value: unknown, path: Steps): unknown {
55
55
  // AN ORDINARY OBJECT, AND `putOwn` FOR THE ONE KEY THAT NEEDS IT: `__proto__` is a SETTER on
56
56
  // `Object.prototype`, so `out[key] = …` for that key creates no own property and
57
57
  // `JSON.stringify` never sees it — `{id: 1, __proto__: {role: 'admin'}}` and `{id: 1}` then
58
- // canonicalise to one string, and 1.8 joins the second reach to the first's standing work.
58
+ // canonicalize to one string, and 1.8 joins the second reach to the first's standing work.
59
59
  // `Object.create(null)` closed that too, and V8 builds it in DICTIONARY mode: the shipped key
60
60
  // took 49-56% of the time without it there, and a standing keyed reach 53%, where JSC reads
61
61
  // level to 110% (`harness/scripts/sharedCases.ts`, two runs per engine).
@@ -1,3 +1,4 @@
1
+ import { isFailed } from '../Failed.ts'
1
2
  import { chunksFrom } from './Reactive.ts'
2
3
 
3
4
  /**
@@ -48,4 +49,9 @@ export async function* chunksOfStream(value: unknown): AsyncGenerator<unknown> {
48
49
  void cursor.return?.(undefined)
49
50
  }
50
51
  if (failure !== undefined) throw failure
52
+ // 16.59, 16.60 — A RUN ENDED BY ITS TIMEOUT SETTLES with the chunks it had and holds its refusal
53
+ // rather than rejecting, so the refusal is read off the value and thrown after them: a stream
54
+ // answered over http then ends on it (16.68) rather than as though it had finished.
55
+ const held = (value as { error?: () => unknown }).error?.()
56
+ if (isFailed(held)) throw held
51
57
  }
@@ -39,7 +39,7 @@ export { isKeyed, type Keyed } from './Keyed.ts'
39
39
  export { NotLanded } from './NotLanded.ts'
40
40
  // The server reaches the core through here rather than `./Reactive.ts`: `Reactive.ts` →
41
41
  // `log.ts` → this barrel → `scoped.ts` → `Reactive.ts` is a cycle, and a server module loaded
42
- // first that imports the core directly meets `MEMBER_KEYS` before it is initialised.
42
+ // first that imports the core directly meets `MEMBER_KEYS` before it is initialized.
43
43
  export {
44
44
  abandonLoad,
45
45
  deadlineOf,
@@ -5,8 +5,9 @@
5
5
  //
6
6
  // ONLY ARRAYS AND PLAIN OBJECTS ARE WRAPPED. A `Map`, a `Set` or a class instance keeps internal
7
7
  // slots a proxy cannot stand in for — `Map.prototype.set` refuses a proxy as its receiver — so one
8
- // reached is marked written the moment it is read, path and all. That over-reports, and an
9
- // over-report costs a row remade where an under-report shows a stale one.
8
+ // reached is marked written the moment it is read, path and all, and each object a `Map` or a `Set`
9
+ // hands out is marked as it leaves (`heldView`). That over-reports, and an over-report costs a row
10
+ // remade where an under-report shows a stale one.
10
11
  //
11
12
  // 18.11 — AND WHERE A SINK IS HANDED IN, each write is kept as data too, as JSON Patch (RFC 6902) at
12
13
  // the JSON Pointer it was reached by: a member set is `add`, an index `replace` (or `add` one past
@@ -49,7 +50,15 @@ export function recordWrites<Value>(
49
50
  sink?: { ops: PatchOp[]; lost: boolean },
50
51
  ): void {
51
52
  if (!isWrappable(root)) {
52
- mutate(root)
53
+ // A root no proxy can stand in for is written as a whole: what the callback reads out of it
54
+ // reaches the rows that show it with no proxy to say so.
55
+ if (root === null || typeof root !== 'object') {
56
+ mutate(root)
57
+ return
58
+ }
59
+ PATCHES.epoch += 1
60
+ PATCHES.written.set(root, PATCHES.epoch)
61
+ mutate(heldView(root, PATCHES.epoch) as Value)
53
62
  return
54
63
  }
55
64
  PATCHES.epoch += 1
@@ -217,9 +226,10 @@ export function recordWrites<Value>(
217
226
  }
218
227
  if (entry.proxy !== null) return entry.proxy
219
228
  mark(value)
229
+ entry.view ??= heldView(value, epoch)
220
230
  // Reached, and outside what a proxy can watch: whatever it does next is not kept.
221
231
  if (sink !== undefined) sink.lost = true
222
- return value
232
+ return entry.view
223
233
  },
224
234
  set(held, property, value) {
225
235
  mark(held)
@@ -363,7 +373,7 @@ function pointerOf(path: (string | number)[]): string {
363
373
  /**
364
374
  * The JSON Patch an array method makes, read off the array BEFORE it runs.
365
375
  *
366
- * A `splice`'s start and count are normalised the way the method does, so the `remove`s and `add`s
376
+ * A `splice`'s start and count are normalized the way the method does, so the `remove`s and `add`s
367
377
  * name the indices it touches.
368
378
  */
369
379
  function opsOfCall(
@@ -421,10 +431,13 @@ class Reached {
421
431
  * Written in this patch, directly or below — `PATCHES.written` holds this patch's epoch for it.
422
432
  */
423
433
  marked: boolean
434
+ /** What one no proxy can stand in for is handed out as: `heldView` of it. */
435
+ view: object | undefined
424
436
  constructor(proxy: object | null, parent: object | Parents | null) {
425
437
  this.proxy = proxy
426
438
  this.parent = parent
427
439
  this.marked = false
440
+ this.view = undefined
428
441
  }
429
442
  }
430
443
 
@@ -463,6 +476,91 @@ class Parents {
463
476
  }
464
477
  }
465
478
 
479
+ /**
480
+ * `container` as the callback is handed it: a `Map` or a `Set` that marks written each object key,
481
+ * value or member it hands out, and anything else as it is.
482
+ *
483
+ * A row over `[...map.values()]` reads one of them, and a write the callback makes to it through
484
+ * `map.get(id)` reaches it with nothing on the way to record it — so each one taken out is taken as
485
+ * written. AS IT LEAVES rather than all of them up front, which would make a patch of one entry cost
486
+ * the whole container. One level down: a row reads its item.
487
+ */
488
+ function heldView(container: object, epoch: number): object {
489
+ if (!(container instanceof Map) && !(container instanceof Set))
490
+ return container
491
+ return new Proxy(container, new HeldHandler(epoch))
492
+ }
493
+
494
+ // `Map`'s and `Set`'s own methods run on the container itself, which is the receiver their internal
495
+ // slots answer to; the ones that hand an object out mark it first.
496
+ class HeldHandler {
497
+ epoch: number
498
+ constructor(epoch: number) {
499
+ this.epoch = epoch
500
+ }
501
+ get(
502
+ held: Map<unknown, unknown> | Set<unknown>,
503
+ property: string | symbol,
504
+ ): unknown {
505
+ if (property === TARGET) return held
506
+ const value = Reflect.get(held, property, held)
507
+ if (typeof value !== 'function') return value
508
+ const epoch = this.epoch
509
+ if (property === 'get')
510
+ return (key: unknown) =>
511
+ markedOut((held as Map<unknown, unknown>).get(key), epoch)
512
+ if (property === 'forEach')
513
+ return (
514
+ each: (value: unknown, key: unknown, set: unknown) => void,
515
+ self?: unknown,
516
+ ) => {
517
+ for (const [key, member] of held.entries())
518
+ each.call(
519
+ self,
520
+ markedOut(member, epoch),
521
+ markedOut(key, epoch),
522
+ held,
523
+ )
524
+ }
525
+ if (
526
+ property === 'values' ||
527
+ property === 'keys' ||
528
+ property === 'entries' ||
529
+ property === Symbol.iterator
530
+ )
531
+ // A Map's own iterator is its entries; a Set's is its values.
532
+ return () =>
533
+ markedEach(
534
+ (value as () => Iterator<unknown>).call(held),
535
+ property === 'entries' ||
536
+ (property === Symbol.iterator && held instanceof Map),
537
+ epoch,
538
+ )
539
+ return (value as (...args: unknown[]) => unknown).bind(held)
540
+ }
541
+ }
542
+
543
+ function* markedEach(
544
+ each: Iterator<unknown>,
545
+ pairs: boolean,
546
+ epoch: number,
547
+ ): Generator<unknown> {
548
+ for (let next = each.next(); next.done !== true; next = each.next()) {
549
+ if (pairs) {
550
+ const [key, value] = next.value as [unknown, unknown]
551
+ markedOut(key, epoch)
552
+ markedOut(value, epoch)
553
+ } else markedOut(next.value, epoch)
554
+ yield next.value
555
+ }
556
+ }
557
+
558
+ function markedOut<Value>(value: Value, epoch: number): Value {
559
+ if (typeof value === 'object' && value !== null)
560
+ PATCHES.written.set(value, epoch)
561
+ return value
562
+ }
563
+
466
564
  /** Whether a proxy can stand in for `value`: an array or a plain object. */
467
565
  function isWrappable(value: unknown): value is object {
468
566
  if (value === null || typeof value !== 'object') return false
@@ -2,7 +2,7 @@
2
2
  * The by-value comparator an app passes as `identity`: deep equality over arrays and plain objects.
3
3
  *
4
4
  * Not the default: the default is the reference, and this is what a value whose producer
5
- * RE-MATERIALISES declares — `reactive(() => getInvoice({ id }), { identity: structural })`. A
5
+ * RE-MATERIALIZES declares — `reactive(() => getInvoice({ id }), { identity: structural })`. A
6
6
  * fresh-but-equal payload waking every reader is why it exists at all; what it stops being is the
7
7
  * price every other value pays.
8
8
  *
@@ -255,7 +255,7 @@ export function dropProbeReader(source: Tracked, reader: Subscriber): void {
255
255
  * sweep below takes it out.
256
256
  *
257
257
  * SWEPT ONCE HALF THE LIST IS DEAD, which keeps a walk to at most twice the live readers and the
258
- * sweep amortised O(1) a drop — and keeps the invariant every `length` test leans on: a list with
258
+ * sweep amortized O(1) a drop — and keeps the invariant every `length` test leans on: a list with
259
259
  * entries has a LIVE one, since a list that is all dead is at least half dead. HALF, NOT MORE THAN
260
260
  * HALF: a keyed replace ends on exactly half, n old rows dead behind n new ones, and a sweep that
261
261
  * waited for one more drop kept the old rows — and through each one's `notify`, its scope and its
@@ -203,7 +203,7 @@ export function watchOver(
203
203
  // UNTRACKED, AND THE CLEAR IS WHAT MAKES IT SO. The sources are given rather
204
204
  // than discovered, but the effect still RUNS wherever it was reached from — a
205
205
  // `s.watch` opened inside a `watch` had its reads attributed to the OUTER
206
- // reader, which then re-ran on a value it never read. A mirror is the same
206
+ // reader, which then reran on a value it never read. A mirror is the same
207
207
  // shape one turn up: it runs inside the mark walk, and 4.7 has the `transform`
208
208
  // it calls there running untracked.
209
209
  const previous = TRACKING.reader
@@ -219,7 +219,7 @@ export function watchOver(
219
219
  // subscribed to, so its landing is what runs this again.
220
220
  if (thrown instanceof NotLanded) return
221
221
  // 12.8: one that throws once usually throws every run, and a watch
222
- // re-running into the same throw is a loop with a log line per iteration.
222
+ // rerunning into the same throw is a loop with a log line per iteration.
223
223
  reader.alive = false
224
224
  detach(reader)
225
225
  effectThrew(thrown)
@@ -242,7 +242,7 @@ export function watchOver(
242
242
  }
243
243
 
244
244
  /**
245
- * The bare form: runs `effect` as a tracked context, re-running when anything it read changes.
245
+ * The bare form: runs `effect` as a tracked context, rerunning when anything it read changes.
246
246
  *
247
247
  * UNOWNED — the caller holds the stop. `watch` is this handed to its owners (`owned`); a template
248
248
  * binding's `Scope` and a `{#try}`'s retry hold their own, and call this direct.
@@ -15,7 +15,7 @@ import type { JsonValue } from './JsonValue.ts'
15
15
  import { namedLogger } from './log.ts'
16
16
  import { type Found, runSchema, type Schema } from './Schema.ts'
17
17
 
18
- /** The `abide:refuse` logger (28.9) — 15.13 and 15.15's namespace. */
18
+ /** The `abide:refuse` logger (28.8) — 15.13 and 15.15's namespace. */
19
19
  const REFUSE_LOG = namedLogger('abide:refuse')
20
20
 
21
21
  /**