@abide/abide 0.101.0 → 0.103.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.
- package/CHANGELOG.md +26 -0
- package/bin/abide.ts +226 -164
- package/docs/REGISTRY.md +730 -0
- package/docs/RULEBOOK.md +2768 -0
- package/docs/abide.md +13127 -0
- package/package.json +6 -3
- package/scaffold/AGENTS.md +38 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/layout.abide +18 -0
- package/src/compiler/Lowering.ts +78 -9
- package/src/compiler/ShadowMap.ts +2 -2
- package/src/compiler/TypeSession.ts +341 -71
- package/src/compiler/appMap.ts +96 -0
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +53 -8
- package/src/compiler/checkedShadow.ts +73 -6
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +598 -200
- package/src/compiler/compiledEntry.ts +48 -40
- package/src/compiler/componentGraph.ts +57 -71
- package/src/compiler/declaredExports.ts +26 -9
- package/src/compiler/emitClient.ts +142 -72
- package/src/compiler/emitComponent.ts +41 -12
- package/src/compiler/emitSource.ts +102 -13
- package/src/compiler/handlersUnder.ts +164 -76
- package/src/compiler/hooksCalled.ts +35 -0
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +150 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +250 -19
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +62 -8
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +37 -15
- package/src/compiler/serveLanguage.ts +101 -34
- package/src/compiler/sharedModuleWrites.ts +2 -2
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +29 -2
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/MODULE_QUERIES.ts +27 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +457 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +241 -107
- package/src/server/answerRpc.ts +107 -33
- package/src/server/answerSocket.ts +65 -20
- package/src/server/answerValue.ts +22 -9
- package/src/server/appDirectories.ts +1 -1
- package/src/server/appPlugins.ts +1 -1
- package/src/server/appTree.ts +238 -0
- package/src/server/argsFor.ts +2 -2
- package/src/server/assistant.ts +2 -2
- package/src/server/bridgeRelay.ts +6 -8
- package/src/server/claudeCode.ts +0 -6
- package/src/server/clientBuildOptions.ts +8 -3
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +38 -37
- package/src/server/componentModules.d.ts +2 -1
- package/src/server/compressAnswer.ts +4 -4
- package/src/server/config.ts +25 -29
- package/src/server/configFields.ts +31 -25
- package/src/server/createApp.ts +77 -79
- package/src/server/devWorker.ts +118 -181
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +57 -32
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +137 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +12 -7
- package/src/server/installSourceMaps.ts +93 -0
- package/src/server/launcherOptions.ts +33 -31
- package/src/server/launcherPage.ts +107 -64
- package/src/server/manifestOf.ts +16 -13
- package/src/server/namesUnder.ts +2 -2
- package/src/server/nativeMenu.ts +6 -6
- package/src/server/negotiateEncoding.ts +1 -1
- package/src/server/preload.ts +13 -0
- package/src/server/principal.ts +10 -5
- package/src/server/realOrAsGiven.ts +10 -0
- package/src/server/refuseAddressCollision.ts +21 -14
- package/src/server/remote.ts +49 -52
- package/src/server/render.ts +65 -94
- package/src/server/renderComponent.ts +5 -0
- package/src/server/reportHealth.ts +2 -2
- package/src/server/requestScope.ts +12 -1
- package/src/server/runBuild.ts +24 -0
- package/src/server/runCompiled.ts +5 -5
- package/src/server/runDesktop.ts +48 -28
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +79 -37
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +20 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +10 -18
- package/src/shared/APP_META.ts +7 -0
- package/src/shared/CopyOptions.ts +1 -1
- package/src/shared/DEBUG_META.ts +1 -1
- package/src/shared/LAUNCHER_FILES.ts +9 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +24 -0
- package/src/shared/cookies.ts +2 -2
- package/src/shared/csp.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +85 -0
- package/src/shared/framing.ts +79 -6
- package/src/shared/headKeyOf.ts +2 -0
- package/src/shared/index.ts +2 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/{ui → shared}/menuBridge.ts +2 -2
- package/src/shared/menuItem.ts +4 -4
- package/src/shared/onMenu.ts +58 -0
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/NotLanded.ts +1 -1
- package/src/shared/reactive/Reactive.ts +43 -43
- package/src/shared/reactive/argsContract.ts +4 -4
- package/src/shared/reactive/canonicalKey.ts +4 -4
- package/src/shared/reactive/chunksOfStream.ts +6 -0
- package/src/shared/reactive/index.ts +1 -1
- package/src/shared/reactive/recordWrites.ts +103 -5
- package/src/shared/reactive/structural.ts +1 -1
- package/src/shared/reactive/tracking.ts +1 -1
- package/src/shared/reactive/watch.ts +3 -3
- package/src/shared/refuse.ts +1 -1
- package/src/{ui → shared}/reportMenu.ts +8 -10
- package/src/shared/routePattern.ts +122 -48
- package/src/shared/rpc.ts +37 -8
- package/src/shared/rpcRequest.ts +27 -3
- package/src/shared/url.ts +4 -44
- package/src/shared/writePath.ts +1 -1
- package/src/ui/LAYER_NAMES.ts +1 -1
- package/src/ui/List.ts +3 -3
- package/src/ui/MOUNTING_LEVEL.ts +18 -0
- package/src/ui/Mismatch.ts +1 -1
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +37 -36
- package/src/ui/Scope.ts +1 -1
- package/src/ui/TryRegion.ts +2 -2
- package/src/ui/applyHead.ts +33 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/routing.ts +2 -2
- package/src/ui/setAttribute.ts +1 -1
- package/src/ui/setText.ts +1 -1
- package/src/ui/socketWire.ts +9 -7
- package/src/ui/wire.ts +1 -1
- package/scaffold/src/server/app.ts +0 -6
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +0 -8
- package/src/server/componentImports.ts +0 -56
- /package/scaffold/src/{ui/pages/page.abide → index.abide} +0 -0
- /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
package/src/shared/url.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
//
|
|
5
5
|
// 16.10 — RESOLVED AGAINST THE MOUNT at the call, so the same build serves at the root and below it.
|
|
6
6
|
|
|
7
|
+
import { filledPath } from './filledPath.ts'
|
|
7
8
|
import { mounted } from './mount.ts'
|
|
8
9
|
import { queryText } from './queryText.ts'
|
|
9
|
-
import { parseRoutePattern } from './routePattern.ts'
|
|
10
10
|
|
|
11
11
|
type Parts<P extends string> = P extends `${infer Head}/${infer Tail}`
|
|
12
12
|
? Head | Parts<Tail>
|
|
@@ -86,29 +86,9 @@ export function url<P extends RouteLiteral>(
|
|
|
86
86
|
): string {
|
|
87
87
|
const params = (rest[0] ?? {}) as Record<string, unknown>
|
|
88
88
|
const query = rest[1]
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
address += `/${segment.name}`
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
const value = params[segment.name]
|
|
96
|
-
if (value === undefined || value === null || value === '') {
|
|
97
|
-
// 30.4 — an optional left out omits its segment, and so does an empty rest (30.5).
|
|
98
|
-
if (segment.kind !== 'required') continue
|
|
99
|
-
throw new Error(
|
|
100
|
-
`abide: url('${pattern}') was given no '${segment.name}', and the route requires it. RULEBOOK 30.3.`,
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
if (segment.kind === 'rest' && Array.isArray(value)) {
|
|
104
|
-
for (const each of value)
|
|
105
|
-
address += `/${encodedSegment(pattern, segment.name, each)}`
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
address += `/${encodedSegment(pattern, segment.name, value)}`
|
|
109
|
-
}
|
|
110
|
-
if (address === '') address = '/'
|
|
111
|
-
address = mounted(address)
|
|
89
|
+
const address = mounted(
|
|
90
|
+
filledPath(pattern ?? '/', params, `url('${pattern}')`),
|
|
91
|
+
)
|
|
112
92
|
// 30.6 — the query half, last, in the spelling `rpcRequest` writes.
|
|
113
93
|
const search =
|
|
114
94
|
query === undefined
|
|
@@ -118,23 +98,3 @@ export function url<P extends RouteLiteral>(
|
|
|
118
98
|
: queryText(query)
|
|
119
99
|
return search === '' ? address : `${address}?${search}`
|
|
120
100
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* A param value encoded as one path segment; `.` and `..` are refused.
|
|
124
|
-
*
|
|
125
|
-
* 30.19 — A VALUE OF `.` OR `..` HAS NO SPELLING AS A SEGMENT: the URL parser collapses it as a
|
|
126
|
-
* dot-segment, and `%2E` / `%2E%2E` with it, so the address would land on another route. Refused
|
|
127
|
-
* rather than written, as a missing required segment is (30.3).
|
|
128
|
-
*/
|
|
129
|
-
function encodedSegment(
|
|
130
|
-
pattern: string | undefined,
|
|
131
|
-
name: string,
|
|
132
|
-
value: unknown,
|
|
133
|
-
): string {
|
|
134
|
-
const text = String(value)
|
|
135
|
-
if (text === '.' || text === '..')
|
|
136
|
-
throw new Error(
|
|
137
|
-
`abide: url('${pattern}') was given '${text}' for '${name}', and no address carries it as a segment — the URL parser resolves it to another route. RULEBOOK 30.19.`,
|
|
138
|
-
)
|
|
139
|
-
return encodeURIComponent(text)
|
|
140
|
-
}
|
package/src/shared/writePath.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { putOwn } from './putOwn.ts'
|
|
|
4
4
|
* A copy of `value` with `next` written at `path`, each level down the path copied.
|
|
5
5
|
*
|
|
6
6
|
* 31.8 — a write through a member path COPIES DOWN THE PATH AND THEN WRITES, and the copy is not
|
|
7
|
-
*
|
|
7
|
+
* optimized away. The copy is the whole point: a `Reactive` wakes its readers on IDENTITY, so
|
|
8
8
|
* `user.name = 'x'` done as a mutation of the held object writes the right value and wakes nobody
|
|
9
9
|
* — the page renders correctly on load and then never moves again, with nothing about it saying
|
|
10
10
|
* so.
|
package/src/ui/LAYER_NAMES.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// code that EMITS the measure to the spec that reads it, and a string typed twice is a
|
|
3
3
|
// `shares()` call that throws "found no measure named" against a layer that ran.
|
|
4
4
|
//
|
|
5
|
-
// `.` RATHER THAN `:`. `abide:hydrate` and `abide:navigate` are WARNING channels (28.
|
|
5
|
+
// `.` RATHER THAN `:`. `abide:hydrate` and `abide:navigate` are WARNING channels (28.8) and these
|
|
6
6
|
// are performance entries — two namespaces that would otherwise be one grep.
|
|
7
7
|
//
|
|
8
8
|
// ONE NAME PER OP SOMEBODY WAITS ON, never per row. `shares()` sums every measure of a layer
|
package/src/ui/List.ts
CHANGED
|
@@ -18,7 +18,7 @@ export class List {
|
|
|
18
18
|
region: Region
|
|
19
19
|
rows: Row[]
|
|
20
20
|
/**
|
|
21
|
-
* The async iterable last started, or null — so a
|
|
21
|
+
* The async iterable last started, or null — so a rerun with the same iterable is not a new
|
|
22
22
|
* source.
|
|
23
23
|
*/
|
|
24
24
|
pumping: unknown
|
|
@@ -209,8 +209,8 @@ function settleList(
|
|
|
209
209
|
* async subject writes no rows inline and the region arrives empty. The pump fills it.
|
|
210
210
|
*/
|
|
211
211
|
function appendRows(list: List, source: AsyncIterable<unknown>): void {
|
|
212
|
-
// A
|
|
213
|
-
// the expression touches
|
|
212
|
+
// A RERUN IS NOT A NEW SOURCE. The subject is read inside a watch, so any other reactive
|
|
213
|
+
// the expression touches reruns it with the SAME iterable — and emptying the list to pump an
|
|
214
214
|
// async generator that has already run to completion left the region blank for good.
|
|
215
215
|
if (list.pumping === source) return
|
|
216
216
|
// A NEW SOURCE IS A NEW LIST. What the last one appended is stopped and removed, or its
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 39.1, 39.6 — THE ROUTER'S LEVEL BEING MOUNTED, set by the router around a page's or a layout's
|
|
2
|
+
// setup and markup: a component mounted inside one adds its `<head>` to that level's
|
|
3
|
+
// (`contributeHead`), and one mounted later — a row a list grew by — finds none and leaves the
|
|
4
|
+
// document's head alone. `unapplied` is the head the components built on the client contributed
|
|
5
|
+
// while it was set, which the router applies once it is unset rather than once a component.
|
|
6
|
+
export const MOUNTING_LEVEL: {
|
|
7
|
+
current:
|
|
8
|
+
| {
|
|
9
|
+
head: (() => string) | undefined
|
|
10
|
+
/** Every component head joined to the level's own, in the order they mounted. */
|
|
11
|
+
contributed: (() => string)[] | undefined
|
|
12
|
+
}
|
|
13
|
+
| undefined
|
|
14
|
+
unapplied: string
|
|
15
|
+
} = {
|
|
16
|
+
current: undefined,
|
|
17
|
+
unapplied: '',
|
|
18
|
+
}
|
package/src/ui/Mismatch.ts
CHANGED
package/src/ui/RawRun.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { removeUntil } from './regions.ts'
|
|
|
11
11
|
*
|
|
12
12
|
* `html` IS UNDEFINED FOR A RUN THE SERVER PAINTED. What is between the anchors is what the server
|
|
13
13
|
* parsed from the same value, so the first write keeps it (`setMarkup`) the way a hydrated arm keeps
|
|
14
|
-
* the arm it was painted with — reading markup back out of nodes is a
|
|
14
|
+
* the arm it was painted with — reading markup back out of nodes is a serialization, and not the
|
|
15
15
|
* string the server wrote.
|
|
16
16
|
*/
|
|
17
17
|
export class RawRun {
|
package/src/ui/Router.ts
CHANGED
|
@@ -8,10 +8,10 @@ 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
|
-
import { menuBridge } from './menuBridge.ts'
|
|
15
15
|
import { fillRegion, pageRegion, type Region } from './regions.ts'
|
|
16
16
|
import type { Scope, Stoppable } from './Scope.ts'
|
|
17
17
|
|
|
@@ -73,6 +73,8 @@ type Level = {
|
|
|
73
73
|
* wrote and the first navigation away from it withdraws that head like any other.
|
|
74
74
|
*/
|
|
75
75
|
head: (() => string) | undefined
|
|
76
|
+
/** The heads of the components mounted in this level, joined to `head` (`contributeHead`). */
|
|
77
|
+
contributed: (() => string)[] | undefined
|
|
76
78
|
/**
|
|
77
79
|
* 41.19 — this level's markup, left by its mount once its setup has run, for the router to run
|
|
78
80
|
* once every level below has been set up too; `undefined` once it has run, or where the module
|
|
@@ -144,20 +146,8 @@ export class Router {
|
|
|
144
146
|
* Read off the compiled `<style>` blocks. Nothing here looks at the document's stylesheets.
|
|
145
147
|
*/
|
|
146
148
|
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
149
|
|
|
155
|
-
constructor(
|
|
156
|
-
region: Region,
|
|
157
|
-
routes: Compiled[],
|
|
158
|
-
transitions: boolean,
|
|
159
|
-
shellHead: string,
|
|
160
|
-
) {
|
|
150
|
+
constructor(region: Region, routes: Compiled[], transitions: boolean) {
|
|
161
151
|
this.region = region
|
|
162
152
|
this.routes = routes
|
|
163
153
|
this.levels = []
|
|
@@ -167,7 +157,6 @@ export class Router {
|
|
|
167
157
|
this.navigation = 0
|
|
168
158
|
this.prefetched = new Set()
|
|
169
159
|
this.transitions = transitions
|
|
170
|
-
this.shellHead = shellHead
|
|
171
160
|
}
|
|
172
161
|
|
|
173
162
|
/**
|
|
@@ -186,7 +175,7 @@ export class Router {
|
|
|
186
175
|
/**
|
|
187
176
|
* Loads a route's chunks ahead of a navigation the reader has signaled.
|
|
188
177
|
*
|
|
189
|
-
* 30.14 — A NAVIGATION THE READER HAS
|
|
178
|
+
* 30.14 — A NAVIGATION THE READER HAS SIGNALED, LOADED BEFORE IT HAPPENS: the route's chunks
|
|
190
179
|
* are asked for on intent, so the click finds them in the module cache and setup — and with it
|
|
191
180
|
* every call the page makes — starts at once. Only the CODE: a call run on a hover would be a
|
|
192
181
|
* call nobody asked for, and for anything but a `GET` one with an effect. A failed load is left
|
|
@@ -385,11 +374,9 @@ export class Router {
|
|
|
385
374
|
// outermost first; a kept layout is not mounted again, so neither is its head.
|
|
386
375
|
this.mountChain(target, found, arriving, shared, into)
|
|
387
376
|
// 39.14 — what the leaving levels keyed and the arriving ones did not goes back to the
|
|
388
|
-
// nearest kept layout's, else
|
|
389
|
-
withdrawHead(
|
|
390
|
-
|
|
391
|
-
this.heads(shared, this.levels.length),
|
|
392
|
-
() => this.shellHead + this.heads(0, shared),
|
|
377
|
+
// nearest kept layout's, else leaves.
|
|
378
|
+
withdrawHead(leaving, this.heads(shared, this.levels.length), () =>
|
|
379
|
+
this.heads(0, shared),
|
|
393
380
|
)
|
|
394
381
|
// The top of the page, or — as the browser does on a load — the fragment's element.
|
|
395
382
|
if (options.keepScroll !== true) scrollToFragment(target.hash)
|
|
@@ -480,6 +467,7 @@ export class Router {
|
|
|
480
467
|
scope: undefined,
|
|
481
468
|
slot: undefined,
|
|
482
469
|
head: undefined,
|
|
470
|
+
contributed: undefined,
|
|
483
471
|
markup: undefined,
|
|
484
472
|
share: null,
|
|
485
473
|
}
|
|
@@ -505,7 +493,9 @@ export class Router {
|
|
|
505
493
|
// runs; a module mounted by the router leaves its markup rather than using this one.
|
|
506
494
|
// A deferred level answers the scope its setup made, so the watches that setup opened are
|
|
507
495
|
// stopped with the level even where its markup never runs — a slot that never opens.
|
|
508
|
-
level.scope =
|
|
496
|
+
level.scope = mounting(level, () =>
|
|
497
|
+
module.default(region, props, level),
|
|
498
|
+
)
|
|
509
499
|
return level.share
|
|
510
500
|
}
|
|
511
501
|
|
|
@@ -515,7 +505,7 @@ export class Router {
|
|
|
515
505
|
const markup = level.markup
|
|
516
506
|
if (markup === undefined) return
|
|
517
507
|
level.markup = undefined
|
|
518
|
-
level.scope = markup(region)
|
|
508
|
+
level.scope = mounting(level, () => markup(region))
|
|
519
509
|
}
|
|
520
510
|
|
|
521
511
|
/**
|
|
@@ -577,11 +567,6 @@ export class Router {
|
|
|
577
567
|
): void {
|
|
578
568
|
arrive(target.href, found.route.pattern, found.params)
|
|
579
569
|
this.chain = modules
|
|
580
|
-
// 38.89 — inside a desktop app, the window shows the menus this chain exports.
|
|
581
|
-
if (menuBridge() !== undefined)
|
|
582
|
-
void import('./reportMenu.ts').then((loaded) =>
|
|
583
|
-
loaded.reportMenu(modules as { menu?: unknown }[]),
|
|
584
|
-
)
|
|
585
570
|
this.standing = found.route
|
|
586
571
|
this.mounted = target.pathname
|
|
587
572
|
if (into === undefined) return
|
|
@@ -685,14 +670,30 @@ export function startRouter(
|
|
|
685
670
|
routes: Route[],
|
|
686
671
|
host: Element = document.body,
|
|
687
672
|
transitions = false,
|
|
688
|
-
shellHead = '',
|
|
689
673
|
): Router {
|
|
690
|
-
const router = new Router(
|
|
691
|
-
pageRegion(host),
|
|
692
|
-
rankRoutes(routes),
|
|
693
|
-
transitions,
|
|
694
|
-
shellHead,
|
|
695
|
-
)
|
|
674
|
+
const router = new Router(pageRegion(host), rankRoutes(routes), transitions)
|
|
696
675
|
CURRENT_ROUTER.current = router
|
|
697
676
|
return router
|
|
698
677
|
}
|
|
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
|
+
}
|
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
|
|
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
|
}
|
package/src/ui/TryRegion.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/src/ui/applyHead.ts
CHANGED
|
@@ -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'
|
package/src/ui/moveNode.ts
CHANGED
|
@@ -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
|
|
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 =
|
package/src/ui/reconcile.ts
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
*
|
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
|
|
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
|
|
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'
|
package/src/ui/setAttribute.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/src/ui/socketWire.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
205
|
+
* The mux a handler's address is carried over, made on first use.
|
|
205
206
|
*
|
|
206
|
-
* `SOCKET_ADDRESS` under
|
|
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
|
|
211
|
-
|
|
212
|
-
|
|
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', '/
|
|
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.
|
|
File without changes
|
package/scaffold/src/ui/app.html
DELETED
|
@@ -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
|
-
}
|
|
File without changes
|
|
File without changes
|