@abide/abide 0.104.0 → 0.105.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/abide.ts +80 -173
  3. package/docs/REGISTRY.md +17 -17
  4. package/docs/RULEBOOK.md +50 -12
  5. package/docs/abide.md +9 -9
  6. package/package.json +1 -1
  7. package/src/compiler/Lowering.ts +15 -5
  8. package/src/compiler/Refusals.ts +57 -0
  9. package/src/compiler/TypeSession.ts +45 -86
  10. package/src/compiler/appLayout.ts +447 -0
  11. package/src/compiler/appMap.ts +21 -20
  12. package/src/compiler/checkApp.ts +61 -97
  13. package/src/compiler/checkedShadow.ts +11 -9
  14. package/src/compiler/classifyRead.ts +3 -6
  15. package/src/compiler/compileApp.ts +180 -512
  16. package/src/compiler/componentGraph.ts +2 -71
  17. package/src/compiler/declaredExports.ts +79 -60
  18. package/src/compiler/emitClient.ts +11 -13
  19. package/src/compiler/emitComponent.ts +21 -6
  20. package/src/compiler/emitSource.ts +20 -20
  21. package/src/compiler/handlersUnder.ts +46 -36
  22. package/src/compiler/hookCalls.ts +152 -0
  23. package/src/compiler/mountsOf.ts +31 -54
  24. package/src/compiler/readSources.ts +27 -42
  25. package/src/compiler/readsIn.ts +5 -16
  26. package/src/compiler/resolverIn.ts +77 -0
  27. package/src/server/abideModules.ts +92 -123
  28. package/src/server/answerRequest.ts +18 -4
  29. package/src/server/answerSocket.ts +136 -76
  30. package/src/server/answerValue.ts +28 -15
  31. package/src/server/appTree.ts +49 -71
  32. package/src/server/claudeApi.ts +2 -5
  33. package/src/server/clientBundle.ts +198 -0
  34. package/src/server/coerceArgs.ts +1 -1
  35. package/src/server/commands.ts +93 -57
  36. package/src/server/createApp.ts +90 -16
  37. package/src/server/deferredBuilds.ts +175 -0
  38. package/src/server/devWorker.ts +107 -319
  39. package/src/server/faceOf.ts +120 -0
  40. package/src/server/fenceServer.ts +10 -18
  41. package/src/server/index.ts +3 -0
  42. package/src/server/placement.ts +167 -0
  43. package/src/server/remote.ts +14 -42
  44. package/src/server/render.ts +25 -17
  45. package/src/server/renderComponent.ts +9 -5
  46. package/src/server/requestScope.ts +10 -4
  47. package/src/server/responseRelay.ts +389 -0
  48. package/src/server/runCompiled.ts +9 -12
  49. package/src/server/runDesktop.ts +6 -9
  50. package/src/server/serveBuild.ts +26 -73
  51. package/src/server/serveDev.ts +19 -176
  52. package/src/server/streamEvent.ts +29 -0
  53. package/src/server/withAbideSources.ts +2 -1
  54. package/src/shared/HandlerRoute.ts +68 -0
  55. package/src/shared/REFUSAL_KEY.ts +5 -0
  56. package/src/shared/answerOf.ts +105 -78
  57. package/src/shared/filledPath.ts +1 -9
  58. package/src/shared/framing.ts +127 -128
  59. package/src/shared/queryText.ts +15 -7
  60. package/src/shared/reactive/Cursor.ts +4 -2
  61. package/src/shared/reactive/Reactive.ts +285 -197
  62. package/src/shared/reactive/index.ts +11 -8
  63. package/src/shared/reactive/pairReactive.ts +1 -1
  64. package/src/shared/reactive/scoped.ts +4 -3
  65. package/src/shared/reactive/share.ts +11 -1
  66. package/src/shared/routePattern.ts +0 -123
  67. package/src/shared/rpc.ts +17 -24
  68. package/src/shared/rpcRequest.ts +18 -47
  69. package/src/shared/takeEarlyCall.ts +12 -37
  70. package/src/shared/url.ts +1 -1
  71. package/src/ui/Level.ts +276 -0
  72. package/src/ui/List.ts +17 -17
  73. package/src/ui/Router.ts +20 -87
  74. package/src/ui/applyHead.ts +92 -79
  75. package/src/ui/index.ts +4 -2
  76. package/src/ui/leave.ts +5 -0
  77. package/src/ui/reconcile.ts +23 -104
  78. package/src/ui/regions.ts +27 -2
  79. package/src/ui/rows.ts +123 -2
  80. package/src/ui/wire.ts +12 -27
  81. package/src/compiler/hooksCalled.ts +0 -35
  82. package/src/server/clientBuildOptions.ts +0 -54
  83. package/src/server/refuseAddressCollision.ts +0 -37
  84. package/src/shared/reactive/chunksOfStream.ts +0 -33
  85. package/src/ui/MOUNTING_LEVEL.ts +0 -18
