@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
package/src/ui/Router.ts CHANGED
@@ -8,8 +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 { withdrawHead } from './applyHead.ts'
11
+ import { applyHead, withdrawHead } from './applyHead.ts'
12
12
  import { LAYER_MOUNT, LAYER_NAVIGATE } from './LAYER_NAMES.ts'
13
+ import { MOUNTING_LEVEL } from './MOUNTING_LEVEL.ts'
13
14
  import { beginLayer, endLayer } from './measureLayer.ts'
14
15
  import { menuBridge } from './menuBridge.ts'
15
16
  import { fillRegion, pageRegion, type Region } from './regions.ts'
@@ -73,6 +74,8 @@ type Level = {
73
74
  * wrote and the first navigation away from it withdraws that head like any other.
74
75
  */
75
76
  head: (() => string) | undefined
77
+ /** The heads of the components mounted in this level, joined to `head` (`contributeHead`). */
78
+ contributed: (() => string)[] | undefined
76
79
  /**
77
80
  * 41.19 — this level's markup, left by its mount once its setup has run, for the router to run
78
81
  * once every level below has been set up too; `undefined` once it has run, or where the module
@@ -144,20 +147,8 @@ export class Router {
144
147
  * Read off the compiled `<style>` blocks. Nothing here looks at the document's stylesheets.
145
148
  */
146
149
  transitions: boolean
147
- /**
148
- * The shell's keyed head entries, as the build read them off the shell (`keyedHead`).
149
- *
150
- * 39.4, 39.14 — THE OUTERMOST CONTRIBUTOR, and the one the document cannot say: the server
151
- * merged out every entry of it a level overrode.
152
- */
153
- shellHead: string
154
150
 
155
- constructor(
156
- region: Region,
157
- routes: Compiled[],
158
- transitions: boolean,
159
- shellHead: string,
160
- ) {
151
+ constructor(region: Region, routes: Compiled[], transitions: boolean) {
161
152
  this.region = region
162
153
  this.routes = routes
163
154
  this.levels = []
@@ -167,7 +158,6 @@ export class Router {
167
158
  this.navigation = 0
168
159
  this.prefetched = new Set()
169
160
  this.transitions = transitions
170
- this.shellHead = shellHead
171
161
  }
172
162
 
173
163
  /**
@@ -186,7 +176,7 @@ export class Router {
186
176
  /**
187
177
  * Loads a route's chunks ahead of a navigation the reader has signaled.
188
178
  *
189
- * 30.14 — A NAVIGATION THE READER HAS SIGNALLED, LOADED BEFORE IT HAPPENS: the route's chunks
179
+ * 30.14 — A NAVIGATION THE READER HAS SIGNALED, LOADED BEFORE IT HAPPENS: the route's chunks
190
180
  * are asked for on intent, so the click finds them in the module cache and setup — and with it
191
181
  * every call the page makes — starts at once. Only the CODE: a call run on a hover would be a
192
182
  * call nobody asked for, and for anything but a `GET` one with an effect. A failed load is left
@@ -385,11 +375,9 @@ export class Router {
385
375
  // outermost first; a kept layout is not mounted again, so neither is its head.
386
376
  this.mountChain(target, found, arriving, shared, into)
387
377
  // 39.14 — what the leaving levels keyed and the arriving ones did not goes back to the
388
- // nearest kept layout's, else the shell's, else leaves.
389
- withdrawHead(
390
- leaving,
391
- this.heads(shared, this.levels.length),
392
- () => this.shellHead + this.heads(0, shared),
378
+ // nearest kept layout's, else leaves.
379
+ withdrawHead(leaving, this.heads(shared, this.levels.length), () =>
380
+ this.heads(0, shared),
393
381
  )
394
382
  // The top of the page, or — as the browser does on a load — the fragment's element.
395
383
  if (options.keepScroll !== true) scrollToFragment(target.hash)
@@ -480,6 +468,7 @@ export class Router {
480
468
  scope: undefined,
481
469
  slot: undefined,
482
470
  head: undefined,
471
+ contributed: undefined,
483
472
  markup: undefined,
484
473
  share: null,
485
474
  }
@@ -505,7 +494,9 @@ export class Router {
505
494
  // runs; a module mounted by the router leaves its markup rather than using this one.
506
495
  // A deferred level answers the scope its setup made, so the watches that setup opened are
507
496
  // stopped with the level even where its markup never runs — a slot that never opens.
508
- level.scope = module.default(region, props, level)
497
+ level.scope = mounting(level, () =>
498
+ module.default(region, props, level),
499
+ )
509
500
  return level.share
510
501
  }
511
502
 
@@ -515,7 +506,7 @@ export class Router {
515
506
  const markup = level.markup
516
507
  if (markup === undefined) return
517
508
  level.markup = undefined
518
- level.scope = markup(region)
509
+ level.scope = mounting(level, () => markup(region))
519
510
  }
520
511
 
521
512
  /**
@@ -577,7 +568,7 @@ export class Router {
577
568
  ): void {
578
569
  arrive(target.href, found.route.pattern, found.params)
579
570
  this.chain = modules
580
- // 38.89 — inside a desktop app, the window shows the menus this chain exports.
571
+ // 38.88 — inside a desktop app, the window shows the menus this chain exports.
581
572
  if (menuBridge() !== undefined)
582
573
  void import('./reportMenu.ts').then((loaded) =>
583
574
  loaded.reportMenu(modules as { menu?: unknown }[]),
@@ -685,14 +676,30 @@ export function startRouter(
685
676
  routes: Route[],
686
677
  host: Element = document.body,
687
678
  transitions = false,
688
- shellHead = '',
689
679
  ): Router {
690
- const router = new Router(
691
- pageRegion(host),
692
- rankRoutes(routes),
693
- transitions,
694
- shellHead,
695
- )
680
+ const router = new Router(pageRegion(host), rankRoutes(routes), transitions)
696
681
  CURRENT_ROUTER.current = router
697
682
  return router
698
683
  }
684
+
685
+ /**
686
+ * `run`, with `level` the one its components add their `<head>` to (39.1) — and what those built on
687
+ * the client contributed applied once it returns, rather than once a component.
688
+ */
689
+ function mounting<Result>(level: Level, run: () => Result): Result {
690
+ const back = MOUNTING_LEVEL.current
691
+ const backUnapplied = MOUNTING_LEVEL.unapplied
692
+ MOUNTING_LEVEL.current = level
693
+ MOUNTING_LEVEL.unapplied = ''
694
+ let unapplied: string
695
+ let result: Result
696
+ try {
697
+ result = run()
698
+ } finally {
699
+ unapplied = MOUNTING_LEVEL.unapplied
700
+ MOUNTING_LEVEL.current = back
701
+ MOUNTING_LEVEL.unapplied = backUnapplied
702
+ }
703
+ applyHead(unapplied)
704
+ return result
705
+ }
package/src/ui/Scope.ts CHANGED
@@ -61,7 +61,7 @@ export class Scope {
61
61
  this.stops.push(watchTracked(effect))
62
62
  return
63
63
  }
64
- // UNDER A BOUNDARY, a throw is the boundary's and the scopes a re-run builds are too, and a
64
+ // UNDER A BOUNDARY, a throw is the boundary's and the scopes a rerun builds are too, and a
65
65
  // failed binding is retried by running it again.
66
66
  this.stops.push(watchTracked(() => boundary.guard(effect, effect)))
67
67
  }
@@ -46,7 +46,7 @@ export class TryRegion extends ArmRegion {
46
46
  /** Only a mount runs the components in a body, so a failed body is retried by mounting it again. */
47
47
  private readonly remount: () => void
48
48
  private retry: Disposer | undefined
49
- /** What the retry re-runs, recorded by `fail` and watched once the catch stands (`startRetry`). */
49
+ /** What the retry reruns, recorded by `fail` and watched once the catch stands (`startRetry`). */
50
50
  private pending: (() => unknown) | undefined
51
51
  /** A body a retry mounted, for the swap to take in place of building one. */
52
52
  private standing: Stoppable | undefined
@@ -110,7 +110,7 @@ export class TryRegion extends ArmRegion {
110
110
  * failure of the body, and is left to the caller that rebuilds; and a value still in flight is
111
111
  * not a failure either: it aborts the run as it would anywhere (2.12).
112
112
  *
113
- * `again` IS WHAT A RETRY RE-RUNS: the binding that threw, or the body mounted again.
113
+ * `again` IS WHAT A RETRY RERUNS: the binding that threw, or the body mounted again.
114
114
  */
115
115
  guard<T>(run: () => T, again: () => unknown): T | undefined {
116
116
  const previous = BOUNDARY.current
@@ -9,7 +9,9 @@
9
9
  // contribution a page makes several of.
10
10
 
11
11
  import { headKeyOf } from '#shared/headKeyOf.ts'
12
+ import { MOUNTING_LEVEL } from './MOUNTING_LEVEL.ts'
12
13
  import { markupNodes } from './parseMarkup.ts'
14
+ import { isEmptyRegion, type Region } from './regions.ts'
13
15
  import type { Scope } from './Scope.ts'
14
16
 
15
17
  export function applyHead(markup: string): void {
@@ -144,3 +146,34 @@ export function liveHead(scope: Scope, markup: () => string): void {
144
146
  else applyHead(text)
145
147
  })
146
148
  }
149
+
150
+ /**
151
+ * A component's `<head>`, added to the page or layout the router is mounting it under (39.1).
152
+ *
153
+ * Joined to that level's head, so it is withdrawn as the level's is when it leaves (39.14), and
154
+ * deeper than the level's own, so it wins by key (39.4). Applied where the component builds its own
155
+ * markup, once the level is mounted (`MOUNTING_LEVEL.unapplied`); a region the server painted
156
+ * already carries the head the server wrote. Nothing where no level is mounting — a row grown after
157
+ * the fact titles nothing.
158
+ *
159
+ * A LIST, NOT A CLOSURE PER CONTRIBUTION: a `{#for}` of components with a `<head>` contributes once a
160
+ * row, and a chain of them is read through as deep a stack.
161
+ */
162
+ export function contributeHead(region: Region, head: () => string): void {
163
+ const level = MOUNTING_LEVEL.current
164
+ if (level === undefined) return
165
+ let contributed = level.contributed
166
+ if (contributed === undefined) {
167
+ const own = level.head
168
+ const joined: (() => string)[] = []
169
+ contributed = joined
170
+ level.contributed = joined
171
+ level.head = () => {
172
+ let markup = own?.() ?? ''
173
+ for (const one of joined) markup += one()
174
+ return markup
175
+ }
176
+ }
177
+ contributed.push(head)
178
+ if (isEmptyRegion(region)) MOUNTING_LEVEL.unapplied += head()
179
+ }
package/src/ui/index.ts CHANGED
@@ -39,7 +39,7 @@ export {
39
39
  } from './adoptWalk.ts'
40
40
  // 39.6 — a module mounted into an empty region applies its own head, from inside its instance
41
41
  // (`emitClient`); only a module that carries its own markup and a `<head>` imports it.
42
- export { applyHead, liveHead } from './applyHead.ts'
42
+ export { applyHead, contributeHead, liveHead } from './applyHead.ts'
43
43
  export { bindElement } from './bindElement.ts'
44
44
  export { bindProperty } from './bindProperty.ts'
45
45
  export { bindSpread } from './bindSpread.ts'
@@ -2,7 +2,7 @@
2
2
  * The channel a desktop app's window opens to the page for its menu, or `undefined` in a browser.
3
3
  *
4
4
  * ITS OWN MODULE, so the router asks without importing the reporter: `reportMenu` is a chunk only a
5
- * page inside a desktop app loads. 38.89.
5
+ * page inside a desktop app loads. 38.88.
6
6
  */
7
7
  export function menuBridge():
8
8
  | { postMessage(message: string): void }
@@ -12,7 +12,7 @@
12
12
  // the `load` an iframe fires when it is torn out, n per reorder against 0.
13
13
  //
14
14
  // PROBED, NOT VERSIONED. Where `moveBefore` is absent this is exactly the call the reconcile made
15
- // before it existed, so the fallback is today's behaviour rather than a degraded one. happy-dom
15
+ // before it existed, so the fallback is today's behavior rather than a degraded one. happy-dom
16
16
  // does not implement it, which is why the gate for this belongs in the browser lane — see
17
17
  // `packages/harness/e2e`.
18
18
  const CAN_MOVE =
@@ -310,7 +310,7 @@ export function dropRow(row: Row): void {
310
310
  *
311
311
  * `abide:hydrate` RATHER THAN A CHANNEL OF ITS OWN. A repeated key is not a hydration mismatch —
312
312
  * it survives a reconcile that no hydration was anywhere near — but the registry's meaning for
313
- * that namespace is THE BROWSER LANE, not a mismatch, and it is the only namespace 28.9 gives the
313
+ * that namespace is THE BROWSER LANE, not a mismatch, and it is the only namespace 28.8 gives the
314
314
  * client runtime. A second one buys an app a second name to learn and silence for one warning,
315
315
  * where widening the lane's one meaning costs nothing that has to be taught.
316
316
  */
@@ -329,7 +329,7 @@ function warnRepeatedKey(list: List): void {
329
329
  * of that length ends on, so the binary search finds the run each row extends, and `came` walks the
330
330
  * answer back out.
331
331
  *
332
- * WHY A SUBSEQUENCE AND NOT A RUN OF NEIGHBOURS. The rows that can stay put are the ones already
332
+ * WHY A SUBSEQUENCE AND NOT A RUN OF NEIGHBORS. The rows that can stay put are the ones already
333
333
  * in the right order RELATIVE TO EACH OTHER — they need not be adjacent, and demanding they be
334
334
  * would move rows that a gap of new or removed rows had merely spread apart.
335
335
  *
@@ -1,4 +1,4 @@
1
- // 38.89 — A ROUTE'S MENU, TOLD TO THE WINDOW IT IS SHOWN IN. Loaded only inside a desktop app, where
1
+ // 38.88 — A ROUTE'S MENU, TOLD TO THE WINDOW IT IS SHOWN IN. Loaded only inside a desktop app, where
2
2
  // the window has opened `abideMenu` to the page; anywhere else the router never imports it.
3
3
 
4
4
  import { MenuEntry, type MenuItem } from '#shared/menuItem.ts'
@@ -31,7 +31,7 @@ export function reportMenu(chain: { menu?: unknown }[]): void {
31
31
  for (const menu of level.menu as MenuItem[]) told.push(tell(menu))
32
32
  if (!listening) {
33
33
  listening = true
34
- // 38.91, 38.92 — the window names the item picked; the page does what it means.
34
+ // 38.90, 38.91 — the window names the item picked; the page does what it means.
35
35
  addEventListener('abide:menu', (event) => {
36
36
  const item = numbered[(event as CustomEvent<number>).detail]
37
37
  if (item === undefined) return
package/src/ui/routing.ts CHANGED
@@ -8,11 +8,11 @@
8
8
  // `--splitting` so the barrel lands in a SHARED chunk — which is the configuration CLAUDE.md names
9
9
  // as where bun stops shaking — came out 7156 bytes with the router off the barrel against 7160
10
10
  // with it on, and zero router symbols in the output either way. Bun shook it out. So the argument
11
- // here is the structural one above, plus not resting a budget on a bundler behaviour the project's
11
+ // here is the structural one above, plus not resting a budget on a bundler behavior the project's
12
12
  // own notes say not to rest one on; it is NOT "this saves the router's bytes", because it does not.
13
13
  //
14
14
  // ONE CALLER: the `routes.ts` the build emits for an app that HAS pages, through
15
- // `ROUTING_SPECIFIER`. An app with no `page.abide` under `pages/` never resolves this entry.
15
+ // `ROUTING_SPECIFIER`. An app with no page never resolves this entry.
16
16
 
17
17
  export { listenForRoutes } from './listenForRoutes.ts'
18
18
  export { type PageMount, type Route, Router, startRouter } from './Router.ts'
@@ -18,7 +18,7 @@ import { WORK } from '#shared/WORK.ts'
18
18
  * the same call on both sides.
19
19
  *
20
20
  * THE COMPARE IS THE REST OF THE POINT. `getAttribute` reads a value the element already holds —
21
- * it is not a layout read and forces nothing — and a binding re-run by a value that landed on the
21
+ * it is not a layout read and forces nothing — and a binding rerun by a value that landed on the
22
22
  * same string is the common case rather than the rare one, so the branch is cheaper than the
23
23
  * mutation it skips.
24
24
  */
package/src/ui/setText.ts CHANGED
@@ -12,7 +12,7 @@ import { markupToText, RawRun } from './RawRun.ts'
12
12
  * particular emitter remembered to write.
13
13
  *
14
14
  * The COMPARE is the rest of the point: a write to `data` that does not change it still costs,
15
- * and a slot re-run by a value that landed on the same string is the common case rather than the
15
+ * and a slot rerun by a value that landed on the same string is the common case rather than the
16
16
  * rare one.
17
17
  *
18
18
  * THE RUN'S STATE COMES BACK, because a run that held markup is a text node again after this and the
@@ -2,7 +2,8 @@
2
2
  // reads is followed (18.3). Each copy is kept in step with the value it was answered from: the value
3
3
  // where the version it holds is not the server's, and the writes of each patch over it (18.6, 18.11).
4
4
 
5
- import { ABIDE_ADDRESS, SOCKET_ADDRESS } from '#shared/ADDRESSES.ts'
5
+ import { SOCKET_ADDRESS } from '#shared/ADDRESSES.ts'
6
+ import { mounted } from '#shared/mount.ts'
6
7
  import { canonicalKey } from '#shared/reactive/canonicalKey.ts'
7
8
  import {
8
9
  heldGenerationOf,
@@ -201,15 +202,16 @@ class Mux {
201
202
  export const MUXES = new Map<string, Mux>()
202
203
 
203
204
  /**
204
- * The mux a socket's or a handler's address is carried over, made on first use.
205
+ * The mux a handler's address is carried over, made on first use.
205
206
  *
206
- * `SOCKET_ADDRESS` under whatever the address stands below, so an app under a sub-path opens its
207
- * own.
207
+ * `SOCKET_ADDRESS` under the app's mount, so an app under a sub-path opens its own — at the
208
+ * address's own origin where it names one, and the page's where it is a path.
208
209
  */
209
210
  function muxFor(address: string): Mux {
210
- const below = address.indexOf(ABIDE_ADDRESS)
211
- const path = `${below === -1 ? '' : address.slice(0, below)}${SOCKET_ADDRESS}`
212
- const url = new URL(path, location.href)
211
+ const url = new URL(
212
+ mounted(SOCKET_ADDRESS),
213
+ URL.canParse(address) ? address : location.href,
214
+ )
213
215
  url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
214
216
  let mux = MUXES.get(url.href)
215
217
  if (mux === undefined) {
package/src/ui/wire.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // 16.28 — A HANDLER, AS A BROWSER HOLDS IT. What a page imported as `getInvoice` is, in the bundle,
2
- // `wire('GET', '/__abide/rpc/invoices/getInvoice')`: the same declaration the server made, over a
2
+ // `wire('GET', '/invoices/getInvoice')`: the same declaration the server made, over a
3
3
  // body that fetches rather than one that reads the database. So it is the same `Rpc` — the call
4
4
  // hands back a `Reactive`, the keyed face narrows by args, a mutation's `ttl` is 0 (16.7), and a
5
5
  // refusal narrows through `isError` — because it is built by the one `declare` both seams share.
@@ -1,6 +0,0 @@
1
- import { log, onStart } from '@abide/abide/server'
2
-
3
- onStart(async (start) => {
4
- await start()
5
- log('started')
6
- })
File without changes
@@ -1,8 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- </head>
7
- <body></body>
8
- </html>
@@ -1,56 +0,0 @@
1
- // 35.4 — A `.abide` IMPORTED FROM AN APP'S OWN `.ts`, as the component it compiles to. A component
2
- // importing a component is pointed at the compiled module by the compile itself (34.14); a handler
3
- // is not compiled, so its import reached bun as written — and bun loads an extension it has no
4
- // loader for as a `file`, handing the handler the path. `render(Invoice({ invoice }))` then threw
5
- // "Invoice is not a function".
6
- //
7
- // BOUND, NOT RAW. The compiled module is a function of accessors answering its markup (33.4); what
8
- // an app calls is a function of plain props answering a `Component` (35.4), which `render` invokes
9
- // with nothing. The adapter is written into the module the import lands on rather than exported
10
- // from a seam, so no name joins the surface.
11
-
12
- import { realpathSync } from 'node:fs' // bun exposes no realpath of its own
13
-
14
- /**
15
- * The plugin landing a `.abide` under `root` on its compiled server module under `compiled`.
16
- *
17
- * One per app root: a runtime plugin's `onLoad` is asked in registration order and the first whose
18
- * filter matches must answer, so the filter carries the root and another app's plugin is never
19
- * asked. The same object serves the bundler, where `abide compile` follows a handler's imports.
20
- */
21
- export function componentImports(
22
- root: string,
23
- compiled: string,
24
- ): Bun.BunPlugin {
25
- // BOTH SPELLINGS OF THE ROOT, because bun names a file with its symlinks resolved and an app
26
- // under `/tmp` is `/private/tmp` to it.
27
- const roots = new Set([root, realpathSync(root)])
28
- return {
29
- name: 'abide-component-imports',
30
- setup(build) {
31
- for (const at of roots)
32
- build.onLoad(
33
- { filter: new RegExp(`^${RegExp.escape(at)}/.*\\.abide$`) },
34
- (args) => ({
35
- contents: boundModule(
36
- `${compiled}/${args.path.slice(at.length + 1, -'.abide'.length)}.ts`,
37
- ),
38
- loader: 'js',
39
- }),
40
- )
41
- },
42
- }
43
- }
44
-
45
- function boundModule(compiledModule: string): string {
46
- return `import component from ${JSON.stringify(compiledModule)}
47
- export default (props = {}) => {
48
- const accessors = {}
49
- for (const key in props) {
50
- const value = props[key]
51
- accessors[key] = () => value
52
- }
53
- return () => component(accessors)
54
- }
55
- `
56
- }