@abide/abide 0.123.2 → 0.123.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/abide.ts +36 -65
  3. package/package.json +1 -1
  4. package/src/compiler/Diagnostics.ts +198 -0
  5. package/src/compiler/KeptChecker.ts +97 -0
  6. package/src/compiler/Lowering.ts +1 -1
  7. package/src/compiler/PositionedRefusal.ts +1 -1
  8. package/src/compiler/TypeSession.ts +62 -85
  9. package/src/compiler/appLayout.ts +28 -20
  10. package/src/compiler/argsDefaultsIn.ts +1 -1
  11. package/src/compiler/checkApp.ts +64 -68
  12. package/src/compiler/checkedShadow.ts +1 -1
  13. package/src/compiler/compileApp.ts +101 -70
  14. package/src/compiler/declaredExports.ts +2 -4
  15. package/src/compiler/emitClient.ts +1 -1
  16. package/src/compiler/formatAbide.ts +2 -2
  17. package/src/compiler/handlersUnder.ts +17 -29
  18. package/src/compiler/lineOf.ts +0 -19
  19. package/src/compiler/matchersOf.ts +3 -3
  20. package/src/compiler/mountsOf.ts +3 -3
  21. package/src/compiler/readSources.ts +16 -18
  22. package/src/compiler/readsIn.ts +1 -1
  23. package/src/compiler/serveLanguage.ts +23 -57
  24. package/src/compiler/sharedModuleWrites.ts +1 -1
  25. package/src/compiler/templateWarnings.ts +1 -2
  26. package/src/compiler/trackingWarnings.ts +1 -1
  27. package/src/server/CLIENT_BUILD.ts +4 -32
  28. package/src/server/RESUME_MILLISECONDS.ts +2 -0
  29. package/src/server/answerRequest.ts +11 -300
  30. package/src/server/answerRpc.ts +12 -54
  31. package/src/server/answerSocket.ts +8 -35
  32. package/src/server/appBuild.ts +377 -0
  33. package/src/server/appPlugins.ts +15 -17
  34. package/src/server/appTree.ts +6 -5
  35. package/src/server/argsContract.ts +209 -0
  36. package/src/server/bridgeRelay.ts +153 -65
  37. package/src/server/claudeCode.ts +30 -127
  38. package/src/server/config.ts +6 -7
  39. package/src/server/createApp.ts +30 -64
  40. package/src/server/devWorker.ts +11 -15
  41. package/src/server/handlerFileMount.ts +40 -0
  42. package/src/server/index.ts +2 -2
  43. package/src/server/packageDirectory.ts +14 -0
  44. package/src/server/preload.ts +3 -2
  45. package/src/server/principal.ts +4 -3
  46. package/src/server/remote.ts +1 -1
  47. package/src/server/runCompiled.ts +1 -1
  48. package/src/server/runDesktop.ts +1 -1
  49. package/src/server/seedBuffer.ts +1 -1
  50. package/src/server/serveDev.ts +24 -58
  51. package/src/server/servedApp.ts +390 -0
  52. package/src/server/signInChanged.ts +20 -0
  53. package/src/server/surfaceOf.ts +2 -1
  54. package/src/server/versionOf.ts +6 -15
  55. package/src/shared/applyPatch.ts +1 -1
  56. package/src/shared/muxFrames.ts +1 -1
  57. package/src/shared/reactive/Keyed.ts +1 -1
  58. package/src/shared/reactive/Reactive.ts +21 -8
  59. package/src/shared/reactive/argsContract.ts +131 -4
  60. package/src/shared/reactive/index.ts +1 -0
  61. package/src/shared/reactive/patchLog.ts +79 -0
  62. package/src/shared/reactive/patchMarks.ts +34 -0
  63. package/src/shared/reactive/recordWrites.ts +11 -14
  64. package/src/shared/reactive/share.ts +4 -3
  65. package/src/shared/rpc.ts +12 -19
  66. package/src/ui/ArmRegion.ts +10 -4
  67. package/src/ui/Level.ts +11 -20
  68. package/src/ui/LevelChain.ts +272 -0
  69. package/src/ui/Router.ts +54 -245
  70. package/src/ui/Scope.ts +17 -3
  71. package/src/ui/TryRegion.ts +22 -23
  72. package/src/ui/bindSpread.ts +2 -4
  73. package/src/ui/listenForRoutes.ts +3 -10
  74. package/src/ui/outboxQueue.ts +21 -3
  75. package/src/ui/reconcile.ts +4 -6
  76. package/src/ui/routing.ts +2 -1
  77. package/src/ui/rows.ts +2 -2
  78. package/src/ui/setAttribute.ts +12 -3
  79. package/src/ui/socketWire.ts +26 -11
  80. package/src/compiler/Finding.ts +0 -2
  81. package/src/compiler/Refusals.ts +0 -65
  82. package/src/compiler/formatDiagnosis.ts +0 -20
  83. package/src/server/appDirectories.ts +0 -9
  84. package/src/server/argsFor.ts +0 -39
  85. package/src/server/buildDirectory.ts +0 -10
  86. package/src/server/coerceArgs.ts +0 -109
  87. package/src/server/derivedArgs.ts +0 -26
  88. package/src/server/runBuild.ts +0 -24
  89. package/src/server/serveBuild.ts +0 -144
  90. package/src/server/testBuild.ts +0 -220
  91. package/src/shared/reactive/FOLLOWED.ts +0 -8
  92. package/src/shared/reactive/PATCHES.ts +0 -11
  93. package/src/shared/reactive/PATCH_SINKS.ts +0 -26
  94. package/src/shared/reactive/canonicalKey.ts +0 -95