@@ -0,0 +1,276 @@
1
+ import {
2
+ currentShare,
3
+ leaveShare,
4
+ resumeShare,
5
+ type ShareScope,
6
+ } from '#shared/reactive/share.ts'
7
+ import { untracked } from '#shared/reactive/tracking.ts'
8
+ import { type Disposer, watchTracked } from '#shared/reactive/watch.ts'
9
+ import { applyHead, withdrawHead } from './applyHead.ts'
10
+ import { isEmptyRegion, type Region } from './regions.ts'
11
+ import type { Scope } from './Scope.ts'
12
+
13
+ /**
14
+ * ONE LEVEL OF A ROUTED CHAIN — a layout or the page — and the head it contributes (39.1, 39.14).
15
+ *
16
+ * 41.11 — the router holds these rather than letting each level own the one below it, which is
17
+ * what makes a shared layout something it can KEEP: a scope stopped from above takes its children
18
+ * with it, and a layout that survives a navigation must not take the page it was holding a moment
19
+ * ago. The module standing here is the router's `chain` at the same index.
20
+ *
21
+ * ON EVERY PAGE'S STATIC CLOSURE, because `contributeHead` is on the runtime barrel — so it knows
22
+ * nothing of the router, and the router knows it only through `levels`.
23
+ */
24
+ export class Level {
25
+ /** The chain this level stands in, and where: a live head yields to every level below it. */
26
+ levels: Level[]
27
+ at: number
28
+ scope: Scope | undefined = undefined
29
+ /**
30
+ * Where this level's `<slot/>` is, learned when the level below it mounted there, and
31
+ * `undefined` again once that region leaves — a `<slot/>` inside a closed `{#if}` is nowhere.
32
+ */
33
+ slot: Region | undefined = undefined
34
+ /**
35
+ * 41.19 — this level's markup, left by its mount once its setup has run, for the router to run
36
+ * once every level below has been set up too; `undefined` once it has run, or where the module
37
+ * has none.
38
+ */
39
+ markup: ((region: Region) => Scope | undefined) | undefined = undefined
40
+ /**
41
+ * 10.5 — the share scope this level's setup ran in, for the level below to be set up inside.
42
+ * `null` where the module left none.
43
+ */
44
+ share: ShareScope | null = null
45
+ /**
46
+ * The head the components built on the client contributed while this level mounts, applied
47
+ * once its `mount` returns; `undefined` outside one, where a contribution applies at once.
48
+ */
49
+ unapplied: string | undefined = undefined
50
+ /**
51
+ * This level's own head markup as it reads now; `undefined` for a level with no `<head>`.
52
+ *
53
+ * 39.14 — READ WHEN THE LEVEL LEAVES OR STAYS ABOVE ONE THAT DOES, which is why it is the
54
+ * markup's thunk and not the markup: a layout's `<title>{name}</title>` puts back the name it
55
+ * reads at that moment.
56
+ */
57
+ head: (() => string) | undefined = undefined
58
+ /**
59
+ * The heads of the components mounted in this level, after its own and in the order they
60
+ * mounted. A COLLECTION, NOT A CLOSURE PER CONTRIBUTION: a `{#for}` of components with a
61
+ * `<head>` contributes once a row, and a chain of closures is read through as deep a stack. A
62
+ * `Set`, so a row leaving with its region (39.14, 39.16) is withdrawn without a walk of the rest.
63
+ */
64
+ contributed = new Set<HeadContribution>()
65
+
66
+ constructor(levels: Level[], at: number) {
67
+ this.levels = levels
68
+ this.at = at
69
+ }
70
+
71
+ /** Left by the level's mount (`emitClient`), painted or not. */
72
+ setHead(head: () => string): void {
73
+ this.head = head
74
+ }
75
+
76
+ /** Left by the level's mount for the router to run once the chain is set up (41.19). */
77
+ leaveMarkup(
78
+ markup: (region: Region) => Scope | undefined,
79
+ share: ShareScope | null,
80
+ ): void {
81
+ this.markup = markup
82
+ this.share = share
83
+ }
84
+
85
+ /** The level's head as it reads now: its own, then every component's, deepest last (39.4). */
86
+ headMarkup(): string {
87
+ let markup = this.head?.() ?? ''
88
+ for (const one of this.contributed) markup += one.head()
89
+ return markup
90
+ }
91
+
92
+ /**
93
+ * Joins a component's `<head>` to this level's. Applied once the level is mounted where the
94
+ * component builds its own markup; a region the server painted already carries the head the
95
+ * server wrote. It follows what it reads from here (39.13): A WATCH WHETHER OR NOT IT READS
96
+ * ANYTHING THAT MOVES, one per component carrying a `<head>`, where a flag would be a second
97
+ * path through here and an argument in every component that has one.
98
+ */
99
+ contribute(region: Region, head: () => string): HeadContribution {
100
+ const contribution = new HeadContribution(this, head)
101
+ this.contributed.add(contribution)
102
+ contribution.dispose = watchTracked(() => contribution.follow())
103
+ // Batched while this level mounts; applied at once by an arm or a row mounted after it,
104
+ // under whatever is deeper (39.4) — only the levels below, since it was added last here.
105
+ const text = contribution.last ?? ''
106
+ if (isEmptyRegion(region))
107
+ if (this.unapplied !== undefined) this.unapplied += text
108
+ else
109
+ applyHead(
110
+ text,
111
+ untracked(Level.headsOf, this.levels, this.at + 1),
112
+ )
113
+ return contribution
114
+ }
115
+
116
+ /**
117
+ * Writes `text` over the `last` that `contributor` in this level wrote.
118
+ *
119
+ * 39.4 — IT YIELDS TO EVERY CONTRIBUTOR DEEPER, read as they stand at the write and untracked:
120
+ * the components that contributed after it, then every level below, so a layout's
121
+ * `<title>{site}</title>` moving does not write over the page's `<title>`, and the page's head
122
+ * moving wakes only the page's own watch. 39.15 — its keyless nodes replace the ones it wrote.
123
+ */
124
+ reapply(text: string, last: string, contributor: HeadContribution): void {
125
+ applyHead(
126
+ text,
127
+ untracked(Level.headsAfter, this, contributor),
128
+ last,
129
+ // Read only where a key `last` held is withdrawn.
130
+ () => untracked(Level.headsBefore, this, contributor),
131
+ )
132
+ }
133
+
134
+ /**
135
+ * The heads either side of `contribution` in `level` (`headsBefore`, `headsAfter`). One `level`
136
+ * does not hold is the level's own head (`followHead`), which stands before every component's.
137
+ */
138
+ static headsAround(
139
+ level: Level,
140
+ contribution: HeadContribution,
141
+ ): [string, string] {
142
+ return [
143
+ Level.headsBefore(level, contribution),
144
+ Level.headsAfter(level, contribution),
145
+ ]
146
+ }
147
+
148
+ /** Every level above `level`, then what stands before `contribution` in it. */
149
+ static headsBefore(level: Level, contribution: HeadContribution): string {
150
+ let outer = Level.headsOf(level.levels, 0, level.at)
151
+ if (!level.contributed.has(contribution)) return outer
152
+ outer += level.head?.() ?? ''
153
+ for (const one of level.contributed) {
154
+ if (one === contribution) break
155
+ outer += one.head()
156
+ }
157
+ return outer
158
+ }
159
+
160
+ /** What stands after `contribution` in `level`, then every level below it. */
161
+ static headsAfter(level: Level, contribution: HeadContribution): string {
162
+ let inner = ''
163
+ let past = !level.contributed.has(contribution)
164
+ for (const one of level.contributed)
165
+ if (past) inner += one.head()
166
+ else if (one === contribution) past = true
167
+ return inner + Level.headsOf(level.levels, level.at + 1)
168
+ }
169
+
170
+ /**
171
+ * 39.13 — A HEAD THAT READS A VALUE FOLLOWS IT for as long as the level is mounted, which is
172
+ * `scope`'s life. The first run only subscribes: the head it would write is already there,
173
+ * written by the server or applied by the level's mount ahead of its fill (39.6). Followed as a
174
+ * component's is, by a contribution the level does not hold (`headsAround`).
175
+ */
176
+ followHead(scope: Scope): void {
177
+ const own = new HeadContribution(this, this.head as () => string)
178
+ scope.watch(() => own.follow())
179
+ }
180
+
181
+ /**
182
+ * Runs `run` with this the level components add their `<head>` to (39.1) — and applies what
183
+ * those built on the client contributed once it returns, rather than once a component.
184
+ */
185
+ mount<Result>(run: () => Result): Result {
186
+ // 39.1 — THE LEVEL RIDES THE SHARE SCOPE, so a component finds it as it finds a shared key,
187
+ // whether it mounts now or in an arm or a row after (`contributeHead`).
188
+ const back = resumeShare({
189
+ parent: currentShare(),
190
+ values: new Map(),
191
+ level: this,
192
+ })
193
+ this.unapplied = ''
194
+ let unapplied: string
195
+ let result: Result
196
+ try {
197
+ result = run()
198
+ } finally {
199
+ unapplied = this.unapplied
200
+ this.unapplied = undefined
201
+ leaveShare(back)
202
+ }
203
+ // 39.4 — under whatever a level below wrote while this one was mounting: the page in a
204
+ // layout's `<slot/>` applied its own head ahead of the components that layout holds.
205
+ if (unapplied !== '')
206
+ applyHead(unapplied, Level.headsOf(this.levels, this.at + 1))
207
+ return result
208
+ }
209
+
210
+ /** The heads of `levels` from `from` to `to`, outermost first. */
211
+ static headsOf(levels: Level[], from: number, to = levels.length): string {
212
+ let markup = ''
213
+ for (let at = from; at < to; at += 1)
214
+ markup += (levels[at] as Level).headMarkup()
215
+ return markup
216
+ }
217
+ }
218
+
219
+ /**
220
+ * One component's `<head>` in its level — what the component's scope owns, so the head leaves with
221
+ * the component's region (39.14, 39.16).
222
+ */
223
+ export class HeadContribution {
224
+ level: Level
225
+ head: () => string
226
+ /** What this contribution last wrote, or `undefined` before its first run. */
227
+ last: string | undefined = undefined
228
+ dispose: Disposer | undefined = undefined
229
+
230
+ constructor(level: Level, head: () => string) {
231
+ this.level = level
232
+ this.head = head
233
+ }
234
+
235
+ /** 39.13 — the first run only subscribes, as a level's own does. */
236
+ follow(): void {
237
+ const text = this.head()
238
+ const last = this.last
239
+ if (last !== undefined && text !== last)
240
+ this.level.reapply(text, last, this)
241
+ this.last = text
242
+ }
243
+
244
+ /**
245
+ * 39.14 — WITHDRAWN AS A LEAVING LEVEL'S IS, its keys going back to what stands outside it and
246
+ * staying where something deeper holds them. Nothing where the level is leaving too: the router
247
+ * withdraws the whole level once, and cleared its contributions before stopping it.
248
+ */
249
+ stop(): void {
250
+ this.dispose?.()
251
+ const level = this.level
252
+ if (!level.contributed.has(this)) return
253
+ withdrawHead(this.last ?? '', () =>
254
+ untracked(Level.headsAround, level, this),
255
+ )
256
+ level.contributed.delete(this)
257
+ }
258
+ }
259
+
260
+ /**
261
+ * A component's `<head>`, added to the page or layout it stands in (39.1) — found through the share
262
+ * scope the component runs in, so an arm or a row mounted after its level finds it as its first
263
+ * mount did (39.13).
264
+ *
265
+ * Joined to that level's head, so it is withdrawn as the level's is when it leaves (39.14), and
266
+ * deeper than the level's own, so it wins by key (39.4). Nothing outside a routed level.
267
+ */
268
+ export function contributeHead(
269
+ region: Region,
270
+ head: () => string,
271
+ ): HeadContribution | undefined {
272
+ return (currentShare()?.level as Level | undefined)?.contribute(
273
+ region,
274
+ head,
275
+ )
276
+ }
package/src/ui/List.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import { listOf } from '#shared/listOf.ts'
2
2
  import { untracked } from '#shared/reactive/tracking.ts'
