@abide/abide 0.100.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 (160) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/abide.ts +227 -163
  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 +10 -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 -56
  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 +124 -39
  53. package/src/server/answerSocket.ts +141 -48
  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 +48 -14
  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 +144 -183
  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 +81 -39
  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/GLOBAL_GETS.ts +9 -3
  107. package/src/shared/RESERVED_DIRECTORIES.ts +15 -0
  108. package/src/shared/RPC_SERVER.ts +2 -2
  109. package/src/shared/cookies.ts +2 -2
  110. package/src/shared/csp.ts +1 -1
  111. package/src/shared/escapeText.ts +1 -1
  112. package/src/shared/filledPath.ts +85 -0
  113. package/src/shared/framing.ts +79 -6
  114. package/src/shared/headKeyOf.ts +2 -0
  115. package/src/shared/index.ts +1 -1
  116. package/src/shared/log.ts +27 -13
  117. package/src/shared/matchRoute.ts +23 -4
  118. package/src/shared/menuItem.ts +3 -3
  119. package/src/shared/reactive/Cursor.ts +3 -9
  120. package/src/shared/reactive/Keyed.ts +4 -3
  121. package/src/shared/reactive/NotLanded.ts +1 -1
  122. package/src/shared/reactive/Reactive.ts +81 -60
  123. package/src/shared/reactive/argsContract.ts +4 -4
  124. package/src/shared/reactive/canonicalKey.ts +4 -4
  125. package/src/shared/reactive/chunksOfStream.ts +6 -0
  126. package/src/shared/reactive/index.ts +1 -1
  127. package/src/shared/reactive/recordWrites.ts +103 -5
  128. package/src/shared/reactive/structural.ts +1 -1
  129. package/src/shared/reactive/tracking.ts +155 -21
  130. package/src/shared/reactive/watch.ts +10 -6
  131. package/src/shared/refuse.ts +1 -1
  132. package/src/shared/routePattern.ts +82 -46
  133. package/src/shared/rpc.ts +47 -13
  134. package/src/shared/rpcRequest.ts +27 -3
  135. package/src/shared/url.ts +4 -44
  136. package/src/shared/writePath.ts +1 -1
  137. package/src/ui/LAYER_NAMES.ts +1 -1
  138. package/src/ui/List.ts +3 -3
  139. package/src/ui/MOUNTING_LEVEL.ts +18 -0
  140. package/src/ui/Mismatch.ts +1 -1
  141. package/src/ui/RawRun.ts +1 -1
  142. package/src/ui/Router.ts +38 -31
  143. package/src/ui/Scope.ts +1 -1
  144. package/src/ui/TryRegion.ts +2 -2
  145. package/src/ui/applyHead.ts +33 -0
  146. package/src/ui/index.ts +1 -1
  147. package/src/ui/menuBridge.ts +1 -1
  148. package/src/ui/moveNode.ts +1 -1
  149. package/src/ui/reconcile.ts +2 -2
  150. package/src/ui/reportMenu.ts +2 -2
  151. package/src/ui/routing.ts +2 -2
  152. package/src/ui/setAttribute.ts +1 -1
  153. package/src/ui/setText.ts +1 -1
  154. package/src/ui/socketWire.ts +9 -7
  155. package/src/ui/wire.ts +1 -1
  156. package/scaffold/src/server/app.ts +0 -6
  157. package/scaffold/src/shared/.gitkeep +0 -0
  158. package/scaffold/src/ui/app.html +0 -8
  159. package/src/server/componentImports.ts +0 -56
  160. /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'
@@ -19,10 +19,10 @@ import { type Found, runSchema, type Schema } from '../Schema.ts'
19
19
  import { UNEXPECTED } from '../UNEXPECTED.ts'
20
20
  import { WORK } from '../WORK.ts'
21
21
  import { CELL } from './CELL.ts'
22
+ import { Cursor, hasCursorBehind, keepPlaces, type Retained } from './Cursor.ts'
22
23
  import { ENTRY_LIVES } from './ENTRY_LIVES.ts'
23
24
  import { armTimer, type Expiry, expiryFor } from './Expiry.ts'
24
25
  import { FOLLOWED } from './FOLLOWED.ts'