@@ -0,0 +1,272 @@
1
+ import type { Params } from '#shared/matchRoute.ts'
2
+ import type { ShareScope } from '#shared/reactive/share.ts'
3
+ import { route } from '#shared/route.ts'
4
+ import { setUpLevels } from '#shared/setUpLevels.ts'
5
+
6
+ import { LAYER_MOUNT } from './LAYER_NAMES.ts'
7
+ import { Level } from './Level.ts'
8
+ import { beginLayer, endLayer } from './measureLayer.ts'
9
+ import { fillRegion, type Region } from './regions.ts'
10
+ import type { Scope, Stoppable } from './Scope.ts'
11
+
12
+ /**
13
+ * A page's client mount, called against the region the router owns.
14
+ *
15
+ * The same default export a page already has — the router is a CALLER of a page exactly as a page
16
+ * is a caller of a component, which is why nothing about the emitted module changes to be routable.
17
+ */
18
+ export type PageMount = (
19
+ root: Region,
20
+ props?: Record<string, unknown>,
21
+ // 39.6 — A LEVEL'S HEAD IS ITS OWN MOUNT'S TO APPLY, from inside the instance its slots read
22
+ // (`emitClient`), so the router hands the module a region and says it is the router by handing
23
+ // it the level it stands at — a file invoked as a component too is mounted without it, and
24
+ // applies no head a row. 39.14 — the level is where the mount leaves its head for the router
25
+ // to read when it withdraws.
26
+ routed?: Level,
27
+ ) => Scope
28
+
29
+ export type PageModule = { default: PageMount }
30
+
31
+ /**
32
+ * The routed chain standing in the document — its layouts outermost first and the page last — and
33
+ * the head they contribute (39.1, 39.14).
34
+ *
35
+ * 41.11 — THE CHAIN HOLDS ITS LEVELS rather than each level owning the one below it, which is what
36
+ * makes a shared layout something a navigation can KEEP: a scope stopped from above takes its
37
+ * children with it, and a layout that survives a navigation must not take the page it was holding.
38
+ *
39
+ * 39.14 — EVERY WAY A LEVEL LEAVES IS BRACKETED HERE: the head reconciled while the leaving levels
40
+ * still read what is on screen, so what the server painted for them is adopted as theirs, and again
41
+ * once they are gone — and a leaving level's contributions are cleared before its scope stops, or
42
+ * each component's `HeadContribution.stop` would withdraw and reconcile on its own.
43
+ */
44
+ export class LevelChain {
45
+ /** The region the outermost level mounts into. */
46
+ private readonly root: Region
47
+ private readonly levels: Level[]
48
+ /**
49
+ * The modules the address resolved to, one per level.
50
+ *
51
+ * A kept layout's `<slot/>` mounts from it whenever its region opens — read at that moment
52
+ * rather than captured when the layout mounted, or a slot reopened after a navigation put back
53
+ * the page the reader had left.
54
+ */
55
+ private modules: PageModule[]
56
+
57
+ constructor(root: Region) {
58
+ this.root = root
59
+ this.levels = []
60
+ this.modules = []
61
+ }
62
+
63
+ /** How many levels stand. */
64
+ get depth(): number {
65
+ return this.levels.length
66
+ }
67
+
68
+ /** The scope of the level at `at` — negative counts from the page — or `undefined`. */
69
+ scopeAt(at: number): Scope | undefined {
70
+ return this.levels.at(at)?.scope
71
+ }
72
+
73
+ /** Reconciles the document head from every level standing (`Level.reconcile`). */
74
+ reconcile(): void {
75
+ Level.reconcile(this.levels)
76
+ }
77
+
78
+ /**
79
+ * How many levels of `arriving` are already standing.
80
+ *
81
+ * Layouts only: a page reached here is another route's, the same route's being followed instead
82
+ * (30.23). EVERY STANDING LEVEL IS COMPARED, the page included: a page is never a layout, so it
83
+ * cannot match one, and a layout whose slot is closed stands with nothing below it.
84
+ *
85
+ * By the MOUNT rather than by the loader or the module record: the table holds an arrow per
86
+ * row, so two routes under one layout name the same file through two different functions, and a
87
+ * table written by hand may answer with a fresh record each call.
88
+ */
89
+ sharedWith(arriving: PageModule[]): number {
90
+ const layouts = arriving.length - 1
91
+ let shared = 0
92
+ while (
93
+ shared < layouts &&
94
+ shared < this.levels.length &&
95
+ (this.modules[shared] as PageModule).default ===
96
+ (arriving[shared] as PageModule).default
97
+ )
98
+ shared += 1
99
+ return shared
100
+ }
101
+
102
+ /**
103
+ * Mounts `modules` from the top into the root region the server painted. No head is reconciled:
104
+ * a painted level's mount hydrates, and the head is the one the server wrote.
105
+ */
106
+ hydrate(modules: PageModule[]): void {
107
+ this.modules = modules
108
+ this.mountMeasured(0, this.root)
109
+ }
110
+
111
+ /**
112
+ * Keeps the first `kept` levels and mounts `modules` below them, reconciling the head around it.
113
+ *
114
+ * `between` runs once the leaving levels have stopped and their region is emptied, and before
115
+ * the arriving ones are set up — the address written and `route` moved, which a setup reads.
116
+ *
117
+ * The arriving part goes into the root where nothing is kept, and into the deepest kept layout's
118
+ * slot where something is — NOWHERE where that slot's region is closed: the slot mounts it off
119
+ * `modules` when it opens, and not into a region no longer in the document.
120
+ */
121
+ replace(kept: number, modules: PageModule[], between: () => void): void {
122
+ this.reconcile()
123
+ this.stopFrom(kept)
124
+ const into =
125
+ kept === 0 ? this.root : (this.levels[kept - 1] as Level).slot
126
+ // EMPTIED THROUGH THE PRIMITIVE EVERY OTHER REGION IS, which carries the 41.10 refusal for a
127
+ // region no longer in a document that a hand-written walk swallows.
128
+ if (into !== undefined) fillRegion(into)
129
+ between()
130
+ this.modules = modules
131
+ // 39.6 — each arriving level applies its own head as it mounts into its emptied region; a
132
+ // kept layout is not mounted again, so neither is its head.
133
+ if (into !== undefined) this.mountMeasured(kept, into)
134
+ // 39.14 — what the leaving levels keyed and the arriving ones did not goes back to the
135
+ // nearest kept layout's, else leaves; their keyless nodes leave with them (39.16).
136
+ this.reconcile()
137
+ }
138
+
139
+ /**
140
+ * AT THE TOP OF THE CHAIN, NOT INSIDE `mountFrom`: it recurses one level per layout, and
141
+ * `shares()` SUMS every measure of a name inside the op — so a pair inside the recursion would
142
+ * count the innermost level once per level above it.
143
+ */
144
+ private mountMeasured(from: number, into: Region): void {
145
+ beginLayer(LAYER_MOUNT)
146
+ try {
147
+ this.mountFrom(from, into)
148
+ } finally {
149
+ endLayer(LAYER_MOUNT)
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Stops every level from `at` down, inside out, and drops them.
155
+ *
156
+ * Stopped from the INSIDE OUT, so a layout's disposers run after the page's — the order a
157
+ * scope's own teardown already has, and the one an author reading two `watch` cleanups in
158
+ * two files would expect.
159
+ */
160
+ private stopFrom(at: number): void {
161
+ for (let index = this.levels.length - 1; index >= at; index -= 1) {
162
+ const level = this.levels[index] as Level
163
+ // OUT OF THE CHAIN BEFORE IT STOPS, so a slot stop it fires on the way finds it gone
164
+ // (`release`) and leaves the head to whoever stopped it.
165
+ this.levels.length = index
166
+ // The level's head leaves whole, so none of its components withdraws its own.
167
+ level.contributed.clear()
168
+ level.scope?.stop()
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Mounts the chain from level `at` into `region`, each level handed the next as `children`.
174
+ *
175
+ * 35.1 — each level is handed the one below it as `children` (32.18), so a layout puts the page
176
+ * in its `<slot/>` with the same mechanism a caller writing markup between two tags uses — and
177
+ * the chain learns where that slot is on the way past, which is what lets it swap the page
178
+ * there later without the layout knowing.
179
+ *
180
+ * THE CHILD'S SCOPE IS THE CHAIN'S, not the layout's, so a kept layout cannot stop the page
181
+ * arriving under it. What `mount` hands the layout is a stop for WHATEVER STANDS IN THE SLOT
182
+ * when the slot's region leaves — a `<slot/>` inside a closed `{#if}` — rather than for the
183
+ * page that happened to be mounted first. And the slot mounts from `modules`, so a slot that
184
+ * opens again mounts the page the address resolves to now.
185
+ */
186
+ private mountFrom(at: number, region: Region): void {
187
+ setUpLevels(
188
+ at,
189
+ this.modules.length,
190
+ this.levels[at - 1]?.share,
191
+ ROUTE_PROPS,
192
+ (below, props) => this.setUp(below, region, props),
193
+ )
194
+ this.markUp(at, region)
195
+ }
196
+
197
+ /**
198
+ * Runs level `at`'s setup over `props`, leaving its markup on the level for `markUp`, and
199
+ * answers the share scope the setup left.
200
+ */
201
+ private setUp(
202
+ at: number,
203
+ region: Region,
204
+ props: Record<string, (() => unknown) | undefined>,
205
+ ): ShareScope | null {
206
+ const module = this.modules[at] as PageModule
207
+ const level = new Level(this)
208
+ this.levels[at] = level
209
+ if (at < this.modules.length - 1)
210
+ props.children = () => ({
211
+ // NO BUILD, because the level below carries its own (32.17): a module a route
212
+ // reaches fills the region it is mounted against when that region is empty, so
213
+ // the build and the mount are one call rather than two.
214
+ build: undefined,
215
+ mount: (slot: Region): Stoppable => {
216
+ // The level below as `mountFrom` set it up, the first time its slot opens; a
217
+ // slot opening again sets it up afresh.
218
+ const pending = this.levels[at + 1]?.markup !== undefined
219
+ if (!pending) this.release(level, at)
220
+ level.slot = slot
221
+ if (pending) this.markUp(at + 1, slot)
222
+ else this.mountFrom(at + 1, slot)
223
+ return { stop: () => this.release(level, at) }
224
+ },
225
+ })
226
+ // The region a level's markup lands in is its slot above, known only once that markup
227
+ // runs; a module mounted by the chain leaves its markup rather than using this one.
228
+ // A deferred level answers the scope its setup made, so the watches that setup opened are
229
+ // stopped with the level even where its markup never runs — a slot that never opens.
230
+ level.scope = level.mount(() => module.default(region, props, level))
231
+ return level.share
232
+ }
233
+
234
+ /** Runs the markup level `at`'s setup left, into `region`. */
235
+ private markUp(at: number, region: Region): void {
236
+ const level = this.levels[at] as Level
237
+ const markup = level.markup
238
+ if (markup === undefined) return
239
+ level.markup = undefined
240
+ level.scope = level.mount(() => markup(region))
241
+ }
242
+
243
+ /**
244
+ * Stops what stands below `level` and forgets its slot.
245
+ *
246
+ * A no-op once the level itself has been replaced, which is how a stop handed to a layout that
247
+ * has since left does nothing.
248
+ */
249
+ private release(level: Level, at: number): void {
250
+ if (this.levels[at] !== level) return
251
+ level.slot = undefined
252
+ // 39.14, 39.16 — THE LEVELS BELOW LEAVE WITH THE SLOT'S REGION, and their head with them.
253
+ this.reconcile()
254
+ this.stopFrom(at + 1)
255
+ this.reconcile()
256
+ }
257
+ }
258
+
259
+ /**
260
+ * The props a routed page or layout is set up over, `children` aside: `params` as a live accessor.
261
+ *
262
+ * 33.4 — A PROP IS AN ACCESSOR. A page is mounted as any other component is, so its one prop is
263
+ * passed the way `renderComponent` passes a child's and the way `answerRequest` paints one: a
264
+ * function per key. Passed as the plain record it lowered to `params()` on a value that was not
265
+ * one, so every page with a `[param]` in its address hydrated dead — with its server markup
266
+ * already on screen, which is what kept it quiet.
267
+ *
268
+ * 33.5 — AND A LIVE ONE: `params` reads `route.params`, which every navigation writes. A layout
269
+ * kept across `/users/1` -> `/users/2` is not mounted again, so an accessor over the params it was
270
+ * mounted with went on answering user 1.
271
+ */
272
+ const ROUTE_PROPS = { params: (): Params => route.params() }