3
3
  import { HYDRATE_LOG, Mismatch, warnMismatch } from './Mismatch.ts'
4
- import { dropRow, makeRow, type Row, reconcile, rowOf } from './reconcile.ts'
4
+ import { reconcile } from './reconcile.ts'
5
5
  import { isEmptyRegion, type Region, removeUntil } from './regions.ts'
6
- import { rowsIn } from './rows.ts'
7
- import type { Scope } from './Scope.ts'
6
+ import {
7
+ dropRow,
8
+ makeRow,
9
+ type Row,
10
+ type RowBuilders,
11
+ rowOf,
12
+ rowsIn,
13
+ } from './rows.ts'
8
14
 
9
15
  /**
10
16
  * The state of one `{#for}` block, shared by its reconcile and its async append.
@@ -253,21 +259,15 @@ async function pump(list: List, source: AsyncIterable<unknown>): Promise<void> {
253
259
  }
254
260
  // Appended, so its position IS the index it arrived at.
255
261
  list.rows.push(
256
- makeRow(list, parent, region.close, list.key(item, at), item, at),
262
+ makeRow(
263
+ list.setUp,
264
+ parent,
265
+ region.close,
266
+ list.key(item, at),
267
+ item,
268
+ at,
269
+ ),
257
270
  )
258
271
  at += 1
259
272
  }
260
273
  }
261
-
262
- export type RowBuilders = {
263
- /**
264
- * A row's nodes, BUILT in the document the list stands in — handed over by the reconcile
265
- * rather than found by the row, which is the one place a row's document is decided.
266
- */
267
- make: (document: Document, item: unknown, at: number) => Node[]
268
- /**
269
- * Handed the row's RECORD rather than its nodes: a row it rebuilds (41.10) writes its new
270
- * first node back there.
271
- */
272
- mount: (row: Row, item: unknown, at: number) => Scope
273
- }
package/src/ui/Router.ts CHANGED
@@ -8,9 +8,9 @@ import { arrive, route } from '#shared/route.ts'
8
8
  import { rankRoutes, type Segment } from '#shared/routePattern.ts'