25
- import { Cursor, hasCursorBehind, keepPlaces, type Retained } from './Cursor.ts'
26
26
  import { isReactive } from './isReactive.ts'
27
27
  import { isThenable } from './isThenable.ts'
28
28
  import { MUTATION } from './MUTATION.ts'
@@ -38,16 +38,19 @@ import {
38
38
  CLEAN,
39
39
  DIRTY,
40
40
  detach,
41
- detachProbes,
42
41
  dropProbeReader,
43
42
  newSubscriber,
43
+ newTracked,
44
44
  RELEASING,
45
+ revive,
45
46
  type Subscriber,
46
47
  TRACKING,
47
48
  type Tracked,
49
+ tombstone,
48
50
  track,
49
51
  trackProbe,
50
52
  truncate,
53
+ truncateProbes,
51
54
  untracked,
52
55
  } from './tracking.ts'
53
56
  import { owned, owning, watchOver } from './watch.ts'
@@ -136,7 +139,7 @@ export type ReactiveOptions<
136
139
  /**
137
140
  * Shapes a settled value on the way in, or refuses it by returning a `Failed`.
138
141
  *
139
- * 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,
140
143
  * and refuses by RETURNING a `Failed` rather than by throwing. A throw here would be
141
144
  * indistinguishable from the body having failed, and 4.9 has a refused write mint
142
145
  * nothing where a failed producer (2.3) may still have something to say.
@@ -177,7 +180,7 @@ export type ReactiveOptions<
177
180
  * What tells one array element from another.
178
181
  *
179
182
  * RULEBOOK 5.22, 32.32: what tells one element from another. The property-name form is the one
180
- * that crosses a wire (18.4) — a function has no serialisation.
183
+ * that crosses a wire (18.4) — a function has no serialization.
181
184
  */
182
185
  by?:
183
186
  | (keyof ElementOf<Stored> & string)
