@abide/abide 0.110.4 → 0.112.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 +23 -0
- package/docs/REGISTRY.md +30 -29
- package/docs/RULEBOOK.md +98 -22
- package/docs/abide.md +46 -32
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +5 -0
- package/src/compiler/classifyTemplate.ts +129 -34
- package/src/compiler/compileApp.ts +2 -1
- package/src/compiler/emitClient.ts +128 -14
- package/src/compiler/renderMarkup.ts +14 -8
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/HEARTBEAT_MILLISECONDS.ts +2 -0
- package/src/server/IDLE_TIMEOUT_SECONDS.ts +6 -0
- package/src/server/answerRequest.ts +44 -3
- package/src/server/answerRpc.ts +14 -41
- package/src/server/answerSocket.ts +2 -2
- package/src/server/answerValue.ts +15 -3
- package/src/server/answerWorker.ts +195 -0
- package/src/server/appManifest.ts +48 -0
- package/src/server/assistant.ts +1 -1
- package/src/server/bridgeRelay.ts +4 -50
- package/src/server/claudeApi.ts +1 -1
- package/src/server/claudeCode.ts +3 -3
- package/src/server/clientBundle.ts +7 -0
- package/src/server/config.ts +14 -0
- package/src/server/createApp.ts +28 -4
- package/src/server/devWorker.ts +4 -1
- package/src/server/render.ts +2 -2
- package/src/server/requestScope.ts +31 -11
- package/src/server/responseRelay.ts +7 -7
- package/src/server/serveBuild.ts +2 -0
- package/src/server/serveDev.ts +4 -1
- package/src/server/streamEvent.ts +60 -1
- package/src/shared/ADDRESSES.ts +11 -0
- package/src/shared/ASCII_WHITESPACE.ts +6 -0
- package/src/shared/LONGEST_DELAY.ts +2 -0
- package/src/shared/OFFLINE_CACHES.ts +12 -0
- package/src/shared/OFFLINE_META.ts +7 -0
- package/src/shared/answerOf.ts +29 -4
- package/src/shared/attributeText.ts +1 -1
- package/src/shared/reactive/Expiry.ts +13 -1
- package/src/shared/reactive/Reactive.ts +13 -3
- package/src/shared/safeUrl.ts +8 -1
- package/src/ui/Level.ts +43 -95
- package/src/ui/Router.ts +37 -13
- package/src/ui/bindGroup.ts +41 -0
- package/src/ui/bindProperty.ts +1 -21
- package/src/ui/index.ts +5 -3
- package/src/ui/reconcileHead.ts +215 -0
- package/src/ui/registerWorker.ts +34 -0
- package/src/ui/routing.ts +1 -0
- package/src/ui/setClassGroup.ts +79 -0
- package/src/ui/setStyleGroup.ts +130 -0
- package/src/ui/socketWire.ts +33 -2
- package/src/ui/applyHead.ts +0 -214
package/docs/abide.md
CHANGED
|
@@ -264,7 +264,7 @@ Read on: [Open it to machines](/guide/open-it-to-machines.md)
|
|
|
264
264
|
|
|
265
265
|
## 10. Test, measure and ship
|
|
266
266
|
|
|
267
|
-
The CRM gets a reports page, and each report gets a test: what a click rewrote, what a change costs as the pipeline grows, and where a slow click spends its time. Then the CRM ships, as a server, a single binary or a desktop app.
|
|
267
|
+
The CRM gets a reports page, and each report gets a test: what a click rewrote, what a change costs as the pipeline grows, and where a slow click spends its time. Then the CRM ships, as a server, a single binary or a desktop app, and keeps its pages readable offline.
|
|
268
268
|
|
|
269
269
|
Read on: [Test, measure, ship](/guide/test-measure-and-ship.md)
|
|
270
270
|
|
|
@@ -4228,7 +4228,7 @@ test('a report that stops producing is refused 504', async () => {
|
|
|
4228
4228
|
|
|
4229
4229
|
On a handler that yields, the bound is per row. So one number lets a long report run and still catches a source that has stopped. A call past it is refused `504`, and the rows already sent stay on the page.
|
|
4230
4230
|
|
|
4231
|
-
|
|
4231
|
+
Without one, nothing is cut short: the server never closes a connection under a call it is still answering, a slow handler or a quiet stream alike. Its sibling is `maxBodySize`: the bytes a mutation's body may carry, refused `413` before a declared oversize body is read.
|
|
4232
4232
|
|
|
4233
4233
|
| Setting | Default | What it bounds |
|
|
4234
4234
|
| --- | --- | --- |
|
|
@@ -5680,7 +5680,7 @@ Without `--url`, the CLI reads `ABIDE_APP_URL`, then falls back to the local app
|
|
|
5680
5680
|
|
|
5681
5681
|
*Test the CRM, measure the work it does, and ship it.*
|
|
5682
5682
|
|
|
5683
|
-
The CRM gets a reports page, and each report gets a test: what a click rewrote, what a change costs as the pipeline grows, and where a slow click spends its time. Then the CRM ships, as a server, a single binary or a desktop app.
|
|
5683
|
+
The CRM gets a reports page, and each report gets a test: what a click rewrote, what a change costs as the pipeline grows, and where a slow click spends its time. Then the CRM ships, as a server, a single binary or a desktop app, and keeps its pages readable offline.
|
|
5684
5684
|
|
|
5685
5685
|
Each card is a file of the reports page, beside the test or the bench that proves it. The commands that ship the CRM close the chapter.
|
|
5686
5686
|
|
|
@@ -6407,6 +6407,19 @@ watch(() => printReport.on(() => print()))
|
|
|
6407
6407
|
|
|
6408
6408
|
The reports page adds a Reports menu while it is showing. `onMenu` is read the way markup is, so the menus follow the page's state. Each entry is a `MenuItem`: `'separator'`, an item with an `href`, one with a `MenuRole` the platform performs, or a `MenuEntry` made by `menuItem`. `MenuEntry.on` files what runs when it is picked, and hands back what stops it, which is what `watch` takes. In a browser nothing picks an item, and the page runs unchanged.
|
|
6409
6409
|
|
|
6410
|
+
## Keep the CRM readable offline
|
|
6411
|
+
|
|
6412
|
+
```
|
|
6413
|
+
abide start
|
|
6414
|
+
ABIDE_OFFLINE=0 abide start
|
|
6415
|
+
```
|
|
6416
|
+
|
|
6417
|
+
Each page the CRM opens is kept on the device, with the answers its handlers gave, the public files it loaded and the bundle it runs, until `ABIDE_OFFLINE=0` turns that off where the CRM is served. With the network gone, a page opened before loads again with its data and works. One never opened says the device is offline, and a link to it leaves the page you are on standing.
|
|
6418
|
+
|
|
6419
|
+
With `src/public/icon.png` beside it, each page also links a manifest, so a phone can add the CRM to its home screen, which is what keeps what was saved there for good.
|
|
6420
|
+
|
|
6421
|
+
A kept page shows what it showed when it was last opened, live updates stop, and a change fails until the network is back. A sign-in, a sign-out or a submitted form clears what was kept, and a new build replaces it. `abide dev` keeps nothing.
|
|
6422
|
+
|
|
6410
6423
|
## Next
|
|
6411
6424
|
|
|
6412
6425
|
That is the CRM: contacts, deals, a live feed, pages, access, operations, machines, and now reports, tested and shipped. Start again from [Start an app](/guide/start-an-app.md) with an app of your own.
|
|
@@ -6709,7 +6722,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
6709
6722
|
|
|
6710
6723
|
| Name | Signature | Meaning | Rules |
|
|
6711
6724
|
| --- | --- | --- | --- |
|
|
6712
|
-
| `s.invalidate` | `() => void` | Drop the cache behind the value. | [7.1](/rulebook#7.1), [7.2](/rulebook#7.2), [7.3](/rulebook#7.3), [7.6](/rulebook#7.6), [7.7](/rulebook#7.7), [7.9](/rulebook#7.9), [7.10](/rulebook#7.10), [7.12](/rulebook#7.12), [7.13](/rulebook#7.13), [7.14](/rulebook#7.14), [7.15](/rulebook#7.15), [7.16](/rulebook#7.16), [7.17](/rulebook#7.17), [11.51](/rulebook#11.51) |
|
|
6725
|
+
| `s.invalidate` | `() => void` | Drop the cache behind the value. | [7.1](/rulebook#7.1), [7.2](/rulebook#7.2), [7.3](/rulebook#7.3), [7.6](/rulebook#7.6), [7.7](/rulebook#7.7), [7.9](/rulebook#7.9), [7.10](/rulebook#7.10), [7.12](/rulebook#7.12), [7.13](/rulebook#7.13), [7.14](/rulebook#7.14), [7.15](/rulebook#7.15), [7.16](/rulebook#7.16), [7.17](/rulebook#7.17), [7.19](/rulebook#7.19), [11.51](/rulebook#11.51) |
|
|
6713
6726
|
| `s.refresh` | `() => void` | Reload while continuing to serve what is held. | [4.12](/rulebook#4.12), [7.1](/rulebook#7.1), [7.4](/rulebook#7.4), [7.5](/rulebook#7.5), [7.6](/rulebook#7.6), [7.11](/rulebook#7.11), [7.18](/rulebook#7.18), [11.51](/rulebook#11.51), [5.24](/rulebook#5.24) |
|
|
6714
6727
|
|
|
6715
6728
|
## Effects
|
|
@@ -7206,7 +7219,7 @@ Reactive primitives: what is written in place rather than imported.
|
|
|
7206
7219
|
|
|
7207
7220
|
| Name | Signature | Meaning | Rules |
|
|
7208
7221
|
| --- | --- | --- | --- |
|
|
7209
|
-
| `Rpc` | `<Value, Args, Failures, Produced = Value>(args?: Args, options?: { signal?: AbortSignal }) => Reactive<Value, Value, Failures \| Failed<'HttpError', undefined>, Produced>` | A handler reached over http, or in process on a server. | [16.3](/rulebook#16.3), [16.4](/rulebook#16.4), [16.8](/rulebook#16.8), [16.11](/rulebook#16.11), [16.25](/rulebook#16.25), [16.26](/rulebook#16.26), [16.27](/rulebook#16.27), [16.28](/rulebook#16.28), [16.29](/rulebook#16.29), [16.33](/rulebook#16.33), [16.43](/rulebook#16.43), [16.48](/rulebook#16.48), [16.49](/rulebook#16.49), [16.62](/rulebook#16.62), [41.5](/rulebook#41.5), [41.7](/rulebook#41.7), [41.12](/rulebook#41.12), [41.21](/rulebook#41.21), [41.22](/rulebook#41.22), [41.25](/rulebook#41.25), [41.26](/rulebook#41.26), [41.27](/rulebook#41.27) |
|
|
7222
|
+
| `Rpc` | `<Value, Args, Failures, Produced = Value>(args?: Args, options?: { signal?: AbortSignal }) => Reactive<Value, Value, Failures \| Failed<'HttpError', undefined>, Produced>` | A handler reached over http, or in process on a server. | [16.3](/rulebook#16.3), [16.4](/rulebook#16.4), [16.8](/rulebook#16.8), [16.11](/rulebook#16.11), [16.25](/rulebook#16.25), [16.26](/rulebook#16.26), [16.27](/rulebook#16.27), [16.28](/rulebook#16.28), [16.29](/rulebook#16.29), [16.33](/rulebook#16.33), [16.43](/rulebook#16.43), [16.48](/rulebook#16.48), [16.49](/rulebook#16.49), [16.62](/rulebook#16.62), [41.5](/rulebook#41.5), [41.7](/rulebook#41.7), [41.12](/rulebook#41.12), [41.21](/rulebook#41.21), [41.22](/rulebook#41.22), [41.25](/rulebook#41.25), [41.26](/rulebook#41.26), [41.27](/rulebook#41.27), [16.79](/rulebook#16.79) |
|
|
7210
7223
|
| `Value` | `unknown` | What the addressed `Reactive` holds. | [16.3](/rulebook#16.3), [16.30](/rulebook#16.30) |
|
|
7211
7224
|
| `Produced` | `unknown` | What it yields, per the unit rule. | [1.2](/rulebook#1.2), [16.3](/rulebook#16.3) |
|
|
7212
7225
|
| `Args` | `Record<string, JsonValue> \| undefined` | The arguments, in the body on a mutation and in the URL on a `GET` or `DELETE`. | [11.24](/rulebook#11.24), [16.18](/rulebook#16.18), [19.15](/rulebook#19.15) |
|
|
@@ -7314,7 +7327,7 @@ Reactive primitives: what is written in place rather than imported.
|
|
|
7314
7327
|
| --- | --- | --- | --- |
|
|
7315
7328
|
| `description` | `string` | The human description carried onto every generated surface. | [16.28](/rulebook#16.28) |
|
|
7316
7329
|
| `middleware` | `Middleware<{ request: Request; args: () => Args }, Value \| Failures>[]` | The rungs run per call. | [16.11](/rulebook#16.11), [16.12](/rulebook#16.12), [16.16](/rulebook#16.16), [16.17](/rulebook#16.17), [16.18](/rulebook#16.18), [16.19](/rulebook#16.19), [16.20](/rulebook#16.20) |
|
|
7317
|
-
| `timeout` | `number` | How long the call may
|
|
7330
|
+
| `timeout` | `number` | How long the call may go without progress. | [16.22](/rulebook#16.22), [16.58](/rulebook#16.58), [16.59](/rulebook#16.59), [16.60](/rulebook#16.60), [16.61](/rulebook#16.61), [16.77](/rulebook#16.77) |
|
|
7318
7331
|
| `clients` | `Clients` | Which surfaces carry this handler. | [16.37](/rulebook#16.37), [20.3](/rulebook#20.3) |
|
|
7319
7332
|
| `crossOrigin` | `boolean \| string[]` | Whether other origins may call it, or follow it over the mux. | [16.24](/rulebook#16.24), [18.2](/rulebook#18.2), [21.19](/rulebook#21.19) |
|
|
7320
7333
|
| `maxBodySize` | `number` | The largest request body a mutation will accept, in bytes. | [16.13](/rulebook#16.13), [16.54](/rulebook#16.54), [16.55](/rulebook#16.55) |
|
|
@@ -7427,7 +7440,7 @@ Reactive primitives: what is written in place rather than imported.
|
|
|
7427
7440
|
|
|
7428
7441
|
| Name | Signature | Meaning | Rules |
|
|
7429
7442
|
| --- | --- | --- | --- |
|
|
7430
|
-
| `sse` | `<T>(values: Values<T>) => Values<T>` | The same machine framed as server-sent events, fixed. | [16.45](/rulebook#16.45), [16.75](/rulebook#16.75), [17.6](/rulebook#17.6), [17.7](/rulebook#17.7) |
|
|
7443
|
+
| `sse` | `<T>(values: Values<T>) => Values<T>` | The same machine framed as server-sent events, fixed. | [16.45](/rulebook#16.45), [16.57](/rulebook#16.57), [16.75](/rulebook#16.75), [17.6](/rulebook#17.6), [17.7](/rulebook#17.7) |
|
|
7431
7444
|
|
|
7432
7445
|
## Taught in
|
|
7433
7446
|
|
|
@@ -7504,7 +7517,7 @@ Transports: what is written in place rather than imported.
|
|
|
7504
7517
|
| `access-control-allow-origin` | an rpc answer to another origin | The origin a handler admitted. | [21.19](/rulebook#21.19), [16.24](/rulebook#16.24) |
|
|
7505
7518
|
| `allow: GET, HEAD` | a page, a public file or the bundle, asked by another method | The methods the address answers. | [15.17](/rulebook#15.17) |
|
|
7506
7519
|
| `server-timing` | an rpc answer; `Middleware` and a `timing` span on any answer | The handler's duration, named `Direct`, `Seeded` from the render's run, `Cached` from a held `global` production, or `Started` to a stream's first chunk; the rungs' time; and each `timing` span. | [21.15](/rulebook#21.15), [21.20](/rulebook#21.20), [21.21](/rulebook#21.21), [24.6](/rulebook#24.6) |
|
|
7507
|
-
| `etag` | a `GET` answered from a `global` value | The version of the production answered. | [18.5](/rulebook#18.5), [18.6](/rulebook#18.6), [18.7](/rulebook#18.7), [18.9](/rulebook#18.9), [18.19](/rulebook#18.19), [18.20](/rulebook#18.20), [18.22](/rulebook#18.22), [18.23](/rulebook#18.23) |
|
|
7520
|
+
| `etag` | a `GET` answered from a `global` value | The version of the production answered. | [18.5](/rulebook#18.5), [18.6](/rulebook#18.6), [18.7](/rulebook#18.7), [18.9](/rulebook#18.9), [18.19](/rulebook#18.19), [18.20](/rulebook#18.20), [18.22](/rulebook#18.22), [18.23](/rulebook#18.23), [16.57](/rulebook#16.57) |
|
|
7508
7521
|
|
|
7509
7522
|
## Taught in
|
|
7510
7523
|
|
|
@@ -7715,7 +7728,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
7715
7728
|
|
|
7716
7729
|
| Name | Signature | Meaning | Rules |
|
|
7717
7730
|
| --- | --- | --- | --- |
|
|
7718
|
-
| `AssistantEvent` | `{ type: 'text'; text: string } \| { type: 'tool'; id: string; name: string } \| { type: 'input'; id: string; input: unknown } \| { type: 'result'; id: string; text: string; failed: boolean } \| { type: 'done'; stop: string; usage: { input: number; output: number } }` | What a conversation produces: text, a tool call as it starts, its input once written, its result, and the end. | [44.2](/rulebook#44.2), [44.11](/rulebook#44.11), [44.
|
|
7731
|
+
| `AssistantEvent` | `{ type: 'text'; text: string } \| { type: 'tool'; id: string; name: string } \| { type: 'input'; id: string; input: unknown } \| { type: 'result'; id: string; text: string; failed: boolean } \| { type: 'done'; stop: string; usage: { input: number; output: number } }` | What a conversation produces: text, a tool call as it starts, its input once written, its result, and the end. | [44.2](/rulebook#44.2), [44.11](/rulebook#44.11), [44.28](/rulebook#44.28) |
|
|
7719
7732
|
|
|
7720
7733
|
## Taught in
|
|
7721
7734
|
|
|
@@ -7751,7 +7764,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
7751
7764
|
|
|
7752
7765
|
| Name | Signature | Meaning | Rules |
|
|
7753
7766
|
| --- | --- | --- | --- |
|
|
7754
|
-
| `Provider` | `{ run(messages: AssistantMessage[], tools: Toolbox, limits: { maxTurns: number; signal: AbortSignal }): AsyncGenerator<AssistantEvent> }` | What runs a conversation. | [44.2](/rulebook#44.2), [44.
|
|
7767
|
+
| `Provider` | `{ run(messages: AssistantMessage[], tools: Toolbox, limits: { maxTurns: number; signal: AbortSignal }): AsyncGenerator<AssistantEvent> }` | What runs a conversation. | [44.2](/rulebook#44.2), [44.28](/rulebook#44.28) |
|
|
7755
7768
|
|
|
7756
7769
|
## Taught in
|
|
7757
7770
|
|
|
@@ -7841,10 +7854,10 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
7841
7854
|
|
|
7842
7855
|
| Name | Signature | Meaning | Rules |
|
|
7843
7856
|
| --- | --- | --- | --- |
|
|
7844
|
-
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | [44.5](/rulebook#44.5), [44.13](/rulebook#44.13), [44.17](/rulebook#44.17), [44.18](/rulebook#44.18), [44.19](/rulebook#44.19), [44.24](/rulebook#44.24), [44.25](/rulebook#44.25), [44.
|
|
7845
|
-
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; expires: number }` | The command that pairs a bridge with this browser, granting the built-in tools it is given. | [44.14](/rulebook#44.14), [44.
|
|
7846
|
-
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | [44.20](/rulebook#44.20), [44.22](/rulebook#44.22), [44.
|
|
7847
|
-
| `/__abide/bridge` | `GET`, `POST` | Where a bridge pairs, and is relayed its turns. | [36.7](/rulebook#36.7), [44.14](/rulebook#44.14), [44.15](/rulebook#44.15), [44.19](/rulebook#44.19), [44.23](/rulebook#44.23), [44.26](/rulebook#44.26), [
|
|
7857
|
+
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | [44.5](/rulebook#44.5), [44.13](/rulebook#44.13), [44.17](/rulebook#44.17), [44.18](/rulebook#44.18), [44.19](/rulebook#44.19), [44.24](/rulebook#44.24), [44.25](/rulebook#44.25), [44.27](/rulebook#44.27), [44.30](/rulebook#44.30) |
|
|
7858
|
+
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; expires: number }` | The command that pairs a bridge with this browser, granting the built-in tools it is given. | [44.14](/rulebook#44.14), [44.32](/rulebook#44.32) |
|
|
7859
|
+
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | [44.20](/rulebook#44.20), [44.22](/rulebook#44.22), [44.32](/rulebook#44.32) |
|
|
7860
|
+
| `/__abide/bridge` | `GET`, `POST` | Where a bridge pairs, and is relayed its turns. | [36.7](/rulebook#36.7), [44.14](/rulebook#44.14), [44.15](/rulebook#44.15), [44.19](/rulebook#44.19), [44.23](/rulebook#44.23), [44.26](/rulebook#44.26), [16.57](/rulebook#16.57), [16.78](/rulebook#16.78) |
|
|
7848
7861
|
|
|
7849
7862
|
## Taught in
|
|
7850
7863
|
|
|
@@ -7862,7 +7875,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
7862
7875
|
|
|
7863
7876
|
| Name | Signature | Meaning | Rules |
|
|
7864
7877
|
| --- | --- | --- | --- |
|
|
7865
|
-
| `ClaudeCodeConfig` | `{ model?: string; fallbackModel?: string; systemPrompt?: string; appendSystemPrompt?: string; effort?: 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'; maxBudgetUsd?: number; betas?: string[]; disallowedTools?: string[]; tools?: string[] }` | The Claude Code options a site may set for a turn, and the built-in tools it asks the visitor for. | [44.16](/rulebook#44.16), [44.
|
|
7878
|
+
| `ClaudeCodeConfig` | `{ model?: string; fallbackModel?: string; systemPrompt?: string; appendSystemPrompt?: string; effort?: 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'; maxBudgetUsd?: number; betas?: string[]; disallowedTools?: string[]; tools?: string[] }` | The Claude Code options a site may set for a turn, and the built-in tools it asks the visitor for. | [44.16](/rulebook#44.16), [44.29](/rulebook#44.29) |
|
|
7866
7879
|
|
|
7867
7880
|
## Taught in
|
|
7868
7881
|
|
|
@@ -7991,7 +8004,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
7991
8004
|
|
|
7992
8005
|
| Name | Signature | Meaning | Rules |
|
|
7993
8006
|
| --- | --- | --- | --- |
|
|
7994
|
-
| `online` | `Reactive<boolean>` | Whether the caller can reach the app. | [23.7](/rulebook#23.7), [23.8](/rulebook#23.8) |
|
|
8007
|
+
| `online` | `Reactive<boolean>` | Whether the caller can reach the app. | [23.7](/rulebook#23.7), [23.8](/rulebook#23.8), [18.33](/rulebook#18.33), [18.34](/rulebook#18.34) |
|
|
7995
8008
|
|
|
7996
8009
|
## Taught in
|
|
7997
8010
|
|
|
@@ -8300,13 +8313,13 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8300
8313
|
| `abide:principal` | `debug` `warning` | A principal set or cleared. | [28.8](/rulebook#28.8) |
|
|
8301
8314
|
| `abide:health` | `warning` | An `onHealth` that threw or answered a non-object. | [25.2](/rulebook#25.2), [28.8](/rulebook#28.8) |
|
|
8302
8315
|
| `abide:mcp` | `debug` `warning` | A handler withheld from MCP, a tool call answered, or a tool published with no description. | [20.3](/rulebook#20.3), [20.9](/rulebook#20.9), [20.18](/rulebook#20.18), [28.8](/rulebook#28.8) |
|
|
8303
|
-
| `abide:assistant` | `debug` | One line per conversation, once it ends. | [44.
|
|
8316
|
+
| `abide:assistant` | `debug` | One line per conversation, once it ends. | [44.31](/rulebook#44.31) |
|
|
8304
8317
|
| `abide:openapi` | `warning` | A handler whose schema had no JSON Schema export. | [20.11](/rulebook#20.11), [28.8](/rulebook#28.8) |
|
|
8305
8318
|
| `abide:render` | `warning` | An `error.abide` that itself failed. | [28.8](/rulebook#28.8) |
|
|
8306
8319
|
| `abide:refuse` | `warning` | A message formatter that threw. | [15.13](/rulebook#15.13), [28.8](/rulebook#28.8) |
|
|
8307
8320
|
| `abide:reactive` | `warning` | A failed revalidation over a value still being served. | [4.12](/rulebook#4.12), [28.8](/rulebook#28.8) |
|
|
8308
8321
|
| `abide:watch` | `warning` | A `watch` effect or `Disposer` that threw. | [12.6](/rulebook#12.6), [28.8](/rulebook#28.8) |
|
|
8309
|
-
| `abide:hydrate` / `abide:navigate` | `warning` | The browser lane. | [28.8](/rulebook#28.8), [41.10](/rulebook#41.10), [41.15](/rulebook#41.15), [41.24](/rulebook#41.24) |
|
|
8322
|
+
| `abide:hydrate` / `abide:navigate` | `warning` | The browser lane. | [28.8](/rulebook#28.8), [41.10](/rulebook#41.10), [41.15](/rulebook#41.15), [41.24](/rulebook#41.24), [45.10](/rulebook#45.10) |
|
|
8310
8323
|
|
|
8311
8324
|
## Taught in
|
|
8312
8325
|
|
|
@@ -8578,7 +8591,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8578
8591
|
|
|
8579
8592
|
| Name | Signature | Meaning | Rules |
|
|
8580
8593
|
| --- | --- | --- | --- |
|
|
8581
|
-
| `navigate` | `(url?: URL \| string, options?: { replace?: boolean, keepScroll?: boolean }) => Promise<void>` | Navigates. | [30.7](/rulebook#30.7), [30.9](/rulebook#30.9), [30.10](/rulebook#30.10), [30.11](/rulebook#30.11), [30.12](/rulebook#30.12), [30.13](/rulebook#30.13), [30.14](/rulebook#30.14), [30.15](/rulebook#30.15), [30.16](/rulebook#30.16), [30.17](/rulebook#30.17), [30.18](/rulebook#30.18), [30.20](/rulebook#30.20), [30.21](/rulebook#30.21), [30.22](/rulebook#30.22), [30.23](/rulebook#30.23), [30.24](/rulebook#30.24), [30.25](/rulebook#30.25), [30.27](/rulebook#30.27), [30.29](/rulebook#30.29), [30.30](/rulebook#30.30), [30.31](/rulebook#30.31) |
|
|
8594
|
+
| `navigate` | `(url?: URL \| string, options?: { replace?: boolean, keepScroll?: boolean }) => Promise<void>` | Navigates. | [30.7](/rulebook#30.7), [30.9](/rulebook#30.9), [30.10](/rulebook#30.10), [30.11](/rulebook#30.11), [30.12](/rulebook#30.12), [30.13](/rulebook#30.13), [30.14](/rulebook#30.14), [30.15](/rulebook#30.15), [30.16](/rulebook#30.16), [30.17](/rulebook#30.17), [30.18](/rulebook#30.18), [30.20](/rulebook#30.20), [30.21](/rulebook#30.21), [30.22](/rulebook#30.22), [30.23](/rulebook#30.23), [30.24](/rulebook#30.24), [30.25](/rulebook#30.25), [30.27](/rulebook#30.27), [30.29](/rulebook#30.29), [30.30](/rulebook#30.30), [30.31](/rulebook#30.31), [30.32](/rulebook#30.32), [45.12](/rulebook#45.12) |
|
|
8582
8595
|
|
|
8583
8596
|
## Taught in
|
|
8584
8597
|
|
|
@@ -8615,18 +8628,18 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8615
8628
|
| `{expr}` | Reactive text, escaped. | [32.1](/rulebook#32.1), [32.23](/rulebook#32.23) |
|
|
8616
8629
|
| formatting whitespace | What a run of whitespace renders as. | [33.17](/rulebook#33.17), [33.18](/rulebook#33.18) |
|
|
8617
8630
|
| `{raw(...)}` | Raw HTML. | [32.2](/rulebook#32.2), [32.25](/rulebook#32.25), [32.26](/rulebook#32.26), [32.30](/rulebook#32.30), [32.31](/rulebook#32.31), [32.70](/rulebook#32.70) |
|
|
8618
|
-
| `name={expr}` | A reactive attribute or property. | [32.3](/rulebook#32.3), [32.20](/rulebook#32.20), [32.23](/rulebook#32.23), [32.37](/rulebook#32.37) |
|
|
8631
|
+
| `name={expr}` | A reactive attribute or property. | [32.3](/rulebook#32.3), [32.20](/rulebook#32.20), [32.23](/rulebook#32.23), [32.37](/rulebook#32.37), [32.71](/rulebook#32.71) |
|
|
8619
8632
|
| `on<event>={fn}` | A native listener on an element, and an ordinary prop on a component. | [32.4](/rulebook#32.4) |
|
|
8620
|
-
| `name="…{expr}…"` | An interpolated quoted value. | [32.3](/rulebook#32.3) |
|
|
8621
|
-
| `bind:value` | Two-way bind on an element. | [5.16](/rulebook#5.16), [32.5](/rulebook#32.5), [32.58](/rulebook#32.58), [32.59](/rulebook#32.59), [32.60](/rulebook#32.60), [32.61](/rulebook#32.61), [32.62](/rulebook#32.62) |
|
|
8633
|
+
| `name="…{expr}…"` | An interpolated quoted value. | [32.3](/rulebook#32.3), [32.71](/rulebook#32.71) |
|
|
8634
|
+
| `bind:value` | Two-way bind on an element. | [5.16](/rulebook#5.16), [32.5](/rulebook#32.5), [32.58](/rulebook#32.58), [32.59](/rulebook#32.59), [32.60](/rulebook#32.60), [32.61](/rulebook#32.61), [32.62](/rulebook#32.62), [32.72](/rulebook#32.72) |
|
|
8622
8635
|
| `bind:prop={state}` | Hands a component prop the caller's `Reactive`, or a `{get, set}` pair, to write through. | [32.6](/rulebook#32.6), [33.6](/rulebook#33.6), [33.8](/rulebook#33.8), [14.18](/rulebook#14.18) |
|
|
8623
|
-
| `bind:checked` | A boolean bind on an input. | [32.7](/rulebook#32.7) |
|
|
8624
|
-
| `bind:open` | The same, on a `<details>`. | [32.7](/rulebook#32.7) |
|
|
8625
|
-
| `bind:group` | Radio or checkbox membership. | [32.8](/rulebook#32.8) |
|
|
8636
|
+
| `bind:checked` | A boolean bind on an input. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72) |
|
|
8637
|
+
| `bind:open` | The same, on a `<details>`. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72) |
|
|
8638
|
+
| `bind:group` | Radio or checkbox membership. | [32.8](/rulebook#32.8), [32.72](/rulebook#32.72) |
|
|
8626
8639
|
| `bind:value={{get, set}}` | Two-way bind over an explicit accessor pair, on an element or a component. | [32.5](/rulebook#32.5), [32.6](/rulebook#32.6) |
|
|
8627
8640
|
| `bind:element={Reactive<Element> \| ((element: Element) => void \| Disposer)}` | A node reference. | [32.9](/rulebook#32.9), [32.38](/rulebook#32.38), [32.66](/rulebook#32.66), [32.68](/rulebook#32.68), [32.69](/rulebook#32.69) |
|
|
8628
|
-
| `class:name={cond}` | Toggles a class. | [32.10](/rulebook#32.10) |
|
|
8629
|
-
| `style:prop={value}` | Sets one style property. | [32.10](/rulebook#32.10), [32.19](/rulebook#32.19) |
|
|
8641
|
+
| `class:name={cond}` | Toggles a class. | [32.10](/rulebook#32.10), [32.71](/rulebook#32.71) |
|
|
8642
|
+
| `style:prop={value}` | Sets one style property. | [32.10](/rulebook#32.10), [32.19](/rulebook#32.19), [32.71](/rulebook#32.71) |
|
|
8630
8643
|
| `{...expr}` | Spreads props or attributes. | [32.11](/rulebook#32.11), [32.37](/rulebook#32.37), [33.12](/rulebook#33.12), [33.13](/rulebook#33.13) |
|
|
8631
8644
|
| static template text | What an author writes outside a slot, in body text and in an attribute value. | [32.27](/rulebook#32.27), [32.28](/rulebook#32.28) |
|
|
8632
8645
|
|
|
@@ -8800,7 +8813,7 @@ Pages and routing: what is written in place rather than imported.
|
|
|
8800
8813
|
|
|
8801
8814
|
| Name | Signature | Meaning | Rules |
|
|
8802
8815
|
| --- | --- | --- | --- |
|
|
8803
|
-
| `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | [29.10](/rulebook#29.10), [35.4](/rulebook#35.4), [35.5](/rulebook#35.5), [35.6](/rulebook#35.6), [35.15](/rulebook#35.15), [35.16](/rulebook#35.16), [41.1](/rulebook#41.1), [41.2](/rulebook#41.2), [41.3](/rulebook#41.3), [41.4](/rulebook#41.4), [41.9](/rulebook#41.9), [41.13](/rulebook#41.13), [41.16](/rulebook#41.16), [41.17](/rulebook#41.17), [41.18](/rulebook#41.18), [41.19](/rulebook#41.19), [41.20](/rulebook#41.20), [41.28](/rulebook#41.28), [35.11](/rulebook#35.11) |
|
|
8816
|
+
| `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | [29.10](/rulebook#29.10), [35.4](/rulebook#35.4), [35.5](/rulebook#35.5), [35.6](/rulebook#35.6), [35.15](/rulebook#35.15), [35.16](/rulebook#35.16), [41.1](/rulebook#41.1), [41.2](/rulebook#41.2), [41.3](/rulebook#41.3), [41.4](/rulebook#41.4), [41.9](/rulebook#41.9), [41.13](/rulebook#41.13), [41.16](/rulebook#41.16), [41.17](/rulebook#41.17), [41.18](/rulebook#41.18), [41.19](/rulebook#41.19), [41.20](/rulebook#41.20), [41.28](/rulebook#41.28), [35.11](/rulebook#35.11), [41.29](/rulebook#41.29) |
|
|
8804
8817
|
|
|
8805
8818
|
## Taught in
|
|
8806
8819
|
|
|
@@ -8828,10 +8841,11 @@ Configuration: what is written in place rather than imported.
|
|
|
8828
8841
|
| `ABIDE_PRINCIPAL_TTL` | `number` | The principal cookie's life, in ms. | [26.15](/rulebook#26.15), [26.16](/rulebook#26.16) |
|
|
8829
8842
|
| `ABIDE_APP_TOKEN` | `string \| null` | The bearer the remote CLI sends. | [36.6](/rulebook#36.6) |
|
|
8830
8843
|
| `ABIDE_APP_URL` | `string \| null` | Names an app somewhere else, for the remote CLI. | [36.6](/rulebook#36.6) |
|
|
8831
|
-
| `ABIDE_RPC_TIMEOUT` | `number` | The default ms a call may go without progress. | [16.22](/rulebook#16.22), [16.
|
|
8844
|
+
| `ABIDE_RPC_TIMEOUT` | `number` | The default ms a call may go without progress. | [16.22](/rulebook#16.22), [16.58](/rulebook#16.58) |
|
|
8832
8845
|
| `ABIDE_MAX_REQUEST_BODY_SIZE` | `number` | The default ceiling on a mutation's body. | [16.13](/rulebook#16.13), [16.54](/rulebook#16.54), [16.55](/rulebook#16.55), [16.78](/rulebook#16.78) |
|
|
8833
8846
|
| `ABIDE_MAX_SOCKET_MESSAGE_SIZE` | `number` | The ceiling on a frame received over the socket mux, in bytes. | [18.28](/rulebook#18.28) |
|
|
8834
8847
|
| `ABIDE_MAX_SOCKET_SUBSCRIPTIONS` | `number` | How many values one connection to the socket mux may follow. | [18.31](/rulebook#18.31), [18.32](/rulebook#18.32) |
|
|
8848
|
+
| `ABIDE_OFFLINE` | `boolean` | Whether the app keeps its pages and its bundle on the device, for when the network is gone. | [45.1](/rulebook#45.1), [45.2](/rulebook#45.2), [45.3](/rulebook#45.3), [45.4](/rulebook#45.4), [45.5](/rulebook#45.5), [45.6](/rulebook#45.6), [45.7](/rulebook#45.7), [45.8](/rulebook#45.8), [45.9](/rulebook#45.9), [45.10](/rulebook#45.10), [45.11](/rulebook#45.11), [45.12](/rulebook#45.12), [45.13](/rulebook#45.13), [45.14](/rulebook#45.14), [45.15](/rulebook#45.15), [45.16](/rulebook#45.16) |
|
|
8835
8849
|
| `ABIDE_STOP_TIMEOUT` | `number` | How long `App.stop` drains, in ms. | [42.17](/rulebook#42.17), [42.18](/rulebook#42.18) |
|
|
8836
8850
|
| `ABIDE_MAX_STREAM_BUFFER_SIZE` | `number` | The cap on a stream transcript held in memory. | [36.18](/rulebook#36.18) |
|
|
8837
8851
|
| `ABIDE_MAX_LOG_BUFFER_COUNT` | `number` | How many lines `log.records` is trimmed back to. | [28.1](/rulebook#28.1) |
|
|
@@ -8852,7 +8866,7 @@ Configuration: what is written in place rather than imported.
|
|
|
8852
8866
|
| `**/public/**` | The files answered as authored, at their path below that directory. | [23.15](/rulebook#23.15), [23.16](/rulebook#23.16), [23.18](/rulebook#23.18), [36.11](/rulebook#36.11), [36.12](/rulebook#36.12), [36.13](/rulebook#36.13), [36.14](/rulebook#36.14), [36.15](/rulebook#36.15), [36.17](/rulebook#36.17), [21.12](/rulebook#21.12), [21.13](/rulebook#21.13), [21.14](/rulebook#21.14), [38.18](/rulebook#38.18), [38.19](/rulebook#38.19) |
|
|
8853
8867
|
| `src/public/favicon.ico` | The file answered at `/favicon.ico`. | [36.11](/rulebook#36.11) |
|
|
8854
8868
|
| `src/public/robots.txt` | The file answered at `/robots.txt`. | [36.11](/rulebook#36.11) |
|
|
8855
|
-
| `src/public/icon.png` · `src/public/icon.icns` | The files answered at `/icon.png` and `/icon.icns`, and the desktop app's icon. | [36.11](/rulebook#36.11), [38.79](/rulebook#38.79) |
|
|
8869
|
+
| `src/public/icon.png` · `src/public/icon.icns` | The files answered at `/icon.png` and `/icon.icns`, and the desktop app's icon. | [36.11](/rulebook#36.11), [38.79](/rulebook#38.79), [45.15](/rulebook#45.15) |
|
|
8856
8870
|
| `src/**/` | A directory of the app's, a segment of the route of every file directly in it. | [23.14](/rulebook#23.14), [23.17](/rulebook#23.17), [23.22](/rulebook#23.22) |
|
|
8857
8871
|
| `**/index.abide` | The page at its directory's route. | [19.1](/rulebook#19.1), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [23.14](/rulebook#23.14), [23.18](/rulebook#23.18) |
|
|
8858
8872
|
| `**/<name>.abide` | The page at its directory's route and then `<name>`. | [19.1](/rulebook#19.1), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [23.14](/rulebook#23.14), [23.21](/rulebook#23.21) |
|
|
@@ -9164,7 +9178,7 @@ Configuration: what is written in place rather than imported.
|
|
|
9164
9178
|
| `App` | `interface App` | What a host, a test or a script holds the app as. | [42.12](/rulebook#42.12), [42.13](/rulebook#42.13) |
|
|
9165
9179
|
| `App.fetch` | `(request: Request) => Promise<Response>` | The whole request path, entered in process. | [42.5](/rulebook#42.5), [42.6](/rulebook#42.6) |
|
|
9166
9180
|
| `App.run` | `<Result>(fn: () => Result \| Promise<Result>, options?: { request?: Request }) => Promise<Result>` | One scope around a function, and nothing else. | [42.7](/rulebook#42.7), [42.8](/rulebook#42.8), [11.38](/rulebook#11.38) |
|
|
9167
|
-
| `App.listen` | `(options?: { port?: number; hostname?: string; unix?: string }) => Promise<void>` | The socket, bound. | [42.9](/rulebook#42.9), [18.28](/rulebook#18.28), [18.29](/rulebook#18.29), [18.30](/rulebook#18.30) |
|
|
9181
|
+
| `App.listen` | `(options?: { port?: number; hostname?: string; unix?: string }) => Promise<void>` | The socket, bound. | [42.9](/rulebook#42.9), [16.23](/rulebook#16.23), [16.56](/rulebook#16.56), [18.28](/rulebook#18.28), [18.29](/rulebook#18.29), [18.30](/rulebook#18.30) |
|
|
9168
9182
|
| `App.stop` | `() => Promise<void>` | The drain and the teardown. | [42.10](/rulebook#42.10), [42.11](/rulebook#42.11), [42.15](/rulebook#42.15), [42.17](/rulebook#42.17), [37.4](/rulebook#37.4) |
|
|
9169
9183
|
| `App[Symbol.asyncDispose]` | `() => Promise<void>` | `App.stop`, under the name `await using` calls. | [42.14](/rulebook#42.14) |
|
|
9170
9184
|
|
|
@@ -10094,7 +10108,7 @@ CLI: what is written in place rather than imported.
|
|
|
10094
10108
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
|
|
10095
10109
|
| `abide check [dir…]` | The type-checker over a `.abide` tree. | [38.3](/rulebook#38.3), [38.31](/rulebook#38.31), [38.32](/rulebook#38.32), [38.33](/rulebook#38.33), [38.40](/rulebook#38.40), [38.105](/rulebook#38.105) |
|
|
10096
10110
|
| `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | [38.108](/rulebook#38.108) |
|
|
10097
|
-
| `abide dev [--port <n>]` | Watches the project and keeps the app up. | [37.8](/rulebook#37.8), [37.9](/rulebook#37.9), [38.4](/rulebook#38.4), [38.14](/rulebook#38.14), [38.15](/rulebook#38.15), [38.16](/rulebook#38.16), [38.17](/rulebook#38.17), [38.19](/rulebook#38.19), [38.24](/rulebook#38.24), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.28](/rulebook#38.28), [38.30](/rulebook#38.30), [38.35](/rulebook#38.35), [38.36](/rulebook#38.36), [38.37](/rulebook#38.37), [38.38](/rulebook#38.38), [38.41](/rulebook#38.41), [38.43](/rulebook#38.43), [38.58](/rulebook#38.58), [38.63](/rulebook#38.63), [38.64](/rulebook#38.64), [38.65](/rulebook#38.65), [38.94](/rulebook#38.94), [38.96](/rulebook#38.96), [38.97](/rulebook#38.97) |
|
|
10111
|
+
| `abide dev [--port <n>]` | Watches the project and keeps the app up. | [16.23](/rulebook#16.23), [16.56](/rulebook#16.56), [37.8](/rulebook#37.8), [37.9](/rulebook#37.9), [38.4](/rulebook#38.4), [38.14](/rulebook#38.14), [38.15](/rulebook#38.15), [38.16](/rulebook#38.16), [38.17](/rulebook#38.17), [38.19](/rulebook#38.19), [38.24](/rulebook#38.24), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.28](/rulebook#38.28), [38.30](/rulebook#38.30), [38.35](/rulebook#38.35), [38.36](/rulebook#38.36), [38.37](/rulebook#38.37), [38.38](/rulebook#38.38), [38.41](/rulebook#38.41), [38.43](/rulebook#38.43), [38.58](/rulebook#38.58), [38.63](/rulebook#38.63), [38.64](/rulebook#38.64), [38.65](/rulebook#38.65), [38.94](/rulebook#38.94), [38.96](/rulebook#38.96), [38.97](/rulebook#38.97) |
|
|
10098
10112
|
| `abide build` | Builds the client into content-hashed chunks and a manifest. | [38.43](/rulebook#38.43), [38.5](/rulebook#38.5), [38.18](/rulebook#38.18), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.30](/rulebook#38.30), [38.66](/rulebook#38.66), [38.94](/rulebook#38.94) |
|
|
10099
10113
|
| `abide start [--port <n>]` | Boots the built app. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.100](/rulebook#38.100), [38.20](/rulebook#38.20), [38.21](/rulebook#38.21), [38.22](/rulebook#38.22), [38.23](/rulebook#38.23), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.28](/rulebook#38.28), [38.29](/rulebook#38.29), [38.58](/rulebook#38.58), [38.67](/rulebook#38.67), [38.68](/rulebook#38.68), [38.95](/rulebook#38.95), [38.97](/rulebook#38.97), [38.110](/rulebook#38.110) |
|
|
10100
10114
|
| `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.7](/rulebook#38.7), [38.53](/rulebook#38.53), [38.54](/rulebook#38.54), [38.55](/rulebook#38.55), [38.58](/rulebook#38.58), [38.59](/rulebook#38.59), [38.60](/rulebook#38.60), [38.62](/rulebook#38.62) |
|
package/package.json
CHANGED
package/src/compiler/Lowering.ts
CHANGED
|
@@ -222,6 +222,11 @@ export class Lowering {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/** 32.67 — whether a position reads any row's item. */
|
|
226
|
+
readsAnItem(holder: object): boolean {
|
|
227
|
+
return this.holdsRead(holder, (read) => read.item !== -1)
|
|
228
|
+
}
|
|
229
|
+
|
|
225
230
|
/** 32.67 — whether a position reads `block`'s item. */
|
|
226
231
|
readsItem(holder: object, block: object): boolean {
|
|
227
232
|
const item = this.itemOf(block)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { ASCII_WHITESPACE } from '#shared/ASCII_WHITESPACE.ts'
|
|
1
2
|
import { escapeAttribute, escapeQuote } from '#shared/escapeText.ts'
|
|
2
3
|
import { isEventName } from '#shared/isEventName.ts'
|
|
3
4
|
import { safeUrl } from '#shared/safeUrl.ts'
|
|
4
5
|
import { URL_ATTRIBUTES } from '#shared/URL_ATTRIBUTES.ts'
|
|
6
|
+
import { decodeCharacterReferences } from './decodeCharacterReferences.ts'
|
|
5
7
|
import {
|
|
6
8
|
type Attribute,
|
|
7
9
|
bodiesOf,
|
|
@@ -90,8 +92,11 @@ export type ClassifiedAttribute = {
|
|
|
90
92
|
kind: 'bound'
|
|
91
93
|
bind: ValueBind
|
|
92
94
|
reflects: 'value' | 'checked' | 'open' | undefined
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
+
/**
|
|
96
|
+
* 32.8 — a group member's `value` attribute, which the group is compared against —
|
|
97
|
+
* `undefined` where it has none, and the input's value is then `on`.
|
|
98
|
+
*/
|
|
99
|
+
own: Attribute | undefined
|
|
95
100
|
}
|
|
96
101
|
// 32.9 — the node itself, which a server has none of: written out it was `bind:element=""`,
|
|
97
102
|
// inert and a tell. The client does not set it either; what the value takes is the element.
|
|
@@ -102,24 +107,39 @@ export type ClassifiedAttribute = {
|
|
|
102
107
|
// 32.10, 32.19 — a member of a `class` or `style` merge group (`MergeGroup`). `group` is set on
|
|
103
108
|
// the FIRST member only, where the whole group is written, which keeps the tag in the author's
|
|
104
109
|
// attribute order. `toggles` is the one class or style property a directive's binding writes,
|
|
105
|
-
//
|
|
110
|
+
// or a shared group's whole value at its first member (32.71), named by the directives'
|
|
111
|
+
// classes or properties, and `undefined` for any other member — see where that is decided.
|
|
106
112
|
| {
|
|
107
113
|
kind: 'merged'
|
|
108
114
|
group: MergeGroup | undefined
|
|
109
|
-
toggles:
|
|
115
|
+
toggles:
|
|
116
|
+
| {
|
|
117
|
+
write: 'class' | 'style' | 'classGroup' | 'styleGroup'
|
|
118
|
+
name: string
|
|
119
|
+
}
|
|
120
|
+
| undefined
|
|
110
121
|
}
|
|
111
122
|
)
|
|
112
123
|
|
|
113
124
|
/**
|
|
114
125
|
* Every attribute of an element, classified, in the order the tag carries them.
|
|
115
126
|
*
|
|
116
|
-
* `lowering` answers `live` (`Lowering.isLive`)
|
|
127
|
+
* `lowering` answers `live` (`Lowering.isLive`), and whether a shared `class` group can move
|
|
128
|
+
* (`Lowering.readsAnItem`: a row's item read at its top level is not live, and a row's `update`
|
|
129
|
+
* writes it).
|
|
117
130
|
*/
|
|
118
131
|
export function classifyAttributes(
|
|
119
132
|
node: ElementNode,
|
|
120
|
-
lowering: {
|
|
133
|
+
lowering: {
|
|
134
|
+
isLive(holder: object): boolean
|
|
135
|
+
readsAnItem(holder: object): boolean
|
|
136
|
+
},
|
|
121
137
|
): ClassifiedAttribute[] {
|
|
138
|
+
refuseTwoOwners(node)
|
|
122
139
|
const groups = mergeGroupsOf(node)
|
|
140
|
+
const sharing: ReturnType<typeof sharingOf>[] = []
|
|
141
|
+
for (const group of groups) sharing.push(sharingOf(group, lowering))
|
|
142
|
+
const held = heldClasses(groups)
|
|
123
143
|
const written = new Set<MergeGroup>()
|
|
124
144
|
const classified: ClassifiedAttribute[] = []
|
|
125
145
|
for (const attribute of node.attributes) {
|
|
@@ -130,33 +150,37 @@ export function classifyAttributes(
|
|
|
130
150
|
if (group !== undefined) {
|
|
131
151
|
const leads = !written.has(group)
|
|
132
152
|
written.add(group)
|
|
133
|
-
// 32.
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// they reran.
|
|
153
|
+
// 32.71 — A SHARED GROUP IS WRITTEN WHOLE AT ITS FIRST MEMBER, through `classList` or
|
|
154
|
+
// the CSSOM, as the server merged it, and moves where any member does — so a directive
|
|
155
|
+
// keeps what the plain attribute still holds without either knowing of the other.
|
|
137
156
|
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
// 32.10 — A STATIC PLAIN ATTRIBUTE IS LEFT UNBOUND, and so is a directive naming one of
|
|
158
|
+
// a static `class`'s classes, which is always on. The static value is never written
|
|
159
|
+
// again, and the directives write through `classList` or the CSSOM beside it.
|
|
160
|
+
const shared = sharing[groups.indexOf(group)]
|
|
161
|
+
if (shared !== undefined) {
|
|
162
|
+
classified.push({
|
|
163
|
+
attribute,
|
|
164
|
+
live: leads ? shared.live : live,
|
|
165
|
+
kind: 'merged',
|
|
166
|
+
group: leads ? group : undefined,
|
|
167
|
+
toggles: leads
|
|
168
|
+
? { write: `${group.name}Group`, name: shared.names }
|
|
169
|
+
: undefined,
|
|
170
|
+
})
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
const name = attribute.name.slice(group.name.length + 1)
|
|
148
174
|
classified.push({
|
|
149
175
|
attribute,
|
|
150
176
|
live,
|
|
151
177
|
kind: 'merged',
|
|
152
178
|
group: leads ? group : undefined,
|
|
153
179
|
toggles:
|
|
154
|
-
attribute === group.plain
|
|
180
|
+
attribute === group.plain ||
|
|
181
|
+
(group.name === 'class' && held.includes(name))
|
|
155
182
|
? undefined
|
|
156
|
-
: {
|
|
157
|
-
write: group.name,
|
|
158
|
-
name: attribute.name.slice(group.name.length + 1),
|
|
159
|
-
},
|
|
183
|
+
: { write: group.name, name },
|
|
160
184
|
})
|
|
161
185
|
continue
|
|
162
186
|
}
|
|
@@ -191,7 +215,7 @@ export function classifyAttributes(
|
|
|
191
215
|
: bind === 'group'
|
|
192
216
|
? 'checked'
|
|
193
217
|
: bind,
|
|
194
|
-
own:
|
|
218
|
+
own: attributeOf(node, 'value'),
|
|
195
219
|
})
|
|
196
220
|
else if (isElementBind(attribute.lowerName))
|
|
197
221
|
classified.push({ attribute, live, kind: 'element' })
|
|
@@ -294,6 +318,85 @@ function mergeGroupsOf(node: ElementNode): MergeGroup[] {
|
|
|
294
318
|
return groups
|
|
295
319
|
}
|
|
296
320
|
|
|
321
|
+
/**
|
|
322
|
+
* 32.71 — for a group whose plain attribute is an expression and where some member can move, so
|
|
323
|
+
* neither half can be written without the other: whether a member is `live`, and the directives'
|
|
324
|
+
* classes or properties, which the group owns before its first write. `undefined` for any other.
|
|
325
|
+
*/
|
|
326
|
+
function sharingOf(
|
|
327
|
+
group: MergeGroup,
|
|
328
|
+
lowering: Parameters<typeof classifyAttributes>[1],
|
|
329
|
+
): { live: boolean; names: string } | undefined {
|
|
330
|
+
if (!group.plain?.expression) return undefined
|
|
331
|
+
let live = false
|
|
332
|
+
let moves = false
|
|
333
|
+
for (const member of group.members) {
|
|
334
|
+
if (!member.expression) continue
|
|
335
|
+
if (lowering.isLive(member)) live = moves = true
|
|
336
|
+
else if (lowering.readsAnItem(member)) moves = true
|
|
337
|
+
}
|
|
338
|
+
if (!moves) return undefined
|
|
339
|
+
const names: string[] = []
|
|
340
|
+
for (const directive of group.directives)
|
|
341
|
+
names.push(directive.name.slice(group.name.length + 1))
|
|
342
|
+
return { live, names: names.join(' ') }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** 32.71 — the classes a static `class` beside a `class:name` holds, each always on. */
|
|
346
|
+
function heldClasses(groups: MergeGroup[]): string[] {
|
|
347
|
+
for (const group of groups)
|
|
348
|
+
if (group.name === 'class' && group.plain && !group.plain.expression)
|
|
349
|
+
return decodeCharacterReferences(
|
|
350
|
+
group.plain.value,
|
|
351
|
+
'attribute',
|
|
352
|
+
).split(ASCII_WHITESPACE)
|
|
353
|
+
return []
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 32.72 — WHAT A `bind:` WRITES IS THE BIND'S ALONE: `checked` beside `bind:checked` or
|
|
358
|
+
* `bind:group`, `value` beside `bind:value`, any `bind:name` beside `name` — and an
|
|
359
|
+
* `<option selected>` inside a `<select bind:value>`, and a `<textarea bind:value>`'s text. Two
|
|
360
|
+
* owners of one value render whichever the browser keeps and move with whichever wrote last.
|
|
361
|
+
*/
|
|
362
|
+
function refuseTwoOwners(node: ElementNode): void {
|
|
363
|
+
for (const attribute of node.attributes) {
|
|
364
|
+
const lower = attribute.lowerName
|
|
365
|
+
if (!lower.startsWith('bind:') || lower === 'bind:element') continue
|
|
366
|
+
const owned =
|
|
367
|
+
lower === 'bind:group' ? 'checked' : lower.slice('bind:'.length)
|
|
368
|
+
const beside = attributeOf(node, owned)
|
|
369
|
+
if (beside !== undefined)
|
|
370
|
+
throw new Error(
|
|
371
|
+
`abide: <${node.tag}> has ${attribute.name} and ${beside.name} — one value, two owners. ${attribute.name} writes ${owned} already; drop ${beside.name}. RULEBOOK 32.72.`,
|
|
372
|
+
)
|
|
373
|
+
if (owned !== 'value') continue
|
|
374
|
+
if (node.lowerTag === 'textarea' && node.children.length > 0)
|
|
375
|
+
throw new Error(
|
|
376
|
+
`abide: <${node.tag} ${attribute.name}> has text of its own — one value, two owners. The bind writes the text; drop what stands between the tags. RULEBOOK 32.72.`,
|
|
377
|
+
)
|
|
378
|
+
if (node.lowerTag === 'select' && holdsSelectedOption(node.children))
|
|
379
|
+
throw new Error(
|
|
380
|
+
`abide: <${node.tag} ${attribute.name}> has an <option selected> — one value, two owners. The bind selects the option its value holds; drop selected. RULEBOOK 32.72.`,
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Whether an `<option>` with a `selected` attribute stands anywhere among `nodes`, blocks included. */
|
|
386
|
+
function holdsSelectedOption(nodes: readonly TemplateNode[]): boolean {
|
|
387
|
+
for (const node of nodes) {
|
|
388
|
+
if (
|
|
389
|
+
node.kind === 'element' &&
|
|
390
|
+
node.lowerTag === 'option' &&
|
|
391
|
+
attributeOf(node, 'selected') !== undefined
|
|
392
|
+
)
|
|
393
|
+
return true
|
|
394
|
+
for (const body of bodiesOf(node))
|
|
395
|
+
if (holdsSelectedOption(body.children)) return true
|
|
396
|
+
}
|
|
397
|
+
return false
|
|
398
|
+
}
|
|
399
|
+
|
|
297
400
|
/** An element's attribute by its folded name, or `undefined`. */
|
|
298
401
|
export function attributeOf(
|
|
299
402
|
node: ElementNode,
|
|
@@ -304,14 +407,6 @@ export function attributeOf(
|
|
|
304
407
|
return undefined
|
|
305
408
|
}
|
|
306
409
|
|
|
307
|
-
/** An attribute's static value, or `undefined` where it is absent or an expression. */
|
|
308
|
-
function staticAttribute(node: ElementNode, name: string): string | undefined {
|
|
309
|
-
const attribute = attributeOf(node, name)
|
|
310
|
-
return attribute === undefined || attribute.expression
|
|
311
|
-
? undefined
|
|
312
|
-
: attribute.value
|
|
313
|
-
}
|
|
314
|
-
|
|
315
410
|
/**
|
|
316
411
|
* Whether a tag names a component: it starts with an uppercase ASCII letter.
|
|
317
412
|
*
|
|
@@ -525,7 +525,7 @@ function tableOf(
|
|
|
525
525
|
*/
|
|
526
526
|
function clientTable(routes: AppRoute[], transitions: boolean): string {
|
|
527
527
|
return `// Written by \`abide build\`. The addresses this app answers, and what to load for each.
|
|
528
|
-
import { listenForRoutes, startRouter } from '${ROUTING_SPECIFIER}'
|
|
528
|
+
import { listenForRoutes, registerWorker, startRouter } from '${ROUTING_SPECIFIER}'
|
|
529
529
|
|
|
530
530
|
export const routes = [
|
|
531
531
|
${tableOf(routes)}
|
|
@@ -538,6 +538,7 @@ export function start(host: Element) {
|
|
|
538
538
|
const router = startRouter(routes, host, ${transitions})
|
|
539
539
|
listenForRoutes(router)
|
|
540
540
|
const ready = router.hydrate()
|
|
541
|
+
registerWorker()
|
|
541
542
|
return { router, ready }
|
|
542
543
|
}
|
|
543
544
|
`
|