9
9
  import { setUpLevels } from '#shared/setUpLevels.ts'
10
10
  import { paintedCallsLanded } from '#shared/takeEarlyCall.ts'
11
- import { applyHead, withdrawHead } from './applyHead.ts'
11
+ import { withdrawHead } from './applyHead.ts'
12
12
  import { LAYER_MOUNT, LAYER_NAVIGATE } from './LAYER_NAMES.ts'
13
- import { MOUNTING_LEVEL } from './MOUNTING_LEVEL.ts'
13
+ import { Level } from './Level.ts'
14
14
  import { beginLayer, endLayer } from './measureLayer.ts'
15
15
  import { fillRegion, pageRegion, type Region } from './regions.ts'
16
16
  import type { Scope, Stoppable } from './Scope.ts'
@@ -51,43 +51,6 @@ type Compiled = Route & { segments: Segment[] }
51
51
 
52
52
  type Found = { route: Compiled; params: Params }
53
53
 
54
- // 41.11 — ONE LEVEL OF WHAT IS MOUNTED: the scope that stops it and the region its child
55
- // occupies. The module standing there is `chain`'s at the same index. The router holds these
56
- // rather than letting each level own the one below it, which is what makes a shared layout
57
- // something it can KEEP: a scope stopped from above takes its children with it, and a layout that
58
- // survives a navigation must not take the page it was holding a moment ago.
59
- type Level = {
60
- scope: Scope | undefined
61
- /**
62
- * Where this level's `<slot/>` is, learned when the level below it mounted there, and
63
- * `undefined` again once that region leaves — a `<slot/>` inside a closed `{#if}` is nowhere.
64
- */
65
- slot: Region | undefined
66
- /**
67
- * This level's head markup as it reads now, left by its mount (`emitClient`); `undefined` for
68
- * a level with no `<head>`.
69
- *
70
- * 39.14 — READ WHEN THE LEVEL LEAVES OR STAYS ABOVE ONE THAT DOES, which is why it is the
71
- * markup's thunk and not the markup: a layout's `<title>{name}</title>` puts back the name it
72
- * reads at that moment. Left painted or not, since a painted level's head is the one the server
73
- * wrote and the first navigation away from it withdraws that head like any other.
74
- */
75
- head: (() => string) | undefined
76
- /** The heads of the components mounted in this level, joined to `head` (`contributeHead`). */
77
- contributed: (() => string)[] | undefined
78
- /**
79
- * 41.19 — this level's markup, left by its mount once its setup has run, for the router to run
80
- * once every level below has been set up too; `undefined` once it has run, or where the module
81
- * has none.
82
- */
83
- markup: ((region: Region) => Scope | undefined) | undefined
84
- /**
85
- * 10.5 — the share scope this level's setup ran in, left by its mount, for `setUpLevels` to
86
- * set the level below up inside. `null` where the module left none.
87
- */
88
- share: ShareScope | null
89
- }
90
-
91
54
  /**
92
55
  * The client router: matches addresses, and mounts and replaces the page chain.
93
56
  *
@@ -354,7 +317,11 @@ export class Router {
354
317
  // exports is the same function either way.
355
318
  const shared = this.sharedLevels(arriving)
356
319
  // READ BEFORE THE STOP, while the leaving levels' heads read what they read on screen.
357
- const leaving = this.heads(shared, this.levels.length)
320
+ const leaving = Level.headsOf(
321
+ this.levels,
322
+ shared,
323
+ this.levels.length,
324
+ )
358
325
  this.stopFrom(shared)
359
326
  // The region the arriving part goes into: the page region where nothing is kept, and
360
327
  // the deepest kept layout's own slot where something is.
@@ -375,9 +342,10 @@ export class Router {
375
342
  this.mountChain(target, found, arriving, shared, into)
376
343
  // 39.14 — what the leaving levels keyed and the arriving ones did not goes back to the
377
344
  // nearest kept layout's, else leaves.
378
- withdrawHead(leaving, this.heads(shared, this.levels.length), () =>
379
- this.heads(0, shared),
380
- )
345
+ withdrawHead(leaving, () => [
346
+ Level.headsOf(this.levels, 0, shared),
347
+ Level.headsOf(this.levels, shared, this.levels.length),
348
+ ])
381
349
  // The top of the page, or — as the browser does on a load — the fragment's element.
382
350
  if (options.keepScroll !== true) scrollToFragment(target.hash)
383
351
  } finally {
@@ -407,14 +375,6 @@ export class Router {
407
375
  return shared
408
376
  }
409
377
 
410
- /** The heads of the standing levels from `from` to `to`, outermost first. */
411
- private heads(from: number, to: number): string {
412
- let markup = ''
413
- for (let level = from; level < to; level += 1)
414
- markup += (this.levels[level] as Level).head?.() ?? ''
415
- return markup
416
- }
417
-
418
378
  /**
419
379
  * Stops every level from `at` down, inside out, and drops them.
420
380
  *
@@ -423,8 +383,12 @@ export class Router {
423
383
  * two files would expect.
424
384
  */
