@abide/abide 0.114.1 → 0.116.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 (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/docs/REGISTRY.md +38 -16
  3. package/docs/RULEBOOK.md +173 -12
  4. package/docs/abide.md +266 -24
  5. package/package.json +4 -2
  6. package/src/compiler/REACTIVE_MEMBERS.ts +1 -0
  7. package/src/compiler/classifyTemplate.ts +21 -14
  8. package/src/compiler/emitClient.ts +5 -1
  9. package/src/server/BRIDGE_VERSION.ts +1 -1
  10. package/src/server/IMAGE_TYPES.ts +10 -0
  11. package/src/server/OFFLINE_STALE.ts +10 -0
  12. package/src/server/answerRequest.ts +39 -10
  13. package/src/server/answerRpc.ts +25 -5
  14. package/src/server/answerWorker.ts +173 -28
  15. package/src/server/assistant.ts +81 -12
  16. package/src/server/claudeApi.ts +25 -20
  17. package/src/server/claudeCode.ts +30 -10
  18. package/src/server/createApp.ts +8 -2
  19. package/src/server/generatedSurfaces.ts +48 -14
  20. package/src/server/index.ts +2 -0
  21. package/src/server/render.ts +3 -2
  22. package/src/server/seedBuffer.ts +28 -9
  23. package/src/shared/KEEP_HEADER.ts +7 -0
  24. package/src/shared/KEEP_OPENED.ts +7 -0
  25. package/src/shared/KEPT_HEADER.ts +6 -0
  26. package/src/shared/LOAD_OFFLINE.ts +15 -0
  27. package/src/shared/OUTBOX_SENDING.ts +9 -0
  28. package/src/shared/REACHED_MESSAGE.ts +7 -0
  29. package/src/shared/answerOf.ts +14 -2
  30. package/src/shared/framing.ts +10 -2
  31. package/src/shared/index.ts +2 -0
  32. package/src/shared/outbox.ts +116 -0
  33. package/src/shared/reactive/Keyed.ts +9 -0
  34. package/src/shared/reactive/Reactive.ts +197 -3
  35. package/src/shared/reactive/SELECTION_MEMBERS.ts +1 -0
  36. package/src/shared/reactive/callLoad.ts +8 -0
  37. package/src/shared/reactive/index.ts +23 -1
  38. package/src/shared/rpc.ts +25 -5
  39. package/src/shared/rpcRequest.ts +12 -8
  40. package/src/ui/Router.ts +28 -7
  41. package/src/ui/bindFiles.ts +42 -0
  42. package/src/ui/index.ts +1 -0
  43. package/src/ui/outboxQueue.ts +427 -0
  44. package/src/ui/socketWire.ts +31 -2
  45. package/src/ui/wire.ts +111 -10
package/docs/abide.md CHANGED
@@ -38,6 +38,7 @@ at its address with `.md` on the end, and the links below point at those files.
38
38
  - `Selection` — `reference/types/Selection.md`
39
39
  - `busy` — `reference/busy.md`
40
40
  - `ready` — `reference/ready.md`
41
+ - `synced` — `reference/synced.md`
41
42
  - `refresh` — `reference/refresh.md`
42
43
  - `invalidate` — `reference/invalidate.md`
43
44
  - `refuse` — `reference/refuse.md`
@@ -71,10 +72,12 @@ at its address with `.md` on the end, and the links below point at those files.
71
72
  - Generated surfaces — `reference/generated-surfaces.md`
72
73
  - `assistant` — `reference/assistant.md`
73
74
  - `AssistantMessage` — `reference/types/AssistantMessage.md`
75
+ - `AssistantContent` — `reference/types/AssistantContent.md`
74
76
  - `AssistantEvent` — `reference/types/AssistantEvent.md`
75
77
  - `AssistantOptions` — `reference/types/AssistantOptions.md`
76
78
  - `Provider` — `reference/types/Provider.md`
77
79
  - `Toolbox` — `reference/types/Toolbox.md`
80
+ - `ToolAnswer` — `reference/types/ToolAnswer.md`
78
81
  - `ToolDefinition` — `reference/types/ToolDefinition.md`
79
82
  - `claudeApi` — `reference/claudeApi.md`
80
83
  - `ClaudeApiConfig` — `reference/types/ClaudeApiConfig.md`
@@ -87,6 +90,8 @@ at its address with `.md` on the end, and the links below point at those files.
87
90
  - `route` — `reference/route.md`
88
91
  - `Params` — `reference/types/Params.md`
89
92
  - `online` — `reference/online.md`
93
+ - `outbox` — `reference/outbox.md`
94
+ - `QueuedCall` — `reference/types/QueuedCall.md`
90
95
  - `reachable` — `reference/reachable.md`
91
96
  - `trace` — `reference/trace.md`
92
97
  - `server` — `reference/server.md`
@@ -591,6 +596,7 @@ test('typing writes the value, and the value fills the field', async () => {
591
596
  | `bind:checked` | a checkbox, to a boolean |
592
597
  | `bind:open` | a `<details>`, to whether it is open |
593
598
  | `bind:group` | radios to one value, or checkboxes to an array of their `value`s |
599
+ | `bind:files` | a file input, to an array of the files picked |
594
600
  | `bind:value={{get, set}}` | a field to a pair you write, where the value is computed |
595
601
  | `bind:prop={state}` | a component's prop, so the child's write reaches your value |
596
602
 
@@ -4889,7 +4895,7 @@ test('the health check carries the database beside the baseline', async () => {
4889
4895
 
4890
4896
  ## The browser going offline is a reactive value
4891
4897
 
4892
- **`online` — the connection, read by name** — A contact edit made on a train is not saved. Take the browser offline in DevTools' Network panel and the banner says so.
4898
+ **`online` — the connection, read by name** — On a train the network comes and goes. Take the browser offline in DevTools' Network panel and the banner says so.
4893
4899
 
4894
4900
  ```abide src/ops/lib/OfflineBanner.abide
4895
4901
  <script>
@@ -4897,7 +4903,9 @@ import { online } from '@abide/abide'
4897
4903
  </script>
4898
4904
 
4899
4905
  {#if !online}
4900
- <p class="status">You are offline. Edits to contacts are not saved.</p>
4906
+ <p class="status">
4907
+ You are offline. Calls you log are sent once you are back.
4908
+ </p>
4901
4909
  {/if}
4902
4910
 
4903
4911
  <ul class="fields">
@@ -4938,6 +4946,107 @@ watch(() => { if (online) refresh() })
4938
4946
  </script>
4939
4947
  ```
4940
4948
 
4949
+ ## A call logged offline is sent when the network is back
4950
+
4951
+ **`offline` — what a call settles to with no network** — A call logged on a train should not be lost in a tunnel. Take the browser offline in DevTools' Network panel, log a call, then go back online.
4952
+
4953
+ ```abide src/ops/lib/CallLog.abide
4954
+ <script>
4955
+ import { outbox, reactive } from '@abide/abide'
4956
+ import listCalls from '../listCalls.ts'
4957
+ import logCall from '../logCall.ts'
4958
+
4959
+ const calls = listCalls()
4960
+ let note = reactive('')
4961
+
4962
+ function log() {
4963
+ const call = { id: crypto.randomUUID(), note }
4964
+ note = ''
4965
+ // With no network: shown at once, and sent from the outbox later.
4966
+ logCall(call, {
4967
+ offline: () => calls.patch((held) => held.push(call)),
4968
+ })
4969
+ }
4970
+ </script>
4971
+
4972
+ <div class="row">
4973
+ <input bind:value={note} placeholder="What Grace said">
4974
+ <button onclick={log}>Log call</button>
4975
+ </div>
4976
+
4977
+ <ul class="fields stacked">
4978
+ {#for call of calls.ready() ? calls : [] by call.id}
4979
+ <li><span>call</span><strong>{call.note}</strong></li>
4980
+ {/for}
4981
+ </ul>
4982
+
4983
+ <ul class="tiles">
4984
+ <li><span>waiting</span>
4985
+ <strong>{outbox(logCall).length}</strong></li>
4986
+ <li><span>saved</span>
4987
+ <strong>{calls.synced() ? 'yes' : 'not yet'}</strong></li>
4988
+ </ul>
4989
+ ```
4990
+
4991
+ ```ts src/ops/logCall.ts
4992
+ import { POST } from '@abide/abide/server'
4993
+ import { type Call, calls } from './lib/calls.ts'
4994
+
4995
+ /** Logs a call once, however often it is sent: the browser picks its id. */
4996
+ export default POST(
4997
+ ({ id, note }: Call) => {
4998
+ calls.patch((held) => {
4999
+ if (!held.some((call) => call.id === id)) held.push({ id, note })
5000
+ })
5001
+ },
5002
+ { description: 'Logs a call against a contact.' },
5003
+ )
5004
+ ```
5005
+
5006
+ ```ts src/ops/lib/CallLog.test.ts
5007
+ import { expect, test } from 'bun:test'
5008
+ import { flushSync } from '@abide/abide'
5009
+ import { mount } from '@abide/harness/abide'
5010
+ import { calls } from './calls.ts'
5011
+ import CallLog from './CallLog.abide'
5012
+
5013
+ test('a call logged with the network is saved, and nothing waits', async () => {
5014
+ using _log = await mount(CallLog({}))
5015
+ const input = document.querySelector('input') as HTMLInputElement
5016
+ input.value = 'Sent the quote'
5017
+ input.dispatchEvent(new Event('input', { bubbles: true }))
5018
+ document.querySelector('button')?.click()
5019
+ await Bun.sleep(10)
5020
+ flushSync()
5021
+ expect(calls().map((call) => call.note)).toContain('Sent the quote')
5022
+ const tiles = [...document.querySelectorAll('.tiles strong')]
5023
+ expect(tiles.map((tile) => tile.textContent)).toEqual(['0', 'yes'])
5024
+ })
5025
+
5026
+ test('a call sent twice under one id is logged once', async () => {
5027
+ const { default: logCall } = await import('../logCall.ts')
5028
+ const call = { id: 'again', note: 'Asked twice' }
5029
+ await logCall(call)
5030
+ await logCall(call)
5031
+ expect(calls().filter((each) => each.id === 'again').length).toBe(1)
5032
+ })
5033
+ ```
5034
+
5035
+ A call given `offline` that gets no answer settles to what `offline` returns. A mutation then waits in the outbox, kept on the device through a reload, and is sent once the network answers: oldest first, one at a time, by one tab for all of them. Here `offline` also adds the call to the list, so it shows at once. `outbox` is asked of a selection as `busy` is, and answers a list: `outbox(logCall)` is the calls waiting for that handler.
5036
+
5037
+ What is shown before the server has it says so: `synced()` is false on a value written in the browser rather than made by its producer, and on anything read from one. The list keeps its added call until the outbox is empty, then reads the server's again. A call is sent with the same `idempotency-key` every time, and `logCall` takes the browser's id, so a send whose answer was lost and is sent again logs the call once.
5038
+
5039
+ | Name | What it does for the CRM |
5040
+ | --- | --- |
5041
+ | `outbox` · `rpc.outbox` | the calls waiting to be sent, and the ones the server refused, over a selection |
5042
+ | `QueuedCall` | one call in the outbox: its key, its address, its args, and how often it was sent |
5043
+ | `call.value` | what the call settled to: its stand-in, read as unsynced, until the server answers, and its refusal as `error()`, after a reload too |
5044
+ | `call.retry` | sends a refused call again |
5045
+ | `call.discard` | drops a call, sent or not |
5046
+ | `s.synced` | whether a value holds what its producer made |
5047
+ | `k.synced` · `rpc.synced` | the same, over a keyed value's or a handler's entries |
5048
+ | `synced` | the same, over a selection |
5049
+
4941
5050
  ## Next
4942
5051
 
4943
5052
  - [Open it to machines](/guide/open-it-to-machines.md) — the CRM's handlers, described to other programs
@@ -5298,8 +5407,11 @@ const recorded: Provider = {
5298
5407
  const input = { name: 'Ada' }
5299
5408
  yield { type: 'tool', id: 'toolu_1', name }
5300
5409
  yield { type: 'input', id: 'toolu_1', input }
5301
- const answered = await tools.call(name, input)
5302
- yield { type: 'result', id: 'toolu_1', ...answered }
5410
+ const { content, failed } = await tools.call(name, input)
5411
+ // A search answers words, so its one piece is text.
5412
+ const [words] = content
5413
+ const text = words?.type === 'text' ? words.text : ''
5414
+ yield { type: 'result', id: 'toolu_1', text, failed }
5303
5415
  yield { type: 'text', text: 'Ada Lovelace is at Analytical Engines.' }
5304
5416
  yield { type: 'done', stop: 'end_turn', usage: { input: 0, output: 0 } }
5305
5417
  },
@@ -5352,11 +5464,13 @@ test('the model calls the CRM search, through the app', async () => {
5352
5464
 
5353
5465
  | Name | What it does for the CRM |
5354
5466
  | --- | --- |
5355
- | `AssistantMessage` | One turn of the transcript, `{ role, content }`. The last is the user's. |
5467
+ | `AssistantMessage` | One turn of the transcript, `{ role, content }`. The last is the user's. Its `content` is its words, or its pieces in order. |
5468
+ | `AssistantContent` | One piece of a turn: `text`, or an `image` as base64 with its type, so a rep can show the assistant a photo of a business card. PNG, JPEG, GIF and WebP, in the rep's own turns. |
5356
5469
  | `AssistantOptions` | `mcp` to offer the tools, and `maxTurns`, 10 by default, to cap the model's turns in one call. |
5357
5470
  | `AssistantEvent` | What the page reads: `text`, `tool` as a call starts, `input` once it is written, `result`, then `done` with the stop reason and token usage. The same events from every provider. |
5358
- | `Provider` | What runs the conversation: one method, `run`. Write one for another model, or a recording for a test. |
5471
+ | `Provider` | What runs the conversation: one method, `run`, handed every turn as its pieces. Write one for another model, or a recording for a test. |
5359
5472
  | `Toolbox` | What a provider is handed: `list` of the tools, and `call` to run one as the asker. |
5473
+ | `ToolAnswer` | What `call` answers: its `content`, the same pieces as a turn — the tool's text, or the image a handler drew, which the model is shown as a picture — and whether it failed. |
5360
5474
  | `ToolDefinition` | One tool in `list`: its name, description and input schema. |
5361
5475
  | `abide:assistant` | The log namespace that writes one `debug` line per conversation once it ends: the stop reason, the transcript's length, the tool calls, the tokens and the time. |
5362
5476
 
@@ -5489,7 +5603,12 @@ test('the body is sent as written, with the transcript', async () => {
5489
5603
  max_tokens: 1024,
5490
5604
  system: 'Answer questions about contacts.',
5491
5605
  stream: true,
5492
- messages: [{ role: 'user', content: 'Which company is Ada at?' }],
5606
+ messages: [
5607
+ {
5608
+ role: 'user',
5609
+ content: [{ type: 'text', text: 'Which company is Ada at?' }],
5610
+ },
5611
+ ],
5493
5612
  })
5494
5613
  })
5495
5614
  ```
@@ -6422,7 +6541,15 @@ abide start
6422
6541
  ABIDE_OFFLINE=0 abide start
6423
6542
  ```
6424
6543
 
6425
- 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.
6544
+ 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. A kept page opens at once, however weak the signal, and the network is asked for the next copy behind it. With the network gone, a page opened in the last week 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.
6545
+
6546
+ | Name | What it does for the CRM |
6547
+ | --- | --- |
6548
+ | `cache-control: private, stale-while-revalidate=604800, stale-if-error=604800` | what a page and a handler's answer carry, so the device shows what it kept while it asks again |
6549
+ | `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=604800` | the same, for a public file |
6550
+ | `clear-site-data: "cache"` | what a sign-in or a sign-out answers, so the browser forgets what it kept for the last person |
6551
+
6552
+ What is kept is what those headers allow, and nothing else decides it. A handler that writes `cache-control: no-store` through `response` is never kept, and an answer with a `ttl` is shown from the device until its `max-age` runs out. A refresh always asks the network.
6426
6553
 
6427
6554
  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.
6428
6555
 
@@ -6464,6 +6591,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
6464
6591
  | [`Selection`](/reference/types/Selection.md) | `@abide/abide` |
6465
6592
  | [`busy`](/reference/busy.md) | `@abide/abide` |
6466
6593
  | [`ready`](/reference/ready.md) | `@abide/abide` |
6594
+ | [`synced`](/reference/synced.md) | `@abide/abide` |
6467
6595
  | [`refresh`](/reference/refresh.md) | `@abide/abide` |
6468
6596
  | [`invalidate`](/reference/invalidate.md) | `@abide/abide` |
6469
6597
  | [`refuse`](/reference/refuse.md) | `@abide/abide` |
@@ -6512,10 +6640,12 @@ Arrive here knowing which name you want. Each page is read off the registry: the
6512
6640
  | --- | --- |
6513
6641
  | [`assistant`](/reference/assistant.md) | `@abide/abide/server` |
6514
6642
  | [`AssistantMessage`](/reference/types/AssistantMessage.md) | `@abide/abide/server` |
6643
+ | [`AssistantContent`](/reference/types/AssistantContent.md) | `@abide/abide/server` |
6515
6644
  | [`AssistantEvent`](/reference/types/AssistantEvent.md) | `@abide/abide/server` |
6516
6645
  | [`AssistantOptions`](/reference/types/AssistantOptions.md) | `@abide/abide/server` |
6517
6646
  | [`Provider`](/reference/types/Provider.md) | `@abide/abide/server` |
6518
6647
  | [`Toolbox`](/reference/types/Toolbox.md) | `@abide/abide/server` |
6648
+ | [`ToolAnswer`](/reference/types/ToolAnswer.md) | `@abide/abide/server` |
6519
6649
  | [`ToolDefinition`](/reference/types/ToolDefinition.md) | `@abide/abide/server` |
6520
6650
  | [`claudeApi`](/reference/claudeApi.md) | `@abide/abide/server` |
6521
6651
  | [`ClaudeApiConfig`](/reference/types/ClaudeApiConfig.md) | `@abide/abide/server` |
@@ -6533,6 +6663,8 @@ Arrive here knowing which name you want. Each page is read off the registry: the
6533
6663
  | [`route`](/reference/route.md) | `@abide/abide` |
6534
6664
  | [`Params`](/reference/types/Params.md) | `@abide/abide` |
6535
6665
  | [`online`](/reference/online.md) | `@abide/abide` |
6666
+ | [`outbox`](/reference/outbox.md) | `@abide/abide` |
6667
+ | [`QueuedCall`](/reference/types/QueuedCall.md) | `@abide/abide` |
6536
6668
  | [`reachable`](/reference/reachable.md) | `@abide/abide/server` |
6537
6669
  | [`trace`](/reference/trace.md) | `@abide/abide/server` |
6538
6670
  | [`server`](/reference/server.md) | `@abide/abide/server` |
@@ -6724,6 +6856,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
6724
6856
  | `s.busy` | `() => boolean` | Work is in flight for this value. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.4](/rulebook#3.4), [3.7](/rulebook#3.7), [3.9](/rulebook#3.9), [5.15](/rulebook#5.15), [7.5](/rulebook#7.5), [7.8](/rulebook#7.8), [11.19](/rulebook#11.19), [11.20](/rulebook#11.20), [11.21](/rulebook#11.21), [11.22](/rulebook#11.22), [11.50](/rulebook#11.50) |
6725
6857
  | `s.ready` | `() => boolean` | There is a value to serve. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.5](/rulebook#3.5), [3.6](/rulebook#3.6), [3.8](/rulebook#3.8), [7.8](/rulebook#7.8), [11.20](/rulebook#11.20), [11.54](/rulebook#11.54), [11.58](/rulebook#11.58) |
6726
6858
  | `s.error` | `() => Failures \| undefined` | The standing refusal. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.5](/rulebook#3.5), [4.10](/rulebook#4.10), [4.11](/rulebook#4.11), [4.12](/rulebook#4.12) |
6859
+ | `s.synced` | `() => boolean` | What is held is what the producer made. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.10](/rulebook#3.10), [3.11](/rulebook#3.11), [3.12](/rulebook#3.12), [3.13](/rulebook#3.13) |
6727
6860
  | `s.isError` | `<Name extends Failures['name']>(error: unknown, name: Name) => error is Extract<Failures, { name: Name }>` | Whether a caught failure is the one named. | [3.1](/rulebook#3.1) |
6728
6861
 
6729
6862
  ## Triggers
@@ -6746,6 +6879,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
6746
6879
  - [Talk to the server](/guide/talk-to-the-server.md)
6747
6880
  - [Keep it current](/guide/keep-it-current.md)
6748
6881
  - [Make it live](/guide/make-it-live.md)
6882
+ - [Run it](/guide/run-it.md)
6749
6883
 
6750
6884
  ---
6751
6885
 
@@ -6768,6 +6902,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
6768
6902
  | --- | --- | --- | --- |
6769
6903
  | `k.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to matching entries. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.12](/rulebook#13.12) |
6770
6904
  | `k.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to matching entries. | [13.11](/rulebook#13.11), [13.12](/rulebook#13.12) |
6905
+ | `k.synced` | `(pattern?: Partial<Args>) => boolean` | `synced` narrowed to matching entries. | [13.12](/rulebook#13.12), [13.18](/rulebook#13.18) |
6771
6906
  | `k.invalidate` | `(pattern?: Partial<Args>) => void` | `invalidate` narrowed to matching entries. | [7.2](/rulebook#7.2), [7.3](/rulebook#7.3), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12) |
6772
6907
  | `k.refresh` | `(pattern?: Partial<Args>) => void` | `refresh` narrowed to matching entries. | [7.4](/rulebook#7.4), [7.5](/rulebook#7.5), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12) |
6773
6908
 
@@ -6775,6 +6910,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
6775
6910
 
6776
6911
  - [Edit a contact](/guide/edit-a-contact.md)
6777
6912
  - [Keep it current](/guide/keep-it-current.md)
6913
+ - [Run it](/guide/run-it.md)
6778
6914
 
6779
6915
  ---
6780
6916
 
@@ -7028,7 +7164,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
7028
7164
 
7029
7165
  | Name | Signature | Meaning | Rules |
7030
7166
  | --- | --- | --- | --- |
7031
- | `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.12](/rulebook#13.12) |
7167
+ | `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.12](/rulebook#13.12), [46.11](/rulebook#46.11) |
7032
7168
 
7033
7169
  ## Taught in
7034
7170
 
@@ -7054,6 +7190,24 @@ Arrive here knowing which name you want. Each page is read off the registry: the
7054
7190
 
7055
7191
  ---
7056
7192
 
7193
+ <!-- reference/synced.md -->
7194
+
7195
+ # `synced`
7196
+
7197
+ `import { synced } from '@abide/abide'`
7198
+
7199
+ ## Selections — over many entries
7200
+
7201
+ | Name | Signature | Meaning | Rules |
7202
+ | --- | --- | --- | --- |
7203
+ | `synced` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection holds what its producer made. | [13.12](/rulebook#13.12), [13.18](/rulebook#13.18) |
7204
+
7205
+ ## Taught in
7206
+
7207
+ - [Run it](/guide/run-it.md)
7208
+
7209
+ ---
7210
+
7057
7211
  <!-- reference/refresh.md -->
7058
7212
 
7059
7213
  # `refresh`
@@ -7227,12 +7381,14 @@ Reactive primitives: what is written in place rather than imported.
7227
7381
 
7228
7382
  | Name | Signature | Meaning | Rules |
7229
7383
  | --- | --- | --- | --- |
7230
- | `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) |
7384
+ | `Rpc` | `<Value, Args, Failures, Produced = Value>(args?: Args, options?: { signal?: AbortSignal; offline?: (args: Args) => Value }) => 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), [16.80](/rulebook#16.80), [16.85](/rulebook#16.85) |
7385
+ | `offline` | `(args: Args) => Value` | What a call settles to where a browser could not get its answer. | [16.81](/rulebook#16.81), [16.82](/rulebook#16.82), [16.83](/rulebook#16.83), [16.84](/rulebook#16.84) |
7231
7386
  | `Value` | `unknown` | What the addressed `Reactive` holds. | [16.3](/rulebook#16.3), [16.30](/rulebook#16.30) |
7232
7387
  | `Produced` | `unknown` | What it yields, per the unit rule. | [1.2](/rulebook#1.2), [16.3](/rulebook#16.3) |
7233
7388
  | `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) |
7234
- | `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.12](/rulebook#13.12) |
7389
+ | `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.12](/rulebook#13.12), [46.11](/rulebook#46.11) |
7235
7390
  | `rpc.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to the handler's matching entries. | [13.11](/rulebook#13.11), [13.12](/rulebook#13.12) |
7391
+ | `rpc.synced` | `(pattern?: Partial<Args>) => boolean` | `synced` narrowed to the handler's matching entries. | [13.12](/rulebook#13.12), [13.18](/rulebook#13.18) |
7236
7392
  | `rpc.invalidate` | `(pattern?: Partial<Args>) => void` | `invalidate` narrowed to the handler's matching entries. | [7.2](/rulebook#7.2), [7.3](/rulebook#7.3), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12) |
7237
7393
  | `rpc.refresh` | `(pattern?: Partial<Args>) => void` | `refresh` narrowed to the handler's matching entries. | [7.4](/rulebook#7.4), [7.5](/rulebook#7.5), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12) |
7238
7394
  | `rpc.isError` | `<Name extends Failures['name']>(error: unknown, name: Name) => error is Extract<Failures, { name: Name }>` | Whether a caught failure is the one named. | [16.8](/rulebook#16.8) |
@@ -7241,11 +7397,18 @@ Reactive primitives: what is written in place rather than imported.
7241
7397
  | `rpc.url` | `(args?: Args) => string` | The address, resolved against the mount. | [16.9](/rulebook#16.9), [16.10](/rulebook#16.10), [30.28](/rulebook#30.28) |
7242
7398
  | `rpc.description` | `string \| undefined` | The human description. | [16.28](/rulebook#16.28) |
7243
7399
 
7400
+ ## `outbox`
7401
+
7402
+ | Name | Signature | Meaning | Rules |
7403
+ | --- | --- | --- | --- |
7404
+ | `rpc.outbox` | `(pattern?: Partial<Args>) => QueuedCall[]` | `outbox` narrowed to the handler's calls. | [46.1](/rulebook#46.1), [13.12](/rulebook#13.12) |
7405
+
7244
7406
  ## Taught in
7245
7407
 
7246
7408
  - [Talk to the server](/guide/talk-to-the-server.md)
7247
7409
  - [Keep it current](/guide/keep-it-current.md)
7248
7410
  - [Make it live](/guide/make-it-live.md)
7411
+ - [Run it](/guide/run-it.md)
7249
7412
 
7250
7413
  ---
7251
7414
 
@@ -7518,6 +7681,9 @@ Transports: what is written in place rather than imported.
7518
7681
  | `cache-control: no-store` | `/__abide/openapi.json`, `/__abide/mcp` | Refuses caching for an answer about the app's surfaces. | [21.4](/rulebook#21.4) |
7519
7682
  | `cache-control: public, max-age=31536000, immutable` | the built bundle | A chunk addressed by its own content hash. | [21.5](/rulebook#21.5) |
7520
7683
  | `cache-control: public, max-age=0, must-revalidate` | a file under a `public` directory | A file at an address the build never chose. | [21.12](/rulebook#21.12) |
7684
+ | `cache-control: private, stale-while-revalidate=604800, stale-if-error=604800` | a page, a `GET`'s answer, where `ABIDE_OFFLINE` registers its worker | The default that lets a copy kept on the device stand in for the network. | [21.22](/rulebook#21.22), [21.11](/rulebook#21.11), [21.17](/rulebook#21.17) |
7685
+ | `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=604800` | a file under a `public` directory, where `ABIDE_OFFLINE` registers its worker | The same, for a file at an address the build never chose. | [21.23](/rulebook#21.23) |
7686
+ | `clear-site-data: "cache"` | an answer carrying `abide-principal`, where pages are kept offline | Drops what the browser's own cache kept for the caller signed in before. | [21.24](/rulebook#21.24) |
7521
7687
  | `etag` | a file under a `public` directory | The answered bytes, addressed by their content. | [21.13](/rulebook#21.13), [21.14](/rulebook#21.14) |
7522
7688
  | `referrer-policy: strict-origin-when-cross-origin` | a page | The browsers' own default, written down. | [21.6](/rulebook#21.6) |
7523
7689
  | `vary` | wherever an answer depends on a request header | Names the header the answer varied on. | [21.7](/rulebook#21.7) |
@@ -7682,7 +7848,7 @@ Generated surfaces: what is written in place rather than imported.
7682
7848
 
7683
7849
  | Name | Signature | Meaning | Rules |
7684
7850
  | --- | --- | --- | --- |
7685
- | `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.9](/rulebook#20.9), [20.10](/rulebook#20.10), [20.12](/rulebook#20.12), [20.13](/rulebook#20.13), [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [20.17](/rulebook#20.17), [20.19](/rulebook#20.19), [20.20](/rulebook#20.20), [36.7](/rulebook#36.7), [16.78](/rulebook#16.78) |
7851
+ | `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.9](/rulebook#20.9), [20.10](/rulebook#20.10), [20.12](/rulebook#20.12), [20.13](/rulebook#20.13), [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [20.17](/rulebook#20.17), [20.19](/rulebook#20.19), [20.20](/rulebook#20.20), [20.21](/rulebook#20.21), [20.22](/rulebook#20.22), [36.7](/rulebook#36.7), [16.78](/rulebook#16.78) |
7686
7852
 
7687
7853
  ## Taught in
7688
7854
 
@@ -7700,7 +7866,7 @@ Generated surfaces: what is written in place rather than imported.
7700
7866
 
7701
7867
  | Name | Signature | Meaning | Rules |
7702
7868
  | --- | --- | --- | --- |
7703
- | `assistant` | `(messages: AssistantMessage[], provider: Provider, options?: AssistantOptions) => AsyncGenerator<AssistantEvent>` | A conversation with a model, as the events it produces. | [44.1](/rulebook#44.1), [44.2](/rulebook#44.2), [44.3](/rulebook#44.3), [44.4](/rulebook#44.4), [44.21](/rulebook#44.21) |
7869
+ | `assistant` | `(messages: AssistantMessage[], provider: Provider, options?: AssistantOptions) => AsyncGenerator<AssistantEvent>` | A conversation with a model, as the events it produces. | [44.1](/rulebook#44.1), [44.2](/rulebook#44.2), [44.3](/rulebook#44.3), [44.4](/rulebook#44.4), [44.21](/rulebook#44.21), [44.36](/rulebook#44.36), [44.37](/rulebook#44.37), [44.39](/rulebook#44.39) |
7704
7870
 
7705
7871
  ## Taught in
7706
7872
 
@@ -7718,7 +7884,25 @@ Generated surfaces: what is written in place rather than imported.
7718
7884
 
7719
7885
  | Name | Signature | Meaning | Rules |
7720
7886
  | --- | --- | --- | --- |
7721
- | `AssistantMessage` | `{ role: 'user' \| 'assistant'; content: string }` | One turn of the transcript a caller sends. | [44.3](/rulebook#44.3), [44.4](/rulebook#44.4) |
7887
+ | `AssistantMessage` | `{ role: 'user' \| 'assistant'; content: string \| AssistantContent[] }` | One turn of the transcript a caller sends: its words, or its pieces in order. | [44.3](/rulebook#44.3), [44.4](/rulebook#44.4) |
7888
+
7889
+ ## Taught in
7890
+
7891
+ - [Open it to machines](/guide/open-it-to-machines.md)
7892
+
7893
+ ---
7894
+
7895
+ <!-- reference/types/AssistantContent.md -->
7896
+
7897
+ # `AssistantContent`
7898
+
7899
+ `import type { AssistantContent } from '@abide/abide/server'`
7900
+
7901
+ ## Assistant
7902
+
7903
+ | Name | Signature | Meaning | Rules |
7904
+ | --- | --- | --- | --- |
7905
+ | `AssistantContent` | `{ type: 'text'; text: string } \| { type: 'image'; data: string; mimeType: string }` | A piece of a turn: words, or an image as base64 with its type. | [44.5](/rulebook#44.5), [44.35](/rulebook#44.35), [44.36](/rulebook#44.36), [44.37](/rulebook#44.37), [44.38](/rulebook#44.38), [44.39](/rulebook#44.39) |
7722
7906
 
7723
7907
  ## Taught in
7724
7908
 
@@ -7772,7 +7956,7 @@ Generated surfaces: what is written in place rather than imported.
7772
7956
 
7773
7957
  | Name | Signature | Meaning | Rules |
7774
7958
  | --- | --- | --- | --- |
7775
- | `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) |
7959
+ | `Provider` | `{ run(messages: (AssistantMessage & { content: AssistantContent[] })[], tools: Toolbox, limits: { maxTurns: number; signal: AbortSignal }): AsyncGenerator<AssistantEvent> }` | What runs a conversation. | [44.2](/rulebook#44.2), [44.28](/rulebook#44.28), [44.39](/rulebook#44.39) |
7776
7960
 
7777
7961
  ## Taught in
7778
7962
 
@@ -7790,7 +7974,25 @@ Generated surfaces: what is written in place rather than imported.
7790
7974
 
7791
7975
  | Name | Signature | Meaning | Rules |
7792
7976
  | --- | --- | --- | --- |
7793
- | `Toolbox` | `{ list: ToolDefinition[]; call(name: string, input: unknown): Promise<{ text: string; failed: boolean }> }` | The app's MCP tools, called as the asker. | [44.6](/rulebook#44.6), [44.7](/rulebook#44.7) |
7977
+ | `Toolbox` | `{ list: ToolDefinition[]; call(name: string, input: unknown): Promise<ToolAnswer> }` | The app's MCP tools, called as the asker. | [44.6](/rulebook#44.6), [44.7](/rulebook#44.7), [44.35](/rulebook#44.35) |
7978
+
7979
+ ## Taught in
7980
+
7981
+ - [Open it to machines](/guide/open-it-to-machines.md)
7982
+
7983
+ ---
7984
+
7985
+ <!-- reference/types/ToolAnswer.md -->
7986
+
7987
+ # `ToolAnswer`
7988
+
7989
+ `import type { ToolAnswer } from '@abide/abide/server'`
7990
+
7991
+ ## Assistant
7992
+
7993
+ | Name | Signature | Meaning | Rules |
7994
+ | --- | --- | --- | --- |
7995
+ | `ToolAnswer` | `{ content: AssistantContent[]; failed: boolean }` | What a tool call answered: its pieces, and whether it failed. | [20.21](/rulebook#20.21), [20.22](/rulebook#20.22), [44.35](/rulebook#44.35) |
7794
7996
 
7795
7997
  ## Taught in
7796
7998
 
@@ -7826,7 +8028,7 @@ Generated surfaces: what is written in place rather than imported.
7826
8028
 
7827
8029
  | Name | Signature | Meaning | Rules |
7828
8030
  | --- | --- | --- | --- |
7829
- | `claudeApi` | `(config: ClaudeApiConfig) => Provider` | The Claude Messages API as a provider. | [44.9](/rulebook#44.9), [44.10](/rulebook#44.10), [44.12](/rulebook#44.12), [44.21](/rulebook#44.21) |
8031
+ | `claudeApi` | `(config: ClaudeApiConfig) => Provider` | The Claude Messages API as a provider. | [44.9](/rulebook#44.9), [44.10](/rulebook#44.10), [44.12](/rulebook#44.12), [44.21](/rulebook#44.21), [44.35](/rulebook#44.35), [44.38](/rulebook#44.38) |
7830
8032
 
7831
8033
  ## Taught in
7832
8034
 
@@ -8020,6 +8222,45 @@ Generated surfaces: what is written in place rather than imported.
8020
8222
 
8021
8223
  ---
8022
8224
 
8225
+ <!-- reference/outbox.md -->
8226
+
8227
+ # `outbox`
8228
+
8229
+ `import { outbox } from '@abide/abide'`
8230
+
8231
+ ## `outbox`
8232
+
8233
+ | Name | Signature | Meaning | Rules |
8234
+ | --- | --- | --- | --- |
8235
+ | `outbox` | `<Chosen extends Selection>(selection?: Chosen, pattern?: Partial<Args>) => QueuedCall[]` | The calls a browser has queued and not delivered, the refused ones included. | [46.1](/rulebook#46.1), [46.2](/rulebook#46.2), [46.3](/rulebook#46.3), [46.4](/rulebook#46.4), [46.5](/rulebook#46.5), [46.6](/rulebook#46.6), [46.7](/rulebook#46.7), [46.8](/rulebook#46.8), [46.9](/rulebook#46.9), [46.10](/rulebook#46.10), [46.14](/rulebook#46.14), [46.15](/rulebook#46.15), [46.16](/rulebook#46.16), [46.17](/rulebook#46.17), [46.20](/rulebook#46.20), [18.35](/rulebook#18.35) |
8236
+ | `call.value` | `Reactive<Value, Value, Failed<string, unknown>>` | The value a queued call settled. | [46.5](/rulebook#46.5), [46.7](/rulebook#46.7), [46.18](/rulebook#46.18), [46.19](/rulebook#46.19) |
8237
+ | `call.retry` | `() => void` | Sends a queued call again. | [46.6](/rulebook#46.6), [46.12](/rulebook#46.12) |
8238
+ | `call.discard` | `() => void` | Drops a queued call. | [46.13](/rulebook#46.13) |
8239
+
8240
+ ## Taught in
8241
+
8242
+ - [Run it](/guide/run-it.md)
8243
+
8244
+ ---
8245
+
8246
+ <!-- reference/types/QueuedCall.md -->
8247
+
8248
+ # `QueuedCall`
8249
+
8250
+ `import type { QueuedCall } from '@abide/abide'`
8251
+
8252
+ ## `outbox`
8253
+
8254
+ | Name | Signature | Meaning | Rules |
8255
+ | --- | --- | --- | --- |
8256
+ | `QueuedCall` | `{ id: string; method: 'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'; url: string; args: Record<string, JsonValue> \| undefined; queuedAt: number; attempts: number; value: Reactive<Value, Value, Failed<string, unknown>>; retry: () => void; discard: () => void }` | One call `outbox` holds. | [46.1](/rulebook#46.1), [46.10](/rulebook#46.10) |
8257
+
8258
+ ## Taught in
8259
+
8260
+ - [Run it](/guide/run-it.md)
8261
+
8262
+ ---
8263
+
8023
8264
  <!-- reference/reachable.md -->
8024
8265
 
8025
8266
  # `reachable`
@@ -8599,7 +8840,7 @@ Generated surfaces: what is written in place rather than imported.
8599
8840
 
8600
8841
  | Name | Signature | Meaning | Rules |
8601
8842
  | --- | --- | --- | --- |
8602
- | `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) |
8843
+ | `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), [45.24](/rulebook#45.24) |
8603
8844
 
8604
8845
  ## Taught in
8605
8846
 
@@ -8639,11 +8880,12 @@ Generated surfaces: what is written in place rather than imported.
8639
8880
  | `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.70](/rulebook#32.70), [32.71](/rulebook#32.71) |
8640
8881
  | `on<event>={fn}` | A native listener on an element, and an ordinary prop on a component. | [32.4](/rulebook#32.4) |
8641
8882
  | `name="…{expr}…"` | An interpolated quoted value. | [32.3](/rulebook#32.3), [32.71](/rulebook#32.71) |
8642
- | `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) |
8883
+ | `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), [32.74](/rulebook#32.74) |
8643
8884
  | `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) |
8644
- | `bind:checked` | A boolean bind on an input. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72) |
8645
- | `bind:open` | The same, on a `<details>`. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72) |
8646
- | `bind:group` | Radio or checkbox membership. | [32.8](/rulebook#32.8), [32.72](/rulebook#32.72) |
8885
+ | `bind:checked` | A boolean bind on an input. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
8886
+ | `bind:open` | The same, on a `<details>`. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
8887
+ | `bind:group` | Radio or checkbox membership. | [32.8](/rulebook#32.8), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
8888
+ | `bind:files` | A file input's files, as a list. | [32.73](/rulebook#32.73), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
8647
8889
  | `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) |
8648
8890
  | `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) |
8649
8891
  | `class:name={cond}` | Toggles a class. | [32.10](/rulebook#32.10), [32.71](/rulebook#32.71) |
@@ -8821,7 +9063,7 @@ Pages and routing: what is written in place rather than imported.
8821
9063
 
8822
9064
  | Name | Signature | Meaning | Rules |
8823
9065
  | --- | --- | --- | --- |
8824
- | `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) |
9066
+ | `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), [41.30](/rulebook#41.30) |
8825
9067
 
8826
9068
  ## Taught in
8827
9069
 
@@ -8853,7 +9095,7 @@ Configuration: what is written in place rather than imported.
8853
9095
  | `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) |
8854
9096
  | `ABIDE_MAX_SOCKET_MESSAGE_SIZE` | `number` | The ceiling on a frame received over the socket mux, in bytes. | [18.28](/rulebook#18.28) |
8855
9097
  | `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) |
8856
- | `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) |
9098
+ | `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), [45.17](/rulebook#45.17), [45.18](/rulebook#45.18), [45.19](/rulebook#45.19), [45.20](/rulebook#45.20), [45.21](/rulebook#45.21), [45.22](/rulebook#45.22), [45.23](/rulebook#45.23), [45.24](/rulebook#45.24) |
8857
9099
  | `ABIDE_STOP_TIMEOUT` | `number` | How long `App.stop` drains, in ms. | [42.17](/rulebook#42.17), [42.18](/rulebook#42.18) |
8858
9100
  | `ABIDE_MAX_STREAM_BUFFER_SIZE` | `number` | The cap on a stream transcript held in memory. | [36.18](/rulebook#36.18) |
8859
9101
  | `ABIDE_MAX_LOG_BUFFER_COUNT` | `number` | How many lines `log.records` is trimmed back to. | [28.1](/rulebook#28.1) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.114.1",
3
+ "version": "0.116.0",
4
4
  "type": "module",
5
5
  "description": "Isomorphic type-safe framework for reactive async interfaces for humans and machines",
6
6
  "license": "MIT",
@@ -57,7 +57,9 @@
57
57
  "sideEffects": [
58
58
  "./src/server/**",
59
59
  "./src/shared/WORK.ts",
60
- "./src/shared/reactive/Reactive.ts"
60
+ "./src/shared/outbox.ts",
61
+ "./src/shared/reactive/Reactive.ts",
62
+ "./src/ui/outboxQueue.ts"
61
63
  ],
62
64
  "dependencies": {
63
65
  "typescript": "^7.0.2"
@@ -28,6 +28,7 @@ export const REACTIVE_MEMBERS: ReadonlySet<string> = new Set([
28
28
  'ready',
29
29
  'refresh',
30
30
  'set',
31
+ 'synced',
31
32
  'watch',
32
33
  // A `Reactive` is thenable (1.4), and `catch`/`finally` derive from `then` (1.5).
33
34
  'then',