@@ -371,7 +374,7 @@ type Persisted = {
371
374
  * A STREAMING PRODUCER'S OWN STATE (9.1, 9.2, 9.6), and `null` on every value whose body is
372
375
  * not an async generator — the shape `cap` and `adoption` already use.
373
376
  *
374
- * 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
375
378
  * cascade; the copy a reader is handed is taken by the read that follows, so a hundred
376
379
  * chunks between two reads are one copy rather than a hundred (9.2). The copy is FRESH per
377
380
  * chunk count and has to be: a reader holding an earlier snapshot still holds what it read,
@@ -398,13 +401,13 @@ type Stream = {
398
401
  *
399
402
  * A version rather than a chunk count because a patch changes the accumulation without
400
403
  * changing its length (9.13), and a write stands at the version it was made at until the
401
- * 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.
402
405
  */
403
- materialisedAt: number
406
+ materializedAt: number
404
407
  /**
405
- * Whether `value` is a write rather than a materialisation.
408
+ * Whether `value` is a write rather than a materialization.
406
409
  *
407
- * 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
408
411
  * forwarding this stream serves the write the way it would have served a production (11.59).
409
412
  */
410
413
  written: boolean
@@ -413,7 +416,7 @@ type Stream = {
413
416
  *
414
417
  * HOW MANY CHUNKS A READER HAS BEEN TOLD OF. Every chunk as it lands, without a cap; with one,
415
418
  * only what the window has released (5.18) — the rest are in `chunks` for a cursor (5.10) and
416
- * out of what a read materialises until the release.
419
+ * out of what a read materializes until the release.
417
420
  */
418
421
  visible: number
419
422
  /** The run in flight, told to return when a newer run supersedes it. */
@@ -467,7 +470,7 @@ function newStream(source: Cell | null): Stream {
467
470
  chunks: EMPTY_RING,
468
471
  version: 0,
469
472
  visible: 0,
470
- materialisedAt: -1,
473
+ materializedAt: -1,
471
474
  written: false,
472
475
  iterator: null,
473
476
  run: 0,
@@ -495,7 +498,7 @@ function becomeStream(cell: Cell, source: Cell | null): Stream {
495
498
  * every other producer's. A failed or superseded run never reaches here, so it starts no clock.
496
499
  */
497
500
  function closeRun(cell: Cell, stream: Stream): void {
498
- // 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.
499
502
  if (heldValue(cell) === undefined) return
500
503
  stream.closed = true
501
504
  const expiry = cell.expiry
@@ -556,8 +559,12 @@ export function isStreamBody(body: (args: never) => unknown): boolean {
556
559
  * under JSC; adding a DEAD boolean back put all three within 0.6%, which is the proof it
557
560
  * was layout and not work — that same dead boolean then cost the probe walk 6-7%.
558
561
  *
559
- * So the boundary is worth KNOWING and not worth engineering around. Adding a field
560
- * costs nothing; getting under costs a structural change that the next field added here
562
+ * Above the budget is not free under JSC, though: three fields more (the dead-reader
563
+ * counts and `probeCursor`) moved `construct a value` and the derived case +6-8% there,
564
+ * and three UNUSED fields on the old record moved them the same — layout, not work.
565
+ *
566
+ * So the boundary is worth KNOWING and not worth engineering around. A field costs a few
567
+ * ns of construction; getting under costs a structural change that the next field added here
561
568
  * silently undoes, and it re-rolls in a different direction for every row. If
562
569
  * construction ever needs that 11%, shed fields — do not build machinery to shed them.
563
570
  * And when a V8-only move of this size shows up on a graph row, the first thing to check
@@ -568,20 +575,24 @@ class Cell {
568
575
  * Who reads this value, on the value edge.
569
576
  *
570
577
  * ARRAYS, not sets. The mark walk walks this per write and a set's iterator
571
- * allocates; removal is O(n) against a reader count that is small by construction.
578
+ * allocates. A disposed reader is left in place and counted rather than found — see
579
+ * `dropFrom` in `tracking.ts` — so a walk over this skips `!alive`.
572
580
  */
573
581
  declare readers: Subscriber[]
582
+ declare deadReaders: number
574
583
  /**
575
584
  * Who asked about this value's PROBES — a different edge from `readers`, and see
576
585
  * `markProbes` for what that buys.
577
586
  */
578
587
  declare probeReaders: Subscriber[]
588
+ declare deadProbeReaders: number
579
589
  declare sources: Cell[]
580
590
  /**
581
591
  * The values whose PROBES this body asked about — a `Cell` is a `Subscriber`, so it
582
592
  * carries the reader half of the probe edge as well as the source half.
583
593
  */
584
594
  declare probeSources: Cell[]
595
+ declare probeCursor: number
585
596
  declare cursor: number
586
597
  /** See `Subscriber.seen`. */
587
598
  declare seen: Set<Cell> | null
@@ -728,7 +739,7 @@ class Cell {
728
739
  * Whether a read may ever take the field straight, fixed at construction.
729
740
  *
730
741
  * WHETHER A READ MAY EVER TAKE THE FIELD STRAIGHT, fixed at construction: no expiry to
731
- * 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
732
743
  * would otherwise read one per mechanism that needs the read to go a frame in.
733
744
  */
734
745
  declare plain: boolean
@@ -801,9 +812,12 @@ class Cell {
801
812
  ) {
802
813
  // Every field, in one order, `undefined` included.
803
814
  this.readers = []
815
+ this.deadReaders = 0
804
816
  this.probeReaders = []
817
+ this.deadProbeReaders = 0
805
818
  this.sources = []
806
819
  this.probeSources = []
820
+ this.probeCursor = 0
807
821
  this.cursor = 0
808
822
  this.seen = null
809
823
  this.seenThrough = 0
@@ -951,7 +965,7 @@ function capFor(
951
965
  *
952
966
  * So the framework takes the cheap, predictable default — `===` is what JavaScript
953
967
  * means by "the same value" — and the app names `identity: structural` on the values
954
- * 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
955
969
  * decide" is unchanged; it is just no longer the default reading.
956
970
  *
957
971
  * `identity` is discriminated by ARITY (5.3), once, at construction — not per compare.
@@ -1088,7 +1102,7 @@ export function flushSync(): void {
1088
1102
  * is never direct, so every read of it goes a frame in to `readIndirect` — which is
1089
1103
  * exactly where 6.5 and 6.6 are answered, and is why a value declaring no `ttl`
1090
1104
  * never reaches a clock. See `Expiry.ts`. A stream is never direct either, for the same
1091
- * 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).
1092
1106
  */
1093
1107
  function setDirect(cell: Cell, when: boolean): void {
1094
1108
  cell.direct = when && cell.plain
@@ -1294,6 +1308,7 @@ function markProbes(cell: Cell, pass: number): void {
1294
1308
  const readers = cell.readers
1295
1309
  for (let index = 0; index < readers.length; index += 1) {
1296
1310
  const reader = readers[index] as Subscriber
1311
+ if (!reader.alive) continue
1297
1312
  if (!reader.isEffect) markProbes(reader as unknown as Cell, pass)
1298
1313
  else if (reader.adopter !== null)
1299
1314
  markProbes(reader.adopter as unknown as Cell, pass)
@@ -1423,7 +1438,7 @@ function drainPending(): void {
1423
1438
  // derived CELL on 2.13's aborted-body path, and for one of those `state` is not a
1424
1439
  // reminder to run — it IS the pull the next read takes (`state !== CLEAN` is what sends
1425
1440
  // a read a frame in). Forced back to `CLEAN` it is not deaf, it is settled: the read
1426
- // 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
1427
1442
  // queued it answers false for the life of the page — undoing, on any throw anywhere in
1428
1443
  // the drain, exactly what that push was added to repair.
1429
1444
  for (let index = pendingAt; index < pendingCount; index += 1) {
@@ -1732,7 +1747,7 @@ function openWindow(cell: Cell, cap: Cap): void {
1732
1747
  *
1733
1748
  * THE WAY IN, AND THE ORDER IS 4.3 THEN 4.4: the schema gates, and what it RETURNS is
1734
1749
  * what `transform` receives — not the original value. A transform reading the raw input
1735
- * 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.
1736
1751
  * What it hands back is the ordinal, or the refusal a gate answered with (4.18) — `unknown`
1737
1752
  * because the one caller that cares tells the two apart by type.
1738
1753
  */
@@ -1754,7 +1769,7 @@ function accept(cell: Cell, value: unknown): unknown {
1754
1769
  if (transform === null) return produce(cell, checked)
1755
1770
  // 4.7 — A `transform` RUNS UNTRACKED: a write can be made from inside a tracked body
1756
1771
  // (`watch(() => target.set(…))`, a recompute that writes), and run tracked, the transform's
1757
- // 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
1758
1773
  // those reads spliced into its cursor-ordered `sources`.
1759
1774
  const shaped = untracked(transform, checked, undefined)
1760
1775
  // 4.6 AND 4.9: a `transform` that returned a refusal stops the write here — nothing is
@@ -1829,7 +1844,7 @@ function writeStream(cell: Cell, value: unknown): unknown {
1829
1844
  function standWrite(cell: Cell, stream: Stream, value: unknown): number {
1830
1845
  // 7.9, as `produce` settles it.
1831
1846
  if (cell.owes === OWES_RELOAD) cell.owes = OWES_NOTHING
1832
- // 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.
1833
1848
  if (cell.servable) {
1834
1849
  const previous = heldValue(cell)
1835
1850
  if (
@@ -1840,7 +1855,7 @@ function standWrite(cell: Cell, stream: Stream, value: unknown): number {
1840
1855
  return cell.ordinal
1841
1856
  }
1842
1857
  cell.value = value
1843
- stream.materialisedAt = stream.version
1858
+ stream.materializedAt = stream.version
1844
1859
  stream.written = true
1845
1860
  const moved = !cell.servable || cell.failure !== undefined
1846
1861
  landChunks(cell)
@@ -2015,13 +2030,13 @@ function follow(cell: Cell, stream: Stream, inner: Cell): Error | undefined {
2015
2030
  if (standing !== null) unfollow(cell)
2016
2031
  // What `standWrite`'s identity gate compares against is what is served now.
2017
2032
  heldValue(cell)
2018
- stream.materialisedAt = stream.version
2033
+ stream.materializedAt = stream.version
2019
2034
  stream.written = true
2020
2035
  const stop = watchOver([inner], cell, (): undefined => {
2021
2036
  // The accumulation moved past the write. The drop is `wakeStreams`'s or the read's, not
2022
2037
  // this one's: a mirror runs inside the mark walk over `inner.readers`, and detaching here
2023
2038
  // would splice the list that walk is iterating.
2024
- if (!stream.written || stream.materialisedAt !== stream.version)
2039
+ if (!stream.written || stream.materializedAt !== stream.version)
2025
2040
  return undefined
2026
2041
  if (inner.owes !== OWES_NOTHING) settleOwed(inner)
2027
2042
  // 11.16: the followed value has nothing to serve, so neither has this.
@@ -2060,7 +2075,7 @@ function unserve(cell: Cell): void {
2060
2075
  }
2061
2076
 
2062
2077
  function mirror(cell: Cell, inner: Cell): void {
2063
- // 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
2064
2079
  // adopted value owes, rather than finding it unservable and giving up.
2065
2080
  if (inner.owes !== OWES_NOTHING) settleOwed(inner)
2066
2081
  const adopted = inner.stream
@@ -2121,7 +2136,7 @@ function dropAdoption(cell: Cell): void {
2121
2136
  * Makes an adopter forward an adopted stream's buffer, ring and ordinals.
2122
2137
  *
2123
2138
  * 11.15 FOR A STREAM: the adopter takes the adopted value's buffer, ring and ordinals by
2124
- * 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).
2125
2140
  * Mirroring the value instead copied the accumulation once per wake whether or not anything
2126
2141
  * read it, and handed a cursor over the adopter whole accumulations where it wanted chunks.
2127
2142
  */
@@ -2136,8 +2151,8 @@ function forwardStream(cell: Cell, inner: Cell, adopted: Stream): void {
2136
2151
  stream.chunks = adopted.chunks
2137
2152
  stream.version = adopted.version
2138
2153
  stream.visible = adopted.visible
2139
- if (adopted.written && adopted.materialisedAt === adopted.version) {
2140
- stream.materialisedAt = stream.version
2154
+ if (adopted.written && adopted.materializedAt === adopted.version) {
2155
+ stream.materializedAt = stream.version
2141
2156
  stream.written = true
2142
2157
  gateInto(cell, inner.value)
2143
2158
  }
@@ -2173,9 +2188,9 @@ function stopForwarding(cell: Cell): void {
2173
2188
  * THE ABORT. A body that reads a value which has not landed throws `NotLanded`, and
2174
2189
  * this catches it and returns — the body is ABANDONED, not retried in place. The read
2175
2190
  * that threw has already registered this cell as a reader of it (tracking happens
2176
- * 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.
2177
2192
  *
2178
- * "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
2179
2194
  * by the reach starting the work (1.7) — the loads are in flight before the body
2180
2195
  * runs, so the abort costs a re-entry rather than a round trip. The third, that
2181
2196
  * nothing on a server wakes an abandoned body, is answered by the sink rather than
@@ -2188,6 +2203,8 @@ function run(cell: Cell): void {
2188
2203
  // where the cascade would try: `a` reads `b` and `b` reads `a`. Without this the
2189
2204
  // recursion is unbounded and the failure is a stack overflow rather than a value.
2190
2205
  if (cell.running) return
2206
+ // A released orphan read again (12.14) — see `tombstone`.
2207
+ if (!cell.alive) revive(cell as unknown as Subscriber)
2191
2208
  cell.generation += 1
2192
2209
  cell.state = CLEAN
2193
2210
  cell.running = true
@@ -2199,9 +2216,7 @@ function run(cell: Cell): void {
2199
2216
  // THE RUN'S CURSOR, spelled out rather than behind a call frame on a path walked once
2200
2217
  // per node per write. Zero is also "has diverged nowhere yet": see `tracking.ts`.
2201
2218
  reader.cursor = 0
2202
- // The probe edge is rebuilt rather than cursored: a guard that stops being taken has
2203
- // to stop waking, and a body reads few probes where it reads many values.
2204
- if (reader.probeSources.length !== 0) detachProbes(reader)
2219
+ reader.probeCursor = 0
2205
2220
  let produced: unknown
2206
2221
  try {
2207
2222
  // 11.32 — UNDER THE VALUE'S OWN SCOPE, not the ambient one. A drain runs every body a
@@ -2211,6 +2226,8 @@ function run(cell: Cell): void {
2211
2226
  } catch (thrown) {
2212
2227
  TRACKING.reader = previous
2213
2228
  if (reader.cursor < reader.sources.length) truncate(reader)
2229
+ if (reader.probeCursor < reader.probeSources.length)
2230
+ truncateProbes(reader)
2214
2231
  cell.running = false
2215
2232
  if (thrown instanceof NotLanded) {
2216
2233
  cell.loading = true
@@ -2235,6 +2252,8 @@ function run(cell: Cell): void {
2235
2252
  // that is the only thing the truncation has to do — so the test is here and the call
2236
2253
  // is the rare arm.
2237
2254
  if (reader.cursor < reader.sources.length) truncate(reader)
2255
+ // And a guard this run did not take stops waking it.
2256
+ if (reader.probeCursor < reader.probeSources.length) truncateProbes(reader)
2238
2257
  cell.running = false
2239
2258
  // A VALUE THAT WAS ADOPTING AND IS NOT ANY MORE. Behind the `adoption` load so every
2240
2259
  // other recompute pays one field test rather than the brand read below it.
@@ -2513,7 +2532,7 @@ function acceptChunk(cell: Cell, stream: Stream, chunk: unknown): void {
2513
2532
  /**
2514
2533
  * Tells readers of every chunk landed so far, waking them at most once per task.
2515
2534
  *
2516
- * 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
2517
2536
  * wake goes out at most once per task (9.9).
2518
2537
  */
2519
2538
  function releaseChunks(cell: Cell, stream: Stream): void {
@@ -2527,7 +2546,7 @@ function releaseChunks(cell: Cell, stream: Stream): void {
2527
2546
  * The streams holding a wake, all drained by one task.
2528
2547
  *
2529
2548
  * THE STREAMS HOLDING A WAKE, and one task drains all of them — never a task per stream.
2530
- * 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
2531
2550
  * (12.12), so what waits is only the telling.
2532
2551
  */
2533
2552
  const WAKING: Cell[] = []
@@ -2602,18 +2621,18 @@ function landChunks(cell: Cell): void {
2602
2621
  }
2603
2622
 
2604
2623
  /**
2605
- * Materialises a stream's visible chunks into `value`, through both gates.
2624
+ * Materializes a stream's visible chunks into `value`, through both gates.
2606
2625
  *
2607
2626
  * 9.1 AND 9.8: the read that asks is where the accumulation becomes a `Stored`, through both
2608
2627
  * gates, once per chunk count however many reads ask.
2609
2628
  */
2610
- function materialise(cell: Cell, stream: Stream): void {
2611
- 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
2612
2631
  // 9.11: the accumulation moved past a write following a `Reactive`, which lets go of it here
2613
2632
  // where no chunk's wake did — a patch, or a run that closed empty. Only an own stream follows;
2614
2633
  // a forwarding one's `adoption` is what it forwards.
2615
2634
  if (cell.adoption !== null && stream.source === null) unfollow(cell)
2616
- stream.materialisedAt = stream.version
2635
+ stream.materializedAt = stream.version
2617
2636
  stream.written = false
2618
2637
  gateInto(cell, stream.chunks.slice(0, stream.visible))
2619
2638
  }
@@ -2658,7 +2677,7 @@ function gateThrough(cell: Cell, raw: unknown): void {
2658
2677
  */
2659
2678
  function heldValue(cell: Cell): unknown {
2660
2679
  const stream = cell.stream
2661
- if (stream !== null) materialise(cell, stream)
2680
+ if (stream !== null) materialize(cell, stream)
2662
2681
  return cell.value
2663
2682
  }
2664
2683
 
@@ -2821,7 +2840,7 @@ function readIndirect(cell: Cell): unknown {
2821
2840
  // 6.5: THE READ THAT FOLLOWS. Every value carrying an expiry arrives here and no
2822
2841
  // other value does, which is the whole of what `setDirect` buys.
2823
2842
  if (cell.expiry !== null) expire(cell)
2824
- if (cell.stream !== null) materialise(cell, cell.stream)
2843
+ if (cell.stream !== null) materialize(cell, cell.stream)
2825
2844
  if (!cell.servable) {
2826
2845
  // 2.3: a failed producer with nothing landed is the one case holding no value
2827
2846
  // and a refusal that explains the absence.
@@ -2847,7 +2866,7 @@ const EMPTY_RING = Object.freeze([]) as unknown as unknown[]
2847
2866
  * so a shared subgraph is re-walked once per path that reaches it, and a 26-cell DAG
2848
2867
  * with 130 edges cost 19,660 edge visits and 4,010 hash operations per probe read.
2849
2868
  *
2850
- * 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,
2851
2870
  * "already visited while the walk is still running" IMPLIES "not busy", because a busy
2852
2871
  * node short-circuits the whole walk at its first visit. So a second arrival at a
2853
2872
  * stamped node answers false without descending.
@@ -2864,7 +2883,7 @@ let PROBE_PASS = 0
2864
2883
  * Propagation, derived on the read that asks (11.22) rather than held as a
2865
2884
  * subscription per source. 11.20: a value is busy where a source it read is busy —
2866
2885
  * and the premise is what `busy` MEANS, not which trigger produced it, so a refresh
2867
- * 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.
2868
2887
  */
2869
2888
  function walkBusy(cell: Cell, pass: number): boolean {
2870
2889
  if (cell.loading) return true
@@ -2948,7 +2967,7 @@ function busyNow(cell: Cell): boolean {
2948
2967
  * serving the figure it had computed before — right by its own lights, and a lie about the
2949
2968
  * data. Nothing in the output says so, which is why this is a walk and not a field.
2950
2969
  *
2951
- * 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:
2952
2971
  * this walk short-circuits on NOT ready, so a second arrival at a node the walk is still
2953
2972
  * inside is a node that has not refused, and answering `true` there is answering what the
2954
2973
  * first visit will.
@@ -3034,7 +3053,7 @@ function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
3034
3053
  const waiter: Subscriber = newSubscriber([cell], null)
3035
3054
  waiter.notify = (): void => {
3036
3055
  // THE WAITER PULLS. A value built from a source that has just landed is marked and
3037
- // 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
3038
3057
  // pulling it answers from the run that aborted, and the await never settles.
3039
3058
  if (cell.state !== CLEAN) resolve(cell)
3040
3059
  if (untilStreaming && cell.stream !== null) {
@@ -3074,7 +3093,7 @@ function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
3074
3093
  * than discovered — and one source given is what `s.watch` means. What was here before was
3075
3094
  * a second copy of the subscriber, the disposer, the re-entrancy guard and 12.8, which is
3076
3095
  * exactly the divergence 12.9 forbids: the copy had no `catch` at all, so a throwing effect
3077
- * re-ran into the same throw on every write where `watch` stops.
3096
+ * reran into the same throw on every write where `watch` stops.
3078
3097
  *
3079
3098
  * The value is handed over rather than read, so the effect never reaches the tracked path
3080
3099
  * and never throws `NotLanded` from this source.
@@ -3290,6 +3309,7 @@ function owedBelow(cell: Cell): void {
3290
3309
  const readers = cell.readers
3291
3310
  for (let index = 0; index < readers.length; index += 1) {
3292
3311
  const reader = readers[index] as Subscriber
3312
+ if (!reader.alive) continue
3293
3313
  const below = (reader.isEffect
3294
3314
  ? reader.adopter
3295
3315
  : reader) as unknown as Cell | null
@@ -3701,7 +3721,7 @@ const TAGGED = new Map<string, Set<WeakRef<Cell>>>()
3701
3721
  * O(1) delete on eviction; here an evicted value drops its `ref` instead, and a handle whose value
3702
3722
  * no longer points back at it is dead, the same as one whose value was collected. Because a bare
3703
3723
  * sweep may never run, the filing that finds the array past `compactProducersAt` compacts it, and
3704
- * 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.
3705
3725
  */
3706
3726
  let filedProducers: WeakRef<Cell>[] = []
3707
3727
  let compactProducersAt = 1024
@@ -3713,14 +3733,13 @@ let compactProducersAt = 1024
3713
3733
  * subscribes to this, never to each entry — the probe edge `trackProbe` makes, from a node that
3714
3734
  * is nothing but that edge.
3715
3735
  */
3716
- type Signal = { readers: Subscriber[]; probeReaders: Subscriber[] }
3717
- const SIGNALS = new Map<string, Signal>()
3718
- const SIGNALS_BY_TAG = new Map<string, Signal[]>()
3736
+ const SIGNALS = new Map<string, Tracked>()
3737
+ const SIGNALS_BY_TAG = new Map<string, Tracked[]>()
3719
3738
  /**
3720
- * 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
3721
3740
  * empty set and matches nothing, where the bare selection matches every filed load in reach.
3722
3741
  */
3723
- const EVERY_PRODUCER: Signal = { readers: [], probeReaders: [] }
3742
+ const EVERY_PRODUCER = newTracked()
3724
3743
 
3725
3744
  function refOf(cell: Cell): WeakRef<Cell> {
3726
3745
  let ref = cell.ref
@@ -3839,13 +3858,13 @@ function trackSignal(tags: string[] | null): void {
3839
3858
  const reader = TRACKING.reader
3840
3859
  if (reader === null) return
3841
3860
  if (tags === null) {
3842
- trackProbe(EVERY_PRODUCER as unknown as Tracked, reader)
3861
+ trackProbe(EVERY_PRODUCER, reader)
3843
3862
  return
3844
3863
  }
3845
3864
  const key = JSON.stringify([...new Set(tags)].sort())
3846
3865
  let signal = SIGNALS.get(key)
3847
3866
  if (signal === undefined) {
3848
- signal = { readers: [], probeReaders: [] }
3867
+ signal = newTracked()
3849
3868
  SIGNALS.set(key, signal)
3850
3869
  for (const tag of tags) {
3851
3870
  const signals = SIGNALS_BY_TAG.get(tag)
@@ -3853,7 +3872,7 @@ function trackSignal(tags: string[] | null): void {
3853
3872
  else signals.push(signal)
3854
3873
  }
3855
3874
  }
3856
- trackProbe(signal as unknown as Tracked, reader)
3875
+ trackProbe(signal, reader)
3857
3876
  }
3858
3877
 
3859
3878
  function markSignals(tags: string[]): void {
@@ -3936,6 +3955,7 @@ export function refreshOver(tags: string[] | null): void {
3936
3955
  function readByAny(readers: Subscriber[]): boolean {
3937
3956
  for (let index = 0; index < readers.length; index += 1) {
3938
3957
  const reader = readers[index] as Subscriber
3958
+ if (!reader.alive) continue
3939
3959
  if (!reader.isMirror || reader.adopter !== null) return true
3940
3960
  }
3941
3961
  return false
@@ -4151,7 +4171,7 @@ export function isStreaming(value: unknown): boolean {
4151
4171
  *
4152
4172
  * 16.32 — THE CHUNKS A STREAMING VALUE HAS TOLD ITS READERS OF PAST `from`, for a writer sending each
4153
4173
  * once. Ungated they are read out of the accumulation in place, to `visible` rather than its length
4154
- * (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
4155
4175
  * n²/2 elements copied to send n. A schema or a transform has to see the whole accumulation (9.8),
4156
4176
  * so a gated value answers from what a read would.
4157
4177
  */
@@ -4368,12 +4388,13 @@ function releaseOrphan(cell: Cell): void {
4368
4388
  const stream = cell.stream
4369
4389
  if (stream !== null && stream.source === null) stopStream(stream)
4370
4390
  dropAdoption(cell)
4371
- detach(cell as unknown as Subscriber)
4372
4391
  cell.aborted = false
4373
4392
  if (cell.body !== null && stream === null) {
4393
+ // OWED A RUN, so a read that reaches it again runs it — and `run` revives it first.
4394
+ tombstone(cell as unknown as Subscriber)
4374
4395
  cell.state = DIRTY
4375
4396
  cell.direct = false
4376
- }
4397
+ } else detach(cell as unknown as Subscriber)
4377
4398
  }
4378
4399
 
4379
4400
  RELEASING.unread = (source: Tracked): void => releaseOrphan(source as Cell)
@@ -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,