425
385
  private stopFrom(at: number): void {
426
- for (let level = this.levels.length - 1; level >= at; level -= 1)
427
- (this.levels[level] as Level).scope?.stop()
386
+ for (let index = this.levels.length - 1; index >= at; index -= 1) {
387
+ const level = this.levels[index] as Level
388
+ // The level's head leaves whole, so none of its components withdraws its own.
389
+ level.contributed.clear()
390
+ level.scope?.stop()
391
+ }
428
392
  this.levels.length = at
429
393
  }
430
394
 
@@ -463,14 +427,7 @@ export class Router {
463
427
  props: Record<string, (() => unknown) | undefined>,
464
428
  ): ShareScope | null {
465
429
  const module = this.chain[at] as PageModule
466
- const level: Level = {
467
- scope: undefined,
468
- slot: undefined,
469
- head: undefined,
470
- contributed: undefined,
471
- markup: undefined,
472
- share: null,
473
- }
430
+ const level = new Level(this.levels, at)
474
431
  this.levels[at] = level
475
432
  if (at < this.chain.length - 1)
476
433
  props.children = () => ({
@@ -493,9 +450,7 @@ export class Router {
493
450
  // runs; a module mounted by the router leaves its markup rather than using this one.
494
451
  // A deferred level answers the scope its setup made, so the watches that setup opened are
495
452
  // stopped with the level even where its markup never runs — a slot that never opens.
496
- level.scope = mounting(level, () =>
497
- module.default(region, props, level),
498
- )
453
+ level.scope = level.mount(() => module.default(region, props, level))
499
454
  return level.share
500
455
  }
501
456
 
@@ -505,7 +460,7 @@ export class Router {
505
460
  const markup = level.markup
506
461
  if (markup === undefined) return
507
462
  level.markup = undefined
508
- level.scope = mounting(level, () => markup(region))
463
+ level.scope = level.mount(() => markup(region))
509
464
  }
510
465
 
511
466
  /**
@@ -675,25 +630,3 @@ export function startRouter(
675
630
  CURRENT_ROUTER.current = router
676
631
  return router
677
632
  }
678
-
679
- /**
680
- * `run`, with `level` the one its components add their `<head>` to (39.1) — and what those built on
681
- * the client contributed applied once it returns, rather than once a component.
682
- */
683
- function mounting<Result>(level: Level, run: () => Result): Result {
684
- const back = MOUNTING_LEVEL.current
685
- const backUnapplied = MOUNTING_LEVEL.unapplied
686
- MOUNTING_LEVEL.current = level
687
- MOUNTING_LEVEL.unapplied = ''
688
- let unapplied: string
689
- let result: Result
690
- try {
691
- result = run()
692
- } finally {
693
- unapplied = MOUNTING_LEVEL.unapplied
694
- MOUNTING_LEVEL.current = back
695
- MOUNTING_LEVEL.unapplied = backUnapplied
696
- }
697
- applyHead(unapplied)
698
- return result
699
- }