@abide/abide 0.118.0 → 0.120.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 (150) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/abide.ts +74 -11
  3. package/docs/REGISTRY.md +5 -4
  4. package/docs/RULEBOOK.md +35 -0
  5. package/docs/abide.md +363 -127
  6. package/package.json +1 -1
  7. package/scaffold/src/getGreeting.test.ts +1 -0
  8. package/scaffold/src/index.abide +4 -1
  9. package/scaffold/src/index.test.ts +2 -0
  10. package/scaffold/src/layout.abide +6 -6
  11. package/src/compiler/Lowering.ts +20 -2
  12. package/src/compiler/PositionTable.ts +1 -0
  13. package/src/compiler/Refusals.ts +1 -0
  14. package/src/compiler/TypeSession.ts +11 -8
  15. package/src/compiler/aliasedSymbol.ts +1 -1
  16. package/src/compiler/ambientReadsIn.ts +1 -0
  17. package/src/compiler/appLayout.ts +4 -2
  18. package/src/compiler/argsDefaultsIn.ts +1 -0
  19. package/src/compiler/argsSchemaOf.ts +1 -0
  20. package/src/compiler/checkApp.ts +3 -1
  21. package/src/compiler/checkedShadow.ts +2 -1
  22. package/src/compiler/classifyRead.ts +1 -0
  23. package/src/compiler/classifyTemplate.ts +1 -0
  24. package/src/compiler/compileApp.ts +4 -2
  25. package/src/compiler/compiledEntry.ts +1 -0
  26. package/src/compiler/componentGraph.ts +1 -0
  27. package/src/compiler/declaredExports.ts +4 -2
  28. package/src/compiler/emitClient.ts +19 -14
  29. package/src/compiler/emitComponent.ts +1 -0
  30. package/src/compiler/emitSource.ts +2 -1
  31. package/src/compiler/formatAbide.ts +857 -0
  32. package/src/compiler/formatDiagnosis.ts +1 -0
  33. package/src/compiler/handlersUnder.ts +2 -1
  34. package/src/compiler/hookCalls.ts +1 -0
  35. package/src/compiler/lineOf.ts +17 -0
  36. package/src/compiler/matchersOf.ts +13 -3
  37. package/src/compiler/mountsOf.ts +2 -0
  38. package/src/compiler/paramFile.ts +1 -0
  39. package/src/compiler/parseAbide.ts +48 -28
  40. package/src/compiler/projectShadow.ts +10 -3
  41. package/src/compiler/reactiveOptions.ts +1 -0
  42. package/src/compiler/reactiveTypes.ts +2 -1
  43. package/src/compiler/readsIn.ts +3 -2
  44. package/src/compiler/renderMarkup.ts +3 -2
  45. package/src/compiler/resolverIn.ts +2 -0
  46. package/src/compiler/sameTemplate.ts +150 -0
  47. package/src/compiler/scaffold.ts +4 -11
  48. package/src/compiler/serveLanguage.ts +46 -19
  49. package/src/compiler/sharedModuleWrites.ts +2 -1
  50. package/src/compiler/sourceMapOf.ts +1 -0
  51. package/src/compiler/splitImports.ts +2 -0
  52. package/src/compiler/templateWarnings.ts +2 -2
  53. package/src/compiler/trackingWarnings.ts +1 -0
  54. package/src/compiler/writeDefaultProject.ts +5 -1
  55. package/src/server/BRIDGE_SCHEME.ts +3 -0
  56. package/src/server/BRIDGE_VERSION.ts +1 -1
  57. package/src/server/abideModules.ts +2 -0
  58. package/src/server/answerFailure.ts +1 -0
  59. package/src/server/answerRequest.ts +15 -14
  60. package/src/server/answerRpc.ts +3 -2
  61. package/src/server/answerSocket.ts +2 -1
  62. package/src/server/answerValue.ts +1 -0
  63. package/src/server/answerWorker.ts +1 -0
  64. package/src/server/appTree.ts +3 -1
  65. package/src/server/argsFor.ts +1 -0
  66. package/src/server/assistant.ts +2 -1
  67. package/src/server/banner.ts +2 -0
  68. package/src/server/bridgeRelay.ts +1 -0
  69. package/src/server/callerOf.ts +1 -0
  70. package/src/server/claudeApi.ts +1 -0
  71. package/src/server/claudeCode.ts +30 -14
  72. package/src/server/clientBundle.ts +1 -0
  73. package/src/server/coerceArgs.ts +1 -2
  74. package/src/server/commands.ts +9 -0
  75. package/src/server/compressAnswer.ts +2 -1
  76. package/src/server/config.ts +2 -0
  77. package/src/server/createApp.ts +4 -2
  78. package/src/server/crossingOrigin.ts +1 -0
  79. package/src/server/csp.ts +1 -0
  80. package/src/server/deferredBuilds.ts +1 -0
  81. package/src/server/derivedArgs.ts +1 -0
  82. package/src/server/devWorker.ts +2 -1
  83. package/src/server/faceOf.ts +2 -7
  84. package/src/server/generatedSurfaces.ts +5 -1
  85. package/src/server/hooks.ts +1 -0
  86. package/src/server/iconAccent.ts +1 -1
  87. package/src/server/installServer.ts +1 -0
  88. package/src/server/installSourceMaps.ts +1 -0
  89. package/src/server/launcherPage.ts +2 -2
  90. package/src/server/manifestOf.ts +1 -0
  91. package/src/server/muxUpgrade.ts +1 -0
  92. package/src/server/nativeMenu.ts +2 -0
  93. package/src/server/principal.ts +2 -1
  94. package/src/server/remote.ts +1 -0
  95. package/src/server/render.ts +3 -3
  96. package/src/server/renderComponent.ts +1 -0
  97. package/src/server/reportHealth.ts +1 -0
  98. package/src/server/requestScope.ts +3 -1
  99. package/src/server/runDesktop.ts +3 -2
  100. package/src/server/seedBuffer.ts +1 -0
  101. package/src/server/serveDev.ts +3 -2
  102. package/src/server/shell.ts +2 -0
  103. package/src/server/streamEvent.ts +1 -0
  104. package/src/server/surfaceOf.ts +1 -0
  105. package/src/server/testBuild.ts +1 -0
  106. package/src/server/testMount.ts +1 -0
  107. package/src/server/trace.ts +2 -0
  108. package/src/server/withAbideSources.ts +1 -0
  109. package/src/shared/Attributes.ts +12 -8
  110. package/src/shared/cookies.ts +1 -1
  111. package/src/shared/escapeText.ts +1 -1
  112. package/src/shared/index.ts +1 -5
  113. package/src/shared/jsonSchema.ts +1 -3
  114. package/src/shared/menuBridge.ts +1 -2
  115. package/src/shared/outbox.ts +1 -1
  116. package/src/shared/reactive/Keyed.ts +2 -2
  117. package/src/shared/reactive/Reactive.ts +12 -16
  118. package/src/shared/reactive/index.ts +3 -4
  119. package/src/shared/reactive/isReactive.ts +1 -1
  120. package/src/shared/reactive/scoped.ts +2 -2
  121. package/src/shared/reactive/share.ts +1 -1
  122. package/src/shared/reactive/watch.ts +4 -4
  123. package/src/shared/refuse.ts +1 -1
  124. package/src/shared/rpc.ts +8 -11
  125. package/src/shared/url.ts +1 -1
  126. package/src/shared/validateJson.ts +1 -1
  127. package/src/ui/ArmRegion.ts +1 -0
  128. package/src/ui/Level.ts +1 -0
  129. package/src/ui/List.ts +1 -0
  130. package/src/ui/RawRun.ts +1 -0
  131. package/src/ui/Router.ts +1 -0
  132. package/src/ui/TryRegion.ts +1 -0
  133. package/src/ui/adoptWalk.ts +2 -1
  134. package/src/ui/bindElement.ts +1 -0
  135. package/src/ui/bindFiles.ts +1 -0
  136. package/src/ui/bindGroup.ts +1 -0
  137. package/src/ui/bindProperty.ts +1 -0
  138. package/src/ui/bindSpread.ts +1 -0
  139. package/src/ui/index.ts +1 -6
  140. package/src/ui/outboxQueue.ts +1 -1
  141. package/src/ui/parseMarkup.ts +2 -1
  142. package/src/ui/reconcile.ts +2 -1
  143. package/src/ui/reconcileHead.ts +1 -0
  144. package/src/ui/regions.ts +2 -1
  145. package/src/ui/registerWorker.ts +1 -0
  146. package/src/ui/rows.ts +1 -0
  147. package/src/ui/setMarkup.ts +1 -0
  148. package/src/ui/setText.ts +1 -0
  149. package/src/ui/socketWire.ts +4 -3
  150. package/src/ui/wire.ts +2 -1
package/docs/abide.md CHANGED
@@ -335,6 +335,7 @@ Any other file beside the pages fails the build, so listing a directory lists it
335
335
  ```abide src/contacts/lib/ContactList.abide
336
336
  <script>
337
337
  import { reactive } from '@abide/abide'
338
+
338
339
  import listContacts from '../listContacts.handler.ts'
339
340
 
340
341
  const contacts = reactive(() => listContacts())
@@ -349,6 +350,7 @@ const contacts = reactive(() => listContacts())
349
350
 
350
351
  ```ts src/contacts/listContacts.handler.ts
351
352
  import { GET } from '@abide/abide/server'
353
+
352
354
  import { CONTACTS } from './lib/CONTACTS.server.ts'
353
355
 
354
356
  export default GET(async () => {
@@ -360,6 +362,7 @@ export default GET(async () => {
360
362
 
361
363
  ```ts src/contacts/lib/ContactList.test.ts
362
364
  import { expect, test } from 'bun:test'
365
+
363
366
  import listContacts from '../listContacts.handler.ts'
364
367
 
365
368
  test('the handler the list reads answers with every contact', async () => {
@@ -383,6 +386,7 @@ The test imports the same handler and calls it, in process. A handler only one p
383
386
  ```abide src/contacts/lib/PageTiming.abide
384
387
  <script>
385
388
  import { reactive } from '@abide/abide'
389
+
386
390
  import listContacts from '../listContacts.handler.ts'
387
391
 
388
392
  let pageComplete = reactive('—')
@@ -411,6 +415,7 @@ async function requestPage() {
411
415
 
412
416
  ```ts src/contacts/lib/PageTiming.test.ts
413
417
  import { expect, test } from 'bun:test'
418
+
414
419
  import { createApp } from '@abide/abide/server'
415
420
 
416
421
  test('the contacts page is complete before the contacts land', async () => {
@@ -450,8 +455,10 @@ let starred = reactive(false)
450
455
 
451
456
  ```ts src/contacts/lib/StarButton.test.ts
452
457
  import { expect, test } from 'bun:test'
458
+
453
459
  import { flushSync } from '@abide/abide'
454
460
  import { mount } from '@abide/harness/abide'
461
+
455
462
  import StarButton from './StarButton.abide'
456
463
 
457
464
  test('the click writes starred and the button follows', async () => {
@@ -473,6 +480,7 @@ Naming `starred` in the markup reads it and follows it from then on. Assigning t
473
480
  ```abide src/contacts/lib/ContactNote.abide
474
481
  <script>
475
482
  import { reactive } from '@abide/abide'
483
+
476
484
  import listContacts from '../listContacts.handler.ts'
477
485
 
478
486
  // Owned, derived, loaded, and waiting for its first write.
@@ -502,8 +510,10 @@ let saved = reactive<string>()
502
510
 
503
511
  ```ts src/contacts/lib/ContactNote.test.ts
504
512
  import { expect, test } from 'bun:test'
513
+
505
514
  import { flushSync } from '@abide/abide'
506
515
  import { mount } from '@abide/harness/abide'
516
+
507
517
  import ContactNote from './ContactNote.abide'
508
518
 
509
519
  const savedNote = () => document.querySelectorAll('strong')[3]?.textContent
@@ -574,8 +584,10 @@ let email = reactive('ada@example.com')
574
584
 
575
585
  ```ts src/profile/lib/ContactFields.test.ts
576
586
  import { expect, test } from 'bun:test'
587
+
577
588
  import { flushSync } from '@abide/abide'
578
589
  import { mount } from '@abide/harness/abide'
590
+
579
591
  import ContactFields from './ContactFields.abide'
580
592
 
581
593
  test('typing writes the value, and the value fills the field', async () => {
@@ -638,8 +650,10 @@ let phone = reactive('', {
638
650
 
639
651
  ```ts src/profile/lib/PhoneField.test.ts
640
652
  import { expect, test } from 'bun:test'
653
+
641
654
  import { flushSync } from '@abide/abide'
642
655
  import { mount } from '@abide/harness/abide'
656
+
643
657
  import PhoneField from './PhoneField.abide'
644
658
 
645
659
  test('the record keeps the digits, and the field shows them back', async () => {
@@ -687,8 +701,10 @@ const filled = reactive(() => [name, phone].filter(Boolean).length)
687
701
 
688
702
  ```ts src/profile/lib/ProfileSummary.test.ts
689
703
  import { expect, test } from 'bun:test'
704
+
690
705
  import { flushSync } from '@abide/abide'
691
706
  import { measure, mount } from '@abide/harness/abide'
707
+
692
708
  import ProfileSummary from './ProfileSummary.abide'
693
709
 
694
710
  const shown = () =>
@@ -747,8 +763,10 @@ let contact = reactive({
747
763
 
748
764
  ```ts src/profile/lib/AddressField.test.ts
749
765
  import { expect, test } from 'bun:test'
766
+
750
767
  import { flushSync } from '@abide/abide'
751
768
  import { mount } from '@abide/harness/abide'
769
+
752
770
  import AddressField from './AddressField.abide'
753
771
 
754
772
  test('a write to the city reaches the record it sits in', async () => {
@@ -809,8 +827,10 @@ let tags = reactive(['investor', 'speaker', 'mentor'])
809
827
 
810
828
  ```ts src/profile/lib/Tags.test.ts
811
829
  import { expect, test } from 'bun:test'
830
+
812
831
  import { flushSync } from '@abide/abide'
813
832
  import { measure, mount } from '@abide/harness/abide'
833
+
814
834
  import Tags from './Tags.abide'
815
835
 
816
836
  test('removing a tag removes its row and rewrites no other', async () => {
@@ -852,8 +872,10 @@ let followUp = reactive(false)
852
872
 
853
873
  ```ts src/profile/lib/FollowUp.test.ts
854
874
  import { expect, test } from 'bun:test'
875
+
855
876
  import { flushSync } from '@abide/abide'
856
877
  import { mount } from '@abide/harness/abide'
878
+
857
879
  import FollowUp from './FollowUp.abide'
858
880
 
859
881
  const dateField = () => document.querySelector('input[type="date"]')
@@ -882,6 +904,7 @@ test('the date field is in the page only while the box is ticked', async () => {
882
904
  ```abide src/profile/lib/ContactCard.abide
883
905
  <script>
884
906
  import { reactive } from '@abide/abide'
907
+
885
908
  import Avatar from './Avatar.abide'
886
909
 
887
910
  let name = reactive('Grace Hopper')
@@ -901,14 +924,19 @@ const { name } = props<{ name: string }>()
901
924
  </script>
902
925
 
903
926
  <span class="rounded-full border border-rule bg-raised px-2 py-1 font-semibold">
904
- {name.split(' ').map((word) => word.charAt(0)).join('')}
927
+ { name
928
+ .split(' ')
929
+ .map((word) => word.charAt(0))
930
+ .join('')}
905
931
  </span>
906
932
  ```
907
933
 
908
934
  ```ts src/profile/lib/ContactCard.test.ts
909
935
  import { expect, test } from 'bun:test'
936
+
910
937
  import { flushSync } from '@abide/abide'
911
938
  import { measure, mount } from '@abide/harness/abide'
939
+
912
940
  import ContactCard from './ContactCard.abide'
913
941
 
914
942
  test('the avatar follows its name prop, and is not rebuilt', async () => {
@@ -976,7 +1004,9 @@ import LeadBadge from './LeadBadge.abide'
976
1004
 
977
1005
  ```ts src/profile/lib/StageBadges.test.ts
978
1006
  import { expect, test } from 'bun:test'
1007
+
979
1008
  import { mount } from '@abide/harness/abide'
1009
+
980
1010
  import StageBadges from './StageBadges.abide'
981
1011
 
982
1012
  // The scope token: an attribute every rule in a `<style>` requires.
@@ -1045,8 +1075,10 @@ let calls = reactive(0)
1045
1075
 
1046
1076
  ```ts src/profile/lib/CallLog.test.ts
1047
1077
  import { expect, test } from 'bun:test'
1078
+
1048
1079
  import { flushSync } from '@abide/abide'
1049
1080
  import { mount } from '@abide/harness/abide'
1081
+
1050
1082
  import CallLog from './CallLog.abide'
1051
1083
 
1052
1084
  const rows = () =>
@@ -1105,6 +1137,7 @@ A handler file exports its handler and nothing else. A page imports it, a script
1105
1137
  ```abide src/deals/lib/DealDetail.abide
1106
1138
  <script>
1107
1139
  import { reactive } from '@abide/abide'
1140
+
1108
1141
  import getDeal from '../getDeal.handler.ts'
1109
1142
 
1110
1143
  let id = reactive('d1')
@@ -1113,10 +1146,12 @@ const deal = reactive(() => getDeal({ id }))
1113
1146
  </script>
1114
1147
 
1115
1148
  <div class="tabs">
1116
- {#for one of ['d1', 'd2']}
1117
- <button aria-selected={id === one}
1118
- onclick={() => { id = one }}>{one}</button>
1119
- {/for}
1149
+ {#for one of ['d1', 'd2']}
1150
+ <button aria-selected={id === one}
1151
+ onclick={() => {
1152
+ id = one
1153
+ }}>{one}</button>
1154
+ {/for}
1120
1155
  </div>
1121
1156
 
1122
1157
  <ul class="fields stacked">
@@ -1128,6 +1163,7 @@ const deal = reactive(() => getDeal({ id }))
1128
1163
  ```ts src/deals/getDeal.handler.ts
1129
1164
  import { refuse } from '@abide/abide'
1130
1165
  import { GET } from '@abide/abide/server'
1166
+
1131
1167
  import { DEALS } from './lib/DEALS.server.ts'
1132
1168
 
1133
1169
  /** Answers at /deals/getDeal?id=…; the argument type is the schema. */
@@ -1143,6 +1179,7 @@ export default GET(
1143
1179
 
1144
1180
  ```ts src/deals/lib/DealDetail.test.ts
1145
1181
  import { expect, test } from 'bun:test'
1182
+
1146
1183
  import getDeal from '../getDeal.handler.ts'
1147
1184
 
1148
1185
  test('the handler answers the deal its args name', async () => {
@@ -1182,14 +1219,15 @@ const notes = reactive(() => getNotes({ id: 'd1' }))
1182
1219
  </script>
1183
1220
 
1184
1221
  <ul class="fields stacked">
1185
- {#for note of notes}
1186
- <li><span>note</span><strong>{note}</strong></li>
1187
- {/for}
1222
+ {#for note of notes}
1223
+ <li><span>note</span><strong>{note}</strong></li>
1224
+ {/for}
1188
1225
  </ul>
1189
1226
  ```
1190
1227
 
1191
1228
  ```ts src/deals/lib/DealNotes.test.ts
1192
1229
  import { expect, test } from 'bun:test'
1230
+
1193
1231
  import { createApp } from '@abide/abide/server'
1194
1232
 
1195
1233
  // The file's path, then the handler's name.
@@ -1226,6 +1264,7 @@ It still answers over http, below `/__abide/handlers` at its file's path and the
1226
1264
  ```abide src/deals/lib/NewDeal.abide
1227
1265
  <script>
1228
1266
  import { type Issues, reactive } from '@abide/abide'
1267
+
1229
1268
  import createDeal from '../createDeal.handler.ts'
1230
1269
 
1231
1270
  type NewDeal = { name: string; company: string; amount: number }
@@ -1255,15 +1294,16 @@ async function add() {
1255
1294
  </div>
1256
1295
 
1257
1296
  <ul class="fields">
1258
- {#for [field, messages] of Object.entries(issues)}
1259
- <li><span>{field}</span><strong>{messages?.[0]}</strong></li>
1260
- {/for}
1297
+ {#for [field, messages] of Object.entries(issues)}
1298
+ <li><span>{field}</span><strong>{messages?.[0]}</strong></li>
1299
+ {/for}
1261
1300
  </ul>
1262
1301
  ```
1263
1302
 
1264
1303
  ```ts src/deals/createDeal.handler.ts
1265
1304
  import { jsonSchema } from '@abide/abide'
1266
1305
  import { POST } from '@abide/abide/server'
1306
+
1267
1307
  import { DEALS } from './lib/DEALS.server.ts'
1268
1308
 
1269
1309
  type NewDeal = { name: string; company: string; amount: number }
@@ -1297,6 +1337,7 @@ export default POST(
1297
1337
 
1298
1338
  ```ts src/deals/lib/NewDeal.test.ts
1299
1339
  import { expect, test } from 'bun:test'
1340
+
1300
1341
  import { createApp } from '@abide/abide/server'
1301
1342
 
1302
1343
  test('a deal that does not fit is refused, field by field', async () => {
@@ -1341,6 +1382,7 @@ Two presses of Add inside one flight send one request and write one deal, and bo
1341
1382
  ```abide src/deals/lib/DealStage.abide
1342
1383
  <script>
1343
1384
  import { reactive } from '@abide/abide'
1385
+
1344
1386
  import getDeal from '../getDeal.handler.ts'
1345
1387
  import moveDeal from '../moveDeal.handler.ts'
1346
1388
 
@@ -1362,10 +1404,13 @@ async function move(stage: string) {
1362
1404
  </script>
1363
1405
 
1364
1406
  <div class="tabs">
1365
- {#for one of ['d2', 'd3']}
1366
- <button aria-selected={id === one}
1367
- onclick={() => { id = one; refusal = '' }}>{one}</button>
1368
- {/for}
1407
+ {#for one of ['d2', 'd3']}
1408
+ <button aria-selected={id === one}
1409
+ onclick={() => {
1410
+ id = one
1411
+ refusal = ''
1412
+ }}>{one}</button>
1413
+ {/for}
1369
1414
  </div>
1370
1415
 
1371
1416
  <ul class="fields">
@@ -1373,9 +1418,9 @@ async function move(stage: string) {
1373
1418
  </ul>
1374
1419
 
1375
1420
  <div class="row">
1376
- {#for stage of ['lead', 'qualified', 'proposal']}
1377
- <button onclick={() => move(stage)}>{stage}</button>
1378
- {/for}
1421
+ {#for stage of ['lead', 'qualified', 'proposal']}
1422
+ <button onclick={() => move(stage)}>{stage}</button>
1423
+ {/for}
1379
1424
  </div>
1380
1425
 
1381
1426
  <p class="status">{refusal}</p>
@@ -1384,6 +1429,7 @@ async function move(stage: string) {
1384
1429
  ```ts src/deals/moveDeal.handler.ts
1385
1430
  import { jsonSchema, refuse } from '@abide/abide'
1386
1431
  import { PATCH } from '@abide/abide/server'
1432
+
1387
1433
  import { DEALS } from './lib/DEALS.server.ts'
1388
1434
 
1389
1435
  // A refusal with a name and typed data, which the page can match.
@@ -1409,6 +1455,7 @@ export default PATCH(
1409
1455
 
1410
1456
  ```ts src/deals/lib/DealStage.test.ts
1411
1457
  import { expect, test } from 'bun:test'
1458
+
1412
1459
  import moveDeal from '../moveDeal.handler.ts'
1413
1460
 
1414
1461
  test('a closed deal is refused by name, with its stage as data', async () => {
@@ -1450,6 +1497,7 @@ import exportDeals from '../exportDeals.handler.ts'
1450
1497
 
1451
1498
  ```ts src/deals/exportDeals.handler.ts
1452
1499
  import { GET } from '@abide/abide/server'
1500
+
1453
1501
  import { DEALS } from './lib/DEALS.server.ts'
1454
1502
 
1455
1503
  /** A Blob answers with its own type: this one is a CSV file. */
@@ -1466,6 +1514,7 @@ export default GET(
1466
1514
 
1467
1515
  ```ts src/deals/lib/ExportDeals.test.ts
1468
1516
  import { expect, test } from 'bun:test'
1517
+
1469
1518
  import exportDeals from '../exportDeals.handler.ts'
1470
1519
 
1471
1520
  test('the export answers a CSV file', async () => {
@@ -1520,6 +1569,7 @@ PATCH /deals/moveDeal deals/moveDeal.handler.ts
1520
1569
  ```abide src/deals/lib/Pipeline.abide
1521
1570
  <script>
1522
1571
  import { reactive, refresh } from '@abide/abide'
1572
+
1523
1573
  import listDeals from '../listDeals.handler.ts'
1524
1574
 
1525
1575
  const deals = reactive(() => listDeals())
@@ -1546,6 +1596,7 @@ const total = reactive(() => {
1546
1596
 
1547
1597
  ```ts src/deals/listDeals.handler.ts
1548
1598
  import { GET } from '@abide/abide/server'
1599
+
1549
1600
  import { DEALS } from './lib/DEALS.server.ts'
1550
1601
 
1551
1602
  export default GET(
@@ -1560,7 +1611,9 @@ export default GET(
1560
1611
 
1561
1612
  ```ts src/deals/lib/Pipeline.test.ts
1562
1613
  import { expect, test } from 'bun:test'
1614
+
1563
1615
  import { reactive, refresh } from '@abide/abide'
1616
+
1564
1617
  import listDeals from '../listDeals.handler.ts'
1565
1618
 
1566
1619
  test('a refresh is busy and still ready; a first load is not', async () => {
@@ -1612,14 +1665,15 @@ import StageValue from './StageValue.abide'
1612
1665
  </script>
1613
1666
 
1614
1667
  <div class="row">
1615
- <StageCount/>
1616
- <StageValue/>
1668
+ <StageCount />
1669
+ <StageValue />
1617
1670
  </div>
1618
1671
  ```
1619
1672
 
1620
1673
  ```abide src/pipeline/lib/StageCount.abide
1621
1674
  <script>
1622
1675
  import { reactive } from '@abide/abide'
1676
+
1623
1677
  import getStage from '../getStage.handler.ts'
1624
1678
  import { STAGES } from './STAGES.ts'
1625
1679
 
@@ -1630,9 +1684,9 @@ const totals = reactive(() => getStage({ stage }))
1630
1684
  <ul class="fields stacked">
1631
1685
  <li><span>stage</span>
1632
1686
  <select bind:value={stage}>
1633
- {#for one of STAGES}
1634
- <option value={one}>{one}</option>
1635
- {/for}
1687
+ {#for one of STAGES}
1688
+ <option value={one}>{one}</option>
1689
+ {/for}
1636
1690
  </select></li>
1637
1691
  <li><span>deals</span>
1638
1692
  <strong>{totals.ready() ? totals.count : '—'}</strong></li>
@@ -1642,6 +1696,7 @@ const totals = reactive(() => getStage({ stage }))
1642
1696
  ```abide src/pipeline/lib/StageValue.abide
1643
1697
  <script>
1644
1698
  import { reactive } from '@abide/abide'
1699
+
1645
1700
  import getStage from '../getStage.handler.ts'
1646
1701
  import { dollars } from './dollars.ts'
1647
1702
  import { STAGES } from './STAGES.ts'
@@ -1654,9 +1709,9 @@ const totals = reactive(() => getStage({ stage }))
1654
1709
  <ul class="fields stacked">
1655
1710
  <li><span>stage</span>
1656
1711
  <select bind:value={stage}>
1657
- {#for one of STAGES}
1658
- <option value={one}>{one}</option>
1659
- {/for}
1712
+ {#for one of STAGES}
1713
+ <option value={one}>{one}</option>
1714
+ {/for}
1660
1715
  </select></li>
1661
1716
  <li><span>value</span>
1662
1717
  <strong>{totals.ready() ? dollars(totals.value) : '—'}</strong></li>
@@ -1665,6 +1720,7 @@ const totals = reactive(() => getStage({ stage }))
1665
1720
 
1666
1721
  ```ts src/pipeline/getStage.handler.ts
1667
1722
  import { GET } from '@abide/abide/server'
1723
+
1668
1724
  import { DEALS } from './lib/DEALS.server.ts'
1669
1725
  import { LOADS } from './lib/LOADS.server.ts'
1670
1726
 
@@ -1689,10 +1745,12 @@ export default GET(
1689
1745
 
1690
1746
  ```ts src/pipeline/lib/StageTiles.test.ts
1691
1747
  import { expect, test } from 'bun:test'
1748
+
1692
1749
  import { mount } from '@abide/harness/abide'
1750
+
1693
1751
  import getStage from '../getStage.handler.ts'
1694
- import StageTiles from './StageTiles.abide'
1695
1752
  import { LOADS } from './LOADS.server.ts'
1753
+ import StageTiles from './StageTiles.abide'
1696
1754
 
1697
1755
  function pick(index: number, stage: string): void {
1698
1756
  const select = document.querySelectorAll('select')[index]
@@ -1732,6 +1790,7 @@ A body with a parameter is a keyed value: one entry per set of arguments, held.
1732
1790
  ```abide src/pipeline/lib/Forecast.abide
1733
1791
  <script>
1734
1792
  import { reactive } from '@abide/abide'
1793
+
1735
1794
  import getForecast from '../getForecast.handler.ts'
1736
1795
  import { dollars } from './dollars.ts'
1737
1796
 
@@ -1756,6 +1815,7 @@ const reloading = reactive(() => forecast.busy() && forecast.ready())
1756
1815
 
1757
1816
  ```ts src/pipeline/getForecast.handler.ts
1758
1817
  import { GET } from '@abide/abide/server'
1818
+
1759
1819
  import { DEALS } from './lib/DEALS.server.ts'
1760
1820
  import { LOADS } from './lib/LOADS.server.ts'
1761
1821
 
@@ -1784,8 +1844,10 @@ export default GET(
1784
1844
 
1785
1845
  ```ts src/pipeline/lib/Forecast.test.ts
1786
1846
  import { expect, test } from 'bun:test'
1847
+
1787
1848
  import { flushSync } from '@abide/abide'
1788
1849
  import { mount } from '@abide/harness/abide'
1850
+
1789
1851
  import Forecast from './Forecast.abide'
1790
1852
  import { LOADS } from './LOADS.server.ts'
1791
1853
 
@@ -1838,8 +1900,8 @@ Press Mark stale and then Reload now, and the forecast blanks between the two. T
1838
1900
  <script>
1839
1901
  import getStage from '../getStage.handler.ts'
1840
1902
  import winDeal from '../winDeal.handler.ts'
1841
- import StageTile from './StageTile.abide'
1842
1903
  import { STAGES } from './STAGES.ts'
1904
+ import StageTile from './StageTile.abide'
1843
1905
 
1844
1906
  async function win() {
1845
1907
  await winDeal()
@@ -1850,9 +1912,9 @@ async function win() {
1850
1912
  </script>
1851
1913
 
1852
1914
  <ul class="tiles">
1853
- {#for stage of STAGES}
1854
- <StageTile stage={stage}/>
1855
- {/for}
1915
+ {#for stage of STAGES}
1916
+ <StageTile stage={stage} />
1917
+ {/for}
1856
1918
  </ul>
1857
1919
 
1858
1920
  <div class="row">
@@ -1863,6 +1925,7 @@ async function win() {
1863
1925
  ```abide src/pipeline/lib/StageTile.abide
1864
1926
  <script>
1865
1927
  import { props, reactive } from '@abide/abide'
1928
+
1866
1929
  import getStage from '../getStage.handler.ts'
1867
1930
 
1868
1931
  const { stage } = props<{ stage: string }>()
@@ -1877,6 +1940,7 @@ const totals = reactive(() => getStage({ stage }))
1877
1940
 
1878
1941
  ```ts src/pipeline/winDeal.handler.ts
1879
1942
  import { POST } from '@abide/abide/server'
1943
+
1880
1944
  import { DEALS } from './lib/DEALS.server.ts'
1881
1945
 
1882
1946
  /** Moves the first open lead to won, and answers with its id. */
@@ -1892,10 +1956,12 @@ export default POST(
1892
1956
 
1893
1957
  ```ts src/pipeline/lib/StageBoard.test.ts
1894
1958
  import { expect, test } from 'bun:test'
1959
+
1895
1960
  import { mount } from '@abide/harness/abide'
1896
- import { STAGES } from './STAGES.ts'
1897
- import StageBoard from './StageBoard.abide'
1961
+
1898
1962
  import { LOADS } from './LOADS.server.ts'
1963
+ import StageBoard from './StageBoard.abide'
1964
+ import { STAGES } from './STAGES.ts'
1899
1965
 
1900
1966
  function counted(): number[] {
1901
1967
  return STAGES.map((stage) => LOADS[stage] ?? 0)
@@ -1934,6 +2000,7 @@ The triggers also come as free functions, and those take a `Selection`: the set
1934
2000
  ```abide src/pipeline/lib/DealSearch.abide
1935
2001
  <script>
1936
2002
  import { reactive } from '@abide/abide'
2003
+
1937
2004
  import searchDeals from '../searchDeals.handler.ts'
1938
2005
 
1939
2006
  let q = reactive('')
@@ -1945,14 +2012,15 @@ const found = reactive(() => searchDeals({ q }), { debounce: 300 })
1945
2012
  </label>
1946
2013
 
1947
2014
  <ul class="fields">
1948
- {#for name of found.ready() ? found : []}
1949
- <li><strong>{name}</strong></li>
1950
- {/for}
2015
+ {#for name of found.ready() ? found : []}
2016
+ <li><strong>{name}</strong></li>
2017
+ {/for}
1951
2018
  </ul>
1952
2019
  ```
1953
2020
 
1954
2021
  ```ts src/pipeline/searchDeals.handler.ts
1955
2022
  import { GET } from '@abide/abide/server'
2023
+
1956
2024
  import { DEALS } from './lib/DEALS.server.ts'
1957
2025
  import { LOADS } from './lib/LOADS.server.ts'
1958
2026
 
@@ -1974,7 +2042,9 @@ export default GET(
1974
2042
 
1975
2043
  ```ts src/pipeline/lib/DealSearch.test.ts
1976
2044
  import { expect, test } from 'bun:test'
2045
+
1977
2046
  import { mount } from '@abide/harness/abide'
2047
+
1978
2048
  import DealSearch from './DealSearch.abide'
1979
2049
  import { LOADS } from './LOADS.server.ts'
1980
2050
 
@@ -2020,6 +2090,7 @@ Every keystroke is a new `q`, and so a new key. `debounce: 300` holds the search
2020
2090
  ```abide src/pipeline/lib/DealFeed.abide
2021
2091
  <script>
2022
2092
  import { reactive } from '@abide/abide'
2093
+
2023
2094
  import listDeals from '../listDeals.handler.ts'
2024
2095
  import { dollars } from './dollars.ts'
2025
2096
 
@@ -2044,10 +2115,10 @@ function applyNextUpdate() {
2044
2115
  </script>
2045
2116
 
2046
2117
  <ul class="fields">
2047
- {#for deal of deals.ready() ? deals : [] by deal.id}
2048
- <li><span>{deal.stage}</span><strong>{dollars(deal.amount)}</strong>
2049
- {deal.name}</li>
2050
- {/for}
2118
+ {#for deal of deals.ready() ? deals : [] by deal.id}
2119
+ <li><span>{deal.stage}</span><strong>{dollars(deal.amount)}</strong>
2120
+ {deal.name}</li>
2121
+ {/for}
2051
2122
  </ul>
2052
2123
 
2053
2124
  <div class="row">
@@ -2057,6 +2128,7 @@ function applyNextUpdate() {
2057
2128
 
2058
2129
  ```ts src/pipeline/listDeals.handler.ts
2059
2130
  import { GET } from '@abide/abide/server'
2131
+
2060
2132
  import { DEALS } from './lib/DEALS.server.ts'
2061
2133
 
2062
2134
  /** Every open deal, as a list. */
@@ -2072,8 +2144,10 @@ export default GET(
2072
2144
 
2073
2145
  ```ts src/pipeline/lib/DealFeed.test.ts
2074
2146
  import { expect, test } from 'bun:test'
2147
+
2075
2148
  import { flushSync } from '@abide/abide'
2076
2149
  import { measure, mount } from '@abide/harness/abide'
2150
+
2077
2151
  import DealFeed from './DealFeed.abide'
2078
2152
 
2079
2153
  test('an update rewrites one amount and builds nothing', async () => {
@@ -2109,6 +2183,7 @@ Change the held value without `patch` and nothing on the page moves, because no
2109
2183
  ```abide src/pipeline/lib/DealPicker.abide
2110
2184
  <script>
2111
2185
  import { reactive } from '@abide/abide'
2186
+
2112
2187
  import listDeals from '../listDeals.handler.ts'
2113
2188
  import DealDetail from './DealDetail.abide'
2114
2189
  import DealRow from './DealRow.abide'
@@ -2119,12 +2194,12 @@ const deals = reactive(() => listDeals())
2119
2194
  reactive.share('selectedDeal', () => reactive('d1'))
2120
2195
  </script>
2121
2196
 
2122
- <DealDetail/>
2197
+ <DealDetail />
2123
2198
 
2124
2199
  <div class="row">
2125
- {#for deal of deals.ready() ? deals : [] by deal.id}
2126
- <DealRow id={deal.id} name={deal.name}/>
2127
- {/for}
2200
+ {#for deal of deals.ready() ? deals : [] by deal.id}
2201
+ <DealRow id={deal.id} name={deal.name} />
2202
+ {/for}
2128
2203
  </div>
2129
2204
  ```
2130
2205
 
@@ -2145,6 +2220,7 @@ let selected = reactive.share('selectedDeal', () => reactive(''))
2145
2220
  ```abide src/pipeline/lib/DealDetail.abide
2146
2221
  <script>
2147
2222
  import { reactive } from '@abide/abide'
2223
+
2148
2224
  import listDeals from '../listDeals.handler.ts'
2149
2225
 
2150
2226
  const deals = reactive(() => listDeals())
@@ -2162,8 +2238,10 @@ const deal = reactive(() =>
2162
2238
 
2163
2239
  ```ts src/pipeline/lib/DealPicker.test.ts
2164
2240
  import { expect, test } from 'bun:test'
2241
+
2165
2242
  import { flushSync } from '@abide/abide'
2166
2243
  import { mount } from '@abide/harness/abide'
2244
+
2167
2245
  import DealPicker from './DealPicker.abide'
2168
2246
 
2169
2247
  test('picking a deal in the list shows it in the detail panel', async () => {
@@ -2193,6 +2271,7 @@ Use the value `reactive.share` returns, and no other name. A value lives in the
2193
2271
  ```abide src/pipeline/lib/DealNote.abide
2194
2272
  <script>
2195
2273
  import { reactive, watch } from '@abide/abide'
2274
+
2196
2275
  import saveNote from '../saveNote.handler.ts'
2197
2276
 
2198
2277
  let note = reactive('Wants a quote by Friday.')
@@ -2222,6 +2301,7 @@ watch(() => {
2222
2301
 
2223
2302
  ```ts src/pipeline/saveNote.handler.ts
2224
2303
  import { POST } from '@abide/abide/server'
2304
+
2225
2305
  import { LOADS } from './lib/LOADS.server.ts'
2226
2306
  import { NOTES } from './lib/NOTES.server.ts'
2227
2307
 
@@ -2237,7 +2317,9 @@ export default POST(
2237
2317
 
2238
2318
  ```ts src/pipeline/lib/DealNote.test.ts
2239
2319
  import { expect, test } from 'bun:test'
2320
+
2240
2321
  import { mount } from '@abide/harness/abide'
2322
+
2241
2323
  import DealNote from './DealNote.abide'
2242
2324
  import { LOADS } from './LOADS.server.ts'
2243
2325
  import { NOTES } from './NOTES.server.ts'
@@ -2287,9 +2369,10 @@ Where a read subscribes depends on where it runs:
2287
2369
  ```abide src/pipeline/lib/PipelineView.abide
2288
2370
  <script>
2289
2371
  import { reactive } from '@abide/abide'
2372
+
2290
2373
  import { preferenceStore } from './preferenceStore.ts'
2291
- import StageTile from './StageTile.abide'
2292
2374
  import { STAGES } from './STAGES.ts'
2375
+ import StageTile from './StageTile.abide'
2293
2376
 
2294
2377
  const VIEWS = ['board', 'list']
2295
2378
 
@@ -2298,21 +2381,22 @@ let view = reactive('board', { store: preferenceStore('view') })
2298
2381
  </script>
2299
2382
 
2300
2383
  <div class="tabs">
2301
- {#for one of VIEWS}
2302
- <button aria-selected={view === one} onclick={() => (view = one)}>
2303
- {one}</button>
2304
- {/for}
2384
+ {#for one of VIEWS}
2385
+ <button aria-selected={view === one} onclick={() => (view = one)}>
2386
+ {one}</button>
2387
+ {/for}
2305
2388
  </div>
2306
2389
 
2307
2390
  <ul class={view === 'board' ? 'tiles' : 'fields'}>
2308
- {#for stage of STAGES}
2309
- <StageTile stage={stage}/>
2310
- {/for}
2391
+ {#for stage of STAGES}
2392
+ <StageTile stage={stage} />
2393
+ {/for}
2311
2394
  </ul>
2312
2395
  ```
2313
2396
 
2314
2397
  ```ts src/pipeline/lib/preferenceStore.ts
2315
2398
  import type { Store } from '@abide/abide'
2399
+
2316
2400
  import getPreference from '../getPreference.handler.ts'
2317
2401
  import setPreference from '../setPreference.handler.ts'
2318
2402
 
@@ -2332,6 +2416,7 @@ export function preferenceStore(name: string): Store<string> {
2332
2416
 
2333
2417
  ```ts src/pipeline/getPreference.handler.ts
2334
2418
  import { GET } from '@abide/abide/server'
2419
+
2335
2420
  import { PREFERENCES } from './lib/PREFERENCES.server.ts'
2336
2421
 
2337
2422
  /** A saved preference, or `null` where none was saved. */
@@ -2347,6 +2432,7 @@ export default GET(
2347
2432
 
2348
2433
  ```ts src/pipeline/setPreference.handler.ts
2349
2434
  import { POST } from '@abide/abide/server'
2435
+
2350
2436
  import { PREFERENCES } from './lib/PREFERENCES.server.ts'
2351
2437
 
2352
2438
  /** Saves a preference. */
@@ -2360,8 +2446,10 @@ export default POST(
2360
2446
 
2361
2447
  ```ts src/pipeline/lib/PipelineView.test.ts
2362
2448
  import { expect, test } from 'bun:test'
2449
+
2363
2450
  import { flushSync } from '@abide/abide'
2364
2451
  import { mount } from '@abide/harness/abide'
2452
+
2365
2453
  import PipelineView from './PipelineView.abide'
2366
2454
  import { PREFERENCES } from './PREFERENCES.server.ts'
2367
2455
 
@@ -2454,6 +2542,7 @@ import LastActivity from './LastActivity.abide'
2454
2542
  ```abide src/activity/lib/LastActivity.abide
2455
2543
  <script>
2456
2544
  import { reactive } from '@abide/abide'
2545
+
2457
2546
  // The same import reaches the same channel: no prop, no provider.
2458
2547
  import { activity } from './activity.ts'
2459
2548
 
@@ -2471,8 +2560,10 @@ const last = reactive(() => activity.at(-1))
2471
2560
 
2472
2561
  ```ts src/activity/lib/QuickLog.test.ts
2473
2562
  import { expect, test } from 'bun:test'
2563
+
2474
2564
  import { flushSync } from '@abide/abide'
2475
2565
  import { mount } from '@abide/harness/abide'
2566
+
2476
2567
  import QuickLog from './QuickLog.abide'
2477
2568
 
2478
2569
  test('a reader the logger never names follows what it logs', async () => {
@@ -2537,8 +2628,10 @@ export function logActivity(contact: string, kind: string): void {
2537
2628
 
2538
2629
  ```ts src/activity/lib/RecentActivity.test.ts
2539
2630
  import { expect, test } from 'bun:test'
2631
+
2540
2632
  import { flushSync } from '@abide/abide'
2541
2633
  import { mount } from '@abide/harness/abide'
2634
+
2542
2635
  import RecentActivity from './RecentActivity.abide'
2543
2636
 
2544
2637
  test('logging past three adds one entry and drops the oldest', async () => {
@@ -2556,7 +2649,7 @@ test('logging past three adds one entry and drops the oldest', async () => {
2556
2649
  ```
2557
2650
 
2558
2651
  The trim is one line in the patch that appends: `if (entries.length > 3)
2559
- entries.shift()`. `patch` hands you the array the channel holds and changes it in place, so the push and the trim are one write. abide puts no cap on the array. The condition is yours: a count here, an age or a byte total where that is what bounds it. Keep the push and the trim in one function every writer calls, and the cap belongs to the channel rather than to whichever caller remembered it.
2652
+ entries.shift()`. `patch` hands you the array the channel holds and changes it in place, so the push and the trim are one write. abide puts no cap on the array. The condition is yours: a count here, an age or a byte total where that is what bounds it. Keep the push and the trim in one function every writer calls, and the cap belongs to the channel rather than to whichever caller remembered it.
2560
2653
 
2561
2654
  A value keeps no past of its own. Where you want what came before, make the value the array and append where you write, as here. An undo stack is the same: an array the app pushes into.
2562
2655
 
@@ -2567,6 +2660,7 @@ A value keeps no past of its own. Where you want what came before, make the valu
2567
2660
  ```abide src/activity/lib/AccountFeed.abide
2568
2661
  <script>
2569
2662
  import { reactive } from '@abide/abide'
2663
+
2570
2664
  import feed from '../feed.handler.ts'
2571
2665
  import logToFeed from '../logToFeed.handler.ts'
2572
2666
 
@@ -2602,6 +2696,7 @@ export const feeds = reactive((_account: { account: string }): Entry[] => [], {
2602
2696
 
2603
2697
  ```ts src/activity/feed.handler.ts
2604
2698
  import { GET } from '@abide/abide/server'
2699
+
2605
2700
  import { feeds } from './lib/feeds.ts'
2606
2701
 
2607
2702
  /** Answers at /activity/feed; every tab reading it follows it. */
@@ -2610,6 +2705,7 @@ export default GET(feeds, { description: "An account's activity feed." })
2610
2705
 
2611
2706
  ```ts src/activity/logToFeed.handler.ts
2612
2707
  import { POST } from '@abide/abide/server'
2708
+
2613
2709
  import { feeds } from './lib/feeds.ts'
2614
2710
 
2615
2711
  const KINDS = ['Call', 'Email', 'Meeting']
@@ -2633,6 +2729,7 @@ export default POST(
2633
2729
 
2634
2730
  ```ts src/activity/lib/AccountFeed.test.ts
2635
2731
  import { expect, test } from 'bun:test'
2732
+
2636
2733
  import { createApp } from '@abide/abide/server'
2637
2734
 
2638
2735
  // Each request is a caller of its own, as each open tab is.
@@ -2674,6 +2771,7 @@ The feed lives in memory. A restart starts it empty, and each instance of the ap
2674
2771
  ```abide src/activity/lib/ActivityHistory.abide
2675
2772
  <script>
2676
2773
  import { reactive, refresh } from '@abide/abide'
2774
+
2677
2775
  import olderActivity from '../olderActivity.handler.ts'
2678
2776
 
2679
2777
  const entries = reactive(() => olderActivity())
@@ -2715,6 +2813,7 @@ export default GET(
2715
2813
 
2716
2814
  ```ts src/activity/lib/ActivityHistory.test.ts
2717
2815
  import { expect, test } from 'bun:test'
2816
+
2718
2817
  import olderActivity from '../olderActivity.handler.ts'
2719
2818
 
2720
2819
  test('the first entry is in hand while the rest are still read', async () => {
@@ -2746,6 +2845,7 @@ A page rendered on the server is not held for an unfinished stream. What the str
2746
2845
  ```abide src/activity/lib/CsvExport.abide
2747
2846
  <script>
2748
2847
  import { reactive } from '@abide/abide'
2848
+
2749
2849
  import exportCsv from '../exportCsv.handler.ts'
2750
2850
 
2751
2851
  const csv = reactive(async () => (await exportCsv.raw()).text())
@@ -2787,6 +2887,7 @@ export default GET(() => bytes(csvOf()), {
2787
2887
 
2788
2888
  ```ts src/activity/lib/CsvExport.test.ts
2789
2889
  import { expect, test } from 'bun:test'
2890
+
2790
2891
  import { createApp } from '@abide/abide/server'
2791
2892
 
2792
2893
  test('the export is a file, written a row at a time', async () => {
@@ -2819,6 +2920,7 @@ A stream whose first chunk is binary is sent as bytes without the helper. `bytes
2819
2920
  ```abide src/activity/lib/Wallboard.abide
2820
2921
  <script>
2821
2922
  import { reactive } from '@abide/abide'
2923
+
2822
2924
  import ticker from '../ticker.handler.ts'
2823
2925
 
2824
2926
  // A plain request, as a wallboard sends it: no `Accept` asks for events.
@@ -2852,6 +2954,7 @@ export default GET(() => sse(MOVES), {
2852
2954
 
2853
2955
  ```ts src/activity/lib/Wallboard.test.ts
2854
2956
  import { expect, test } from 'bun:test'
2957
+
2855
2958
  import { createApp } from '@abide/abide/server'
2856
2959
 
2857
2960
  test('a caller asking for nothing in particular is sent events', async () => {
@@ -2954,6 +3057,7 @@ const address = reactive(() => url('/companies/[id=SLUG]', { id: open }))
2954
3057
  ```abide src/companies/[id=SLUG].abide
2955
3058
  <script>
2956
3059
  import { reactive, route } from '@abide/abide'
3060
+
2957
3061
  import getCompany from './getCompany.handler.ts'
2958
3062
  import CompanyTabs from './lib/CompanyTabs.abide'
2959
3063
 
@@ -2977,6 +3081,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
2977
3081
 
2978
3082
  ```ts src/companies/CompanyPage.test.ts
2979
3083
  import { expect, test } from 'bun:test'
3084
+
2980
3085
  import { createApp } from '@abide/abide/server'
2981
3086
 
2982
3087
  test('one file answers for every company, reading its id', async () => {
@@ -3033,6 +3138,7 @@ export const SLUG = /[a-z0-9-]+/
3033
3138
  ```abide src/companies/[id=SLUG].abide
3034
3139
  <script>
3035
3140
  import { reactive, route } from '@abide/abide'
3141
+
3036
3142
  import getCompany from './getCompany.handler.ts'
3037
3143
  import CompanyTabs from './lib/CompanyTabs.abide'
3038
3144
 
@@ -3056,7 +3162,9 @@ const company = reactive(() => getCompany({ id: route.params.id }))
3056
3162
 
3057
3163
  ```ts src/companies/Matcher.test.ts
3058
3164
  import { expect, test } from 'bun:test'
3165
+
3059
3166
  import { createApp } from '@abide/abide/server'
3167
+
3060
3168
  import { COMPANIES } from './lib/COMPANIES.server.ts'
3061
3169
 
3062
3170
  test('the company page takes every id the CRM has, and no name', async () => {
@@ -3103,6 +3211,7 @@ import { url } from '@abide/abide'
3103
3211
  ```abide src/companies/layout.abide
3104
3212
  <script server>
3105
3213
  import { onError } from '@abide/abide/server'
3214
+
3106
3215
  import { tracker } from './lib/tracker.server.ts'
3107
3216
 
3108
3217
  onError((error) => tracker.capture(error))
@@ -3121,11 +3230,12 @@ let note = reactive('')
3121
3230
  <input bind:value={note} placeholder="Note for the call">
3122
3231
  </header>
3123
3232
 
3124
- <slot/>
3233
+ <slot />
3125
3234
  ```
3126
3235
 
3127
3236
  ```ts src/companies/Layout.test.ts
3128
3237
  import { expect, test } from 'bun:test'
3238
+
3129
3239
  import { createApp } from '@abide/abide/server'
3130
3240
 
3131
3241
  test('the list and a company render inside one layout', async () => {
@@ -3194,6 +3304,7 @@ let draft = reactive('')
3194
3304
 
3195
3305
  ```ts src/companies/lib/CompanyTabs.test.ts
3196
3306
  import { expect, test } from 'bun:test'
3307
+
3197
3308
  import { createApp } from '@abide/abide/server'
3198
3309
 
3199
3310
  test('the open tab is read from the address', async () => {
@@ -3225,6 +3336,7 @@ import { url } from '@abide/abide'
3225
3336
  ```abide src/companies/index.abide
3226
3337
  <script>
3227
3338
  import { reactive, url } from '@abide/abide'
3339
+
3228
3340
  import listCompanies from './listCompanies.handler.ts'
3229
3341
 
3230
3342
  const companies = reactive(() => listCompanies())
@@ -3247,6 +3359,7 @@ const companies = reactive(() => listCompanies())
3247
3359
  ```abide src/companies/[id=SLUG].abide
3248
3360
  <script>
3249
3361
  import { reactive, route } from '@abide/abide'
3362
+
3250
3363
  import getCompany from './getCompany.handler.ts'
3251
3364
  import CompanyTabs from './lib/CompanyTabs.abide'
3252
3365
 
@@ -3270,6 +3383,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
3270
3383
 
3271
3384
  ```ts src/companies/ViewTransition.test.ts
3272
3385
  import { expect, test } from 'bun:test'
3386
+
3273
3387
  import { createApp } from '@abide/abide/server'
3274
3388
 
3275
3389
  test("the list row and the company's heading share one name", async () => {
@@ -3342,6 +3456,7 @@ function followTitle(element: Element) {
3342
3456
  ```abide src/companies/layout.abide
3343
3457
  <script server>
3344
3458
  import { onError } from '@abide/abide/server'
3459
+
3345
3460
  import { tracker } from './lib/tracker.server.ts'
3346
3461
 
3347
3462
  onError((error) => tracker.capture(error))
@@ -3360,12 +3475,13 @@ let note = reactive('')
3360
3475
  <input bind:value={note} placeholder="Note for the call">
3361
3476
  </header>
3362
3477
 
3363
- <slot/>
3478
+ <slot />
3364
3479
  ```
3365
3480
 
3366
3481
  ```abide src/companies/[id=SLUG].abide
3367
3482
  <script>
3368
3483
  import { reactive, route } from '@abide/abide'
3484
+
3369
3485
  import getCompany from './getCompany.handler.ts'
3370
3486
  import CompanyTabs from './lib/CompanyTabs.abide'
3371
3487
 
@@ -3389,6 +3505,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
3389
3505
 
3390
3506
  ```ts src/companies/Head.test.ts
3391
3507
  import { expect, test } from 'bun:test'
3508
+
3392
3509
  import { createApp } from '@abide/abide/server'
3393
3510
 
3394
3511
  const titlesOf = (html: string) => html.match(/<title>[^<]*<\/title>/g)
@@ -3432,6 +3549,7 @@ let loads = reactive(1)
3432
3549
  ```abide src/companies/[id=SLUG].abide
3433
3550
  <script>
3434
3551
  import { reactive, route } from '@abide/abide'
3552
+
3435
3553
  import getCompany from './getCompany.handler.ts'
3436
3554
  import CompanyTabs from './lib/CompanyTabs.abide'
3437
3555
 
@@ -3455,6 +3573,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
3455
3573
 
3456
3574
  ```ts src/companies/getCompany.handler.ts
3457
3575
  import { GET, notFound } from '@abide/abide/server'
3576
+
3458
3577
  import { COMPANIES } from './lib/COMPANIES.server.ts'
3459
3578
 
3460
3579
  /** Answers at /companies/getCompany; a company's page reads it. */
@@ -3473,6 +3592,7 @@ export default GET(
3473
3592
 
3474
3593
  ```ts src/companies/Streaming.test.ts
3475
3594
  import { expect, test } from 'bun:test'
3595
+
3476
3596
  import { createApp } from '@abide/abide/server'
3477
3597
 
3478
3598
  test('the page goes out without the company, and asks for it', async () => {
@@ -3530,6 +3650,7 @@ const { failure } = props<{ failure: Failed<string, unknown> }>()
3530
3650
 
3531
3651
  ```ts src/companies/getCompany.handler.ts
3532
3652
  import { GET, notFound } from '@abide/abide/server'
3653
+
3533
3654
  import { COMPANIES } from './lib/COMPANIES.server.ts'
3534
3655
 
3535
3656
  /** Answers at /companies/getCompany; a company's page reads it. */
@@ -3548,6 +3669,7 @@ export default GET(
3548
3669
 
3549
3670
  ```ts src/companies/ErrorPage.test.ts
3550
3671
  import { expect, test } from 'bun:test'
3672
+
3551
3673
  import { createApp } from '@abide/abide/server'
3552
3674
 
3553
3675
  test('a missing company is a 404, painted by error.abide', async () => {
@@ -3571,6 +3693,7 @@ The refusal has to come in the page's first turn to set the status. That is why
3571
3693
  ```abide src/companies/lib/ExportButton.abide
3572
3694
  <script>
3573
3695
  import { isFailed, reactive, refresh } from '@abide/abide'
3696
+
3574
3697
  import exportCompanies from '../exportCompanies.handler.ts'
3575
3698
  import reportedErrors from '../reportedErrors.handler.ts'
3576
3699
 
@@ -3613,6 +3736,7 @@ export default POST(
3613
3736
  ```abide src/companies/layout.abide
3614
3737
  <script server>
3615
3738
  import { onError } from '@abide/abide/server'
3739
+
3616
3740
  import { tracker } from './lib/tracker.server.ts'
3617
3741
 
3618
3742
  onError((error) => tracker.capture(error))
@@ -3631,11 +3755,12 @@ let note = reactive('')
3631
3755
  <input bind:value={note} placeholder="Note for the call">
3632
3756
  </header>
3633
3757
 
3634
- <slot/>
3758
+ <slot />
3635
3759
  ```
3636
3760
 
3637
3761
  ```ts src/companies/lib/ExportButton.test.ts
3638
3762
  import { expect, test } from 'bun:test'
3763
+
3639
3764
  import { createApp } from '@abide/abide/server'
3640
3765
 
3641
3766
  test('the caller gets a bare 500, and onError gets the reason', async () => {
@@ -3674,8 +3799,9 @@ import previewCompany from '../previewCompany.handler.ts'
3674
3799
 
3675
3800
  ```ts src/companies/previewCompany.handler.ts
3676
3801
  import { GET, notFound, page, render } from '@abide/abide/server'
3677
- import CompanySheet from './lib/CompanySheet.abide'
3802
+
3678
3803
  import { COMPANIES } from './lib/COMPANIES.server.ts'
3804
+ import CompanySheet from './lib/CompanySheet.abide'
3679
3805
 
3680
3806
  /** Answers at /companies/previewCompany: an account sheet, as its own page. */
3681
3807
  export default GET(
@@ -3698,19 +3824,20 @@ const { company } = props<{
3698
3824
  </script>
3699
3825
 
3700
3826
  <html lang="en">
3701
- <body style="margin: 0; padding: 1.25rem; background: #fff; color: #1d2a28;
3827
+ <body style="margin: 0; padding: 1.25rem; background: #fff; color: #1d2a28;
3702
3828
  font: 14px/1.5 system-ui, sans-serif">
3703
- <h1 style="margin: 0 0 0.5rem; font-size: 1.25rem">{company.name}</h1>
3829
+ <h1 style="margin: 0 0 0.5rem; font-size: 1.25rem">{company.name}</h1>
3704
3830
  <table>
3705
- <tr><th align="left">Contact</th><td>{company.contact}</td></tr>
3706
- <tr><th align="left">Pipeline</th><td>{company.pipeline}</td></tr>
3707
- </table>
3708
- </body>
3831
+ <tr><th align="left">Contact</th><td>{company.contact}</td></tr>
3832
+ <tr><th align="left">Pipeline</th><td>{company.pipeline}</td></tr>
3833
+ </table>
3834
+ </body>
3709
3835
  </html>
3710
3836
  ```
3711
3837
 
3712
3838
  ```ts src/companies/lib/SheetPreview.test.ts
3713
3839
  import { expect, test } from 'bun:test'
3840
+
3714
3841
  import { createApp } from '@abide/abide/server'
3715
3842
 
3716
3843
  test('the sheet is a document of its own', async () => {
@@ -3765,6 +3892,7 @@ Everything in this chapter is in `src/account` of the chapter app, beside `src/c
3765
3892
  ```abide src/account/lib/SignIn.abide
3766
3893
  <script>
3767
3894
  import { principal, reactive } from '@abide/abide'
3895
+
3768
3896
  import signIn from '../signIn.handler.ts'
3769
3897
  import signOut from '../signOut.handler.ts'
3770
3898
 
@@ -3785,6 +3913,7 @@ let email = reactive('maya@crm.example')
3785
3913
  ```ts src/account/signIn.handler.ts
3786
3914
  import { principal, refuse } from '@abide/abide'
3787
3915
  import { onPrincipal, POST } from '@abide/abide/server'
3916
+
3788
3917
  import { TEAM } from './lib/TEAM.server.ts'
3789
3918
 
3790
3919
  /** Seals the member's id into the cookie, and nothing else. */
@@ -3814,6 +3943,7 @@ export default POST(() => principal.clear(), { description: 'Signs out.' })
3814
3943
 
3815
3944
  ```ts src/account/lib/SignIn.test.ts
3816
3945
  import { expect, test } from 'bun:test'
3946
+
3817
3947
  import { principal } from '@abide/abide'
3818
3948
  import { createApp } from '@abide/abide/server'
3819
3949
 
@@ -3876,6 +4006,7 @@ Each is a reactive value. A template reading one follows it, and on the server e
3876
4006
  ```abide src/account/lib/Badge.abide
3877
4007
  <script>
3878
4008
  import { principal } from '@abide/abide'
4009
+
3879
4010
  import SignInAs from './SignInAs.abide'
3880
4011
  </script>
3881
4012
 
@@ -3892,6 +4023,7 @@ import SignInAs from './SignInAs.abide'
3892
4023
  ```ts src/account/signIn.handler.ts
3893
4024
  import { principal, refuse } from '@abide/abide'
3894
4025
  import { onPrincipal, POST } from '@abide/abide/server'
4026
+
3895
4027
  import { TEAM } from './lib/TEAM.server.ts'
3896
4028
 
3897
4029
  /** Seals the member's id into the cookie, and nothing else. */
@@ -3914,6 +4046,7 @@ onPrincipal((claims) => {
3914
4046
 
3915
4047
  ```ts src/account/lib/Badge.test.ts
3916
4048
  import { expect, test } from 'bun:test'
4049
+
3917
4050
  import { principal } from '@abide/abide'
3918
4051
  import { createApp } from '@abide/abide/server'
3919
4052
 
@@ -3949,10 +4082,10 @@ const density = reactive(() => cookies().get('density') ?? 'comfortable')
3949
4082
  </script>
3950
4083
 
3951
4084
  <div class="tabs">
3952
- {#for one of DENSITIES}
3953
- <button aria-selected={density === one}
3954
- onclick={() => cookies().set('density', one)}>{one}</button>
3955
- {/for}
4085
+ {#for one of DENSITIES}
4086
+ <button aria-selected={density === one}
4087
+ onclick={() => cookies().set('density', one)}>{one}</button>
4088
+ {/for}
3956
4089
  </div>
3957
4090
 
3958
4091
  <ul class={density === 'compact' ? 'fields gap-0' : 'fields gap-flow'}>
@@ -3963,6 +4096,7 @@ const density = reactive(() => cookies().get('density') ?? 'comfortable')
3963
4096
 
3964
4097
  ```ts src/account/lib/Density.test.ts
3965
4098
  import { expect, test } from 'bun:test'
4099
+
3966
4100
  import { createApp } from '@abide/abide/server'
3967
4101
 
3968
4102
  test('the server paints the density the cookie holds', async () => {
@@ -3986,6 +4120,7 @@ test('the server paints the density the cookie holds', async () => {
3986
4120
  ```abide src/account/lib/ContactCard.abide
3987
4121
  <script>
3988
4122
  import { principal, reactive, watch } from '@abide/abide'
4123
+
3989
4124
  import getContact from '../getContact.handler.ts'
3990
4125
  import SignInAs from './SignInAs.abide'
3991
4126
 
@@ -4032,7 +4167,9 @@ export default GET(
4032
4167
 
4033
4168
  ```ts src/account/lib/ContactCard.test.ts
4034
4169
  import { expect, test } from 'bun:test'
4170
+
4035
4171
  import { createApp } from '@abide/abide/server'
4172
+
4036
4173
  import getContact from '../getContact.handler.ts'
4037
4174
 
4038
4175
  test('an admin reads the deal value', async () => {
@@ -4064,6 +4201,7 @@ An answer read under one member is not read again on its own when another signs
4064
4201
  ```abide src/account/lib/ExportContacts.abide
4065
4202
  <script>
4066
4203
  import { reactive } from '@abide/abide'
4204
+
4067
4205
  import exportContacts from '../exportContacts.handler.ts'
4068
4206
  import SignInAs from './SignInAs.abide'
4069
4207
 
@@ -4091,6 +4229,7 @@ async function exportAll() {
4091
4229
  ```ts src/account/exportContacts.handler.ts
4092
4230
  import { principal, refuse } from '@abide/abide'
4093
4231
  import { POST } from '@abide/abide/server'
4232
+
4094
4233
  import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
4095
4234
 
4096
4235
  export default POST(async () => CONTACTS, {
@@ -4108,7 +4247,9 @@ export default POST(async () => CONTACTS, {
4108
4247
 
4109
4248
  ```ts src/account/lib/ExportContacts.test.ts
4110
4249
  import { expect, test } from 'bun:test'
4250
+
4111
4251
  import { createApp } from '@abide/abide/server'
4252
+
4112
4253
  import exportContacts from '../exportContacts.handler.ts'
4113
4254
 
4114
4255
  test('an admin exports every contact', async () => {
@@ -4167,6 +4308,7 @@ Filed in the root `src/layout.abide`, a rung runs before routing, over every req
4167
4308
  ```abide src/account/lib/PartnerStatement.abide
4168
4309
  <script server>
4169
4310
  import { GET } from '@abide/abide/server'
4311
+
4170
4312
  import getStatement from '../getStatement.handler.ts'
4171
4313
 
4172
4314
  // Another site's browser, asking for the statement under its own origin.
@@ -4214,6 +4356,7 @@ export default GET(() => ({ account: 'A-1042', balance: '$2,140.00' }), {
4214
4356
 
4215
4357
  ```ts src/account/lib/PartnerStatement.test.ts
4216
4358
  import { expect, test } from 'bun:test'
4359
+
4217
4360
  import { createApp } from '@abide/abide/server'
4218
4361
 
4219
4362
  function askAs(origin: string): Request {
@@ -4250,6 +4393,7 @@ abide answers the `OPTIONS` preflight a cross-origin `POST` sends, outside every
4250
4393
  ```abide src/account/lib/SalesReport.abide
4251
4394
  <script>
4252
4395
  import { reactive } from '@abide/abide'
4396
+
4253
4397
  import salesReport from '../salesReport.handler.ts'
4254
4398
 
4255
4399
  const REGIONS = ['north', 'south']
@@ -4265,10 +4409,12 @@ function refusal() {
4265
4409
  </script>
4266
4410
 
4267
4411
  <div class="tabs">
4268
- {#for one of REGIONS}
4269
- <button aria-selected={region === one}
4270
- onclick={() => { region = one }}>{one}</button>
4271
- {/for}
4412
+ {#for one of REGIONS}
4413
+ <button aria-selected={region === one}
4414
+ onclick={() => {
4415
+ region = one
4416
+ }}>{one}</button>
4417
+ {/for}
4272
4418
  </div>
4273
4419
 
4274
4420
  <p class="status">{refusal()}</p>
@@ -4306,6 +4452,7 @@ export default GET(
4306
4452
 
4307
4453
  ```ts src/account/lib/SalesReport.test.ts
4308
4454
  import { expect, test } from 'bun:test'
4455
+
4309
4456
  import salesReport from '../salesReport.handler.ts'
4310
4457
 
4311
4458
  test('a report slower than the timeout runs, a row at a time', async () => {
@@ -4349,6 +4496,7 @@ None of these is a rate limit. A caller sending many small, fast requests passes
4349
4496
  ```abide src/account/lib/SyncContact.abide
4350
4497
  <script>
4351
4498
  import { reactive } from '@abide/abide'
4499
+
4352
4500
  import syncContact from '../syncContact.handler.ts'
4353
4501
 
4354
4502
  let status = reactive('')
@@ -4374,6 +4522,7 @@ async function sync() {
4374
4522
  ```ts src/account/syncContact.handler.ts
4375
4523
  import { refuse } from '@abide/abide'
4376
4524
  import { POST, reachable } from '@abide/abide/server'
4525
+
4377
4526
  import { billingOrigin } from './lib/billing.server.ts'
4378
4527
 
4379
4528
  /** Refuses at once while billing is down, rather than waiting on every call. */
@@ -4396,6 +4545,7 @@ export default POST(
4396
4545
 
4397
4546
  ```ts src/account/lib/SyncContact.test.ts
4398
4547
  import { expect, test } from 'bun:test'
4548
+
4399
4549
  import { reachable } from '@abide/abide/server'
4400
4550
 
4401
4551
  test('a host is asked once, however many calls ask after it', async () => {
@@ -4433,6 +4583,7 @@ The answer can be a minute old. The call that follows a `true` can still fail, a
4433
4583
  ```abide src/account/lib/AddContact.abide
4434
4584
  <script>
4435
4585
  import { reactive } from '@abide/abide'
4586
+
4436
4587
  import addContact from '../addContact.handler.ts'
4437
4588
 
4438
4589
  let added = reactive<{ status: number; location: string | null }>()
@@ -4444,7 +4595,9 @@ async function add() {
4444
4595
  </script>
4445
4596
 
4446
4597
  <div class="row">
4447
- <button onclick={() => { added = add() }}>Add Katherine Johnson</button>
4598
+ <button onclick={() => {
4599
+ added = add()
4600
+ }}>Add Katherine Johnson</button>
4448
4601
  </div>
4449
4602
 
4450
4603
  <ul class="fields">
@@ -4475,6 +4628,7 @@ export default POST(
4475
4628
 
4476
4629
  ```ts src/account/lib/AddContact.test.ts
4477
4630
  import { expect, test } from 'bun:test'
4631
+
4478
4632
  import { createApp } from '@abide/abide/server'
4479
4633
 
4480
4634
  test('adding a contact answers 201, and where it lives', async () => {
@@ -4515,9 +4669,13 @@ let refused = reactive('—')
4515
4669
  function addWidget() {
4516
4670
  const widget = document.createElement('script')
4517
4671
  widget.src = 'https://widget.example/chat.js'
4518
- document.addEventListener('securitypolicyviolation', (event) => {
4519
- refused = `${event.blockedURI} by ${event.violatedDirective}`
4520
- }, { once: true })
4672
+ document.addEventListener(
4673
+ 'securitypolicyviolation',
4674
+ (event) => {
4675
+ refused = `${event.blockedURI} by ${event.violatedDirective}`
4676
+ },
4677
+ { once: true },
4678
+ )
4521
4679
  document.head.append(widget)
4522
4680
  }
4523
4681
  </script>
@@ -4540,18 +4698,19 @@ middleware(csp())
4540
4698
  </script>
4541
4699
 
4542
4700
  <html lang="en">
4543
- <head>
4544
- <meta charset="utf-8" />
4545
- <meta name="viewport" content="width=device-width, initial-scale=1" />
4546
- </head>
4701
+ <head>
4702
+ <meta charset="utf-8" />
4703
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
4704
+ </head>
4547
4705
  <body>
4548
- <slot/>
4549
- </body>
4706
+ <slot />
4707
+ </body>
4550
4708
  </html>
4551
4709
  ```
4552
4710
 
4553
4711
  ```ts src/account/lib/Widget.test.ts
4554
4712
  import { expect, test } from 'bun:test'
4713
+
4555
4714
  import { createApp } from '@abide/abide/server'
4556
4715
 
4557
4716
  test("a page admits the app's own scripts, and no other origin's", async () => {
@@ -4604,6 +4763,7 @@ Everything in this chapter lands on one page of the CRM, `/ops`, under `src/ops`
4604
4763
  ```abide src/ops/lib/Retries.abide
4605
4764
  <script>
4606
4765
  import { reactive } from '@abide/abide'
4766
+
4607
4767
  import getRetries from '../getRetries.handler.ts'
4608
4768
 
4609
4769
  const policy = reactive(() => getRetries())
@@ -4633,6 +4793,7 @@ RETRIES=5
4633
4793
 
4634
4794
  ```ts src/ops/lib/Retries.test.ts
4635
4795
  import { expect, test } from 'bun:test'
4796
+
4636
4797
  import { createApp } from '@abide/abide/server'
4637
4798
 
4638
4799
  test('RETRIES=5 in .env overrides the default of 3, as a number', async () => {
@@ -4686,6 +4847,7 @@ onConfig(() => ({ RETRIES: 3 }), {
4686
4847
  ```abide src/ops/lib/Pool.abide
4687
4848
  <script>
4688
4849
  import { reactive } from '@abide/abide'
4850
+
4689
4851
  import getPool from '../getPool.handler.ts'
4690
4852
 
4691
4853
  const times = reactive(() => getPool())
@@ -4699,6 +4861,7 @@ const times = reactive(() => getPool())
4699
4861
 
4700
4862
  ```ts src/ops/getPool.handler.ts
4701
4863
  import { GET, onStart } from '@abide/abide/server'
4864
+
4702
4865
  import { pool } from './lib/pool.server.ts'
4703
4866
 
4704
4867
  // Connect before the app answers anything; note when it came up.
@@ -4716,6 +4879,7 @@ export default GET(() => ({ connectedAt: pool.connectedAt, upAt: pool.upAt }), {
4716
4879
 
4717
4880
  ```ts src/ops/lib/Pool.test.ts
4718
4881
  import { expect, test } from 'bun:test'
4882
+
4719
4883
  import { createApp } from '@abide/abide/server'
4720
4884
 
4721
4885
  test('the pool is connected before the first request is answered', async () => {
@@ -4755,21 +4919,23 @@ const sync = log.named('sync')
4755
4919
  </div>
4756
4920
 
4757
4921
  <ul class="fields stacked">
4758
- {#for record of log.records().slice(-20)}
4759
- {#if /:(billing|sync)$/.test(record.namespace)}
4760
- <li>
4761
- <span>{record.namespace}</span><strong>{record.message}</strong>
4762
- </li>
4763
- {/if}
4764
- {/for}
4922
+ {#for record of log.records().slice(-20)}
4923
+ {#if /:(billing|sync)$/.test(record.namespace)}
4924
+ <li>
4925
+ <span>{record.namespace}</span><strong>{record.message}</strong>
4926
+ </li>
4927
+ {/if}
4928
+ {/for}
4765
4929
  </ul>
4766
4930
  ```
4767
4931
 
4768
4932
  ```ts src/ops/lib/Console.test.ts
4769
4933
  import { expect, test } from 'bun:test'
4934
+
4770
4935
  import { flushSync } from '@abide/abide'
4771
4936
  import { createApp } from '@abide/abide/server'
4772
4937
  import { mount } from '@abide/harness/abide'
4938
+
4773
4939
  import manifest from '../../../package.json' with { type: 'json' }
4774
4940
  import Console from './Console.abide'
4775
4941
 
@@ -4830,6 +4996,7 @@ abide logs under `abide:`, so `DEBUG=abide:*` turns on the framework and nothing
4830
4996
  ```abide src/ops/lib/Sync.abide
4831
4997
  <script>
4832
4998
  import { reactive } from '@abide/abide'
4999
+
4833
5000
  import syncContact from '../syncContact.handler.ts'
4834
5001
 
4835
5002
  let synced = reactive<{ trace: string; lines: string[] }>()
@@ -4855,6 +5022,7 @@ async function sync() {
4855
5022
 
4856
5023
  ```ts src/ops/syncContact.handler.ts
4857
5024
  import { log, POST, trace } from '@abide/abide/server'
5025
+
4858
5026
  import { billing } from './lib/billing.ts'
4859
5027
 
4860
5028
  /** Answers at /ops/syncContact: syncs a contact, with its trace's lines. */
@@ -4885,6 +5053,7 @@ export const billing = {
4885
5053
 
4886
5054
  ```ts src/ops/lib/Sync.test.ts
4887
5055
  import { expect, test } from 'bun:test'
5056
+
4888
5057
  import { createApp } from '@abide/abide/server'
4889
5058
 
4890
5059
  const sync = () =>
@@ -4934,6 +5103,7 @@ With `timing`, a span joins `server-timing` under its name, so the name has to b
4934
5103
  ```abide src/ops/lib/Health.abide
4935
5104
  <script server>
4936
5105
  import { onHealth } from '@abide/abide/server'
5106
+
4937
5107
  import { pool } from './pool.server.ts'
4938
5108
 
4939
5109
  // Lets a page read `account.database` as a string.
@@ -4960,6 +5130,7 @@ const account = health()
4960
5130
 
4961
5131
  ```ts src/ops/lib/Health.test.ts
4962
5132
  import { expect, test } from 'bun:test'
5133
+
4963
5134
  import { createApp } from '@abide/abide/server'
4964
5135
 
4965
5136
  test('the health check carries the database beside the baseline', async () => {
@@ -5006,8 +5177,10 @@ import { online } from '@abide/abide'
5006
5177
 
5007
5178
  ```ts src/ops/lib/OfflineBanner.test.ts
5008
5179
  import { expect, test } from 'bun:test'
5180
+
5009
5181
  import { flushSync } from '@abide/abide'
5010
5182
  import { mount } from '@abide/harness/abide'
5183
+
5011
5184
  import OfflineBanner from './OfflineBanner.abide'
5012
5185
 
5013
5186
  test('the banner shows while the browser is offline, and goes', async () => {
@@ -5043,6 +5216,7 @@ watch(() => { if (online) refresh() })
5043
5216
  ```abide src/ops/lib/CallLog.abide
5044
5217
  <script>
5045
5218
  import { outbox, reactive } from '@abide/abide'
5219
+
5046
5220
  import listCalls from '../listCalls.handler.ts'
5047
5221
  import logCall from '../logCall.handler.ts'
5048
5222
 
@@ -5080,6 +5254,7 @@ function log() {
5080
5254
 
5081
5255
  ```ts src/ops/logCall.handler.ts
5082
5256
  import { POST } from '@abide/abide/server'
5257
+
5083
5258
  import { type Call, calls } from './lib/calls.ts'
5084
5259
 
5085
5260
  /** Logs a call once, however often it is sent: the browser picks its id. */
@@ -5095,10 +5270,12 @@ export default POST(
5095
5270
 
5096
5271
  ```ts src/ops/lib/CallLog.test.ts
5097
5272
  import { expect, test } from 'bun:test'
5273
+
5098
5274
  import { flushSync } from '@abide/abide'
5099
5275
  import { mount } from '@abide/harness/abide'
5100
- import { calls } from './calls.ts'
5276
+
5101
5277
  import CallLog from './CallLog.abide'
5278
+ import { calls } from './calls.ts'
5102
5279
 
5103
5280
  test('a call logged with the network is saved, and nothing waits', async () => {
5104
5281
  using _log = await mount(CallLog({}))
@@ -5196,6 +5373,7 @@ async function readDocument() {
5196
5373
 
5197
5374
  ```ts src/assistant/searchContacts.handler.ts
5198
5375
  import { GET } from '@abide/abide/server'
5376
+
5199
5377
  import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
5200
5378
 
5201
5379
  /** One handler: an OpenAPI operation, an MCP tool and a command. */
@@ -5208,6 +5386,7 @@ export default GET(
5208
5386
 
5209
5387
  ```ts src/assistant/lib/OpenApiOperation.test.ts
5210
5388
  import { expect, test } from 'bun:test'
5389
+
5211
5390
  import { createApp } from '@abide/abide/server'
5212
5391
 
5213
5392
  test('the handler is an operation, with its sentence and type', async () => {
@@ -5295,6 +5474,7 @@ async function callAsAModel() {
5295
5474
 
5296
5475
  ```ts src/assistant/searchContacts.handler.ts
5297
5476
  import { GET } from '@abide/abide/server'
5477
+
5298
5478
  import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
5299
5479
 
5300
5480
  /** One handler: an OpenAPI operation, an MCP tool and a command. */
@@ -5307,6 +5487,7 @@ export default GET(
5307
5487
 
5308
5488
  ```ts src/assistant/lib/ModelToolCall.test.ts
5309
5489
  import { expect, test } from 'bun:test'
5490
+
5310
5491
  import { createApp } from '@abide/abide/server'
5311
5492
 
5312
5493
  const TOOL = 'get_assistant_searchContacts'
@@ -5353,6 +5534,7 @@ abide mcp --url https://crm.example
5353
5534
  ```abide src/assistant/lib/WithheldExport.abide
5354
5535
  <script>
5355
5536
  import { reactive, url } from '@abide/abide'
5537
+
5356
5538
  import exportContacts from '../exportContacts.handler.ts'
5357
5539
 
5358
5540
  let listed = reactive('—')
@@ -5385,6 +5567,7 @@ async function check() {
5385
5567
 
5386
5568
  ```ts src/assistant/exportContacts.handler.ts
5387
5569
  import { GET } from '@abide/abide/server'
5570
+
5388
5571
  import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
5389
5572
 
5390
5573
  // Every contact at once: for the export button, not for a model.
@@ -5396,6 +5579,7 @@ export default GET(() => CONTACTS, {
5396
5579
 
5397
5580
  ```ts src/assistant/lib/WithheldExport.test.ts
5398
5581
  import { expect, test } from 'bun:test'
5582
+
5399
5583
  import { createApp } from '@abide/abide/server'
5400
5584
 
5401
5585
  test('off the tool list, and still answering on its address', async () => {
@@ -5442,6 +5626,7 @@ A handler that returns a whole `Response` starts with `mcp` and `cli` off, havin
5442
5626
  ```abide src/assistant/lib/AssistantChat.abide
5443
5627
  <script>
5444
5628
  import { reactive } from '@abide/abide'
5629
+
5445
5630
  import askAssistant from '../askAssistant.handler.ts'
5446
5631
 
5447
5632
  const QUESTION = 'Which company is Ada at?'
@@ -5457,32 +5642,34 @@ const events = reactive(() =>
5457
5642
  <ul class="chat">
5458
5643
  <li data-speaker="you"><span>you</span><p>{QUESTION}</p></li>
5459
5644
  {#for event of events}
5460
- {#if event.type === 'tool'}
5461
- <li><span>tool call</span>
5462
- <p class="font-mono text-chrome">{event.name}</p></li>
5463
- {:else if event.type === 'input'}
5464
- <li><span>with input</span>
5465
- <p class="font-mono text-chrome">
5466
- {JSON.stringify(event.input)}
5467
- </p></li>
5468
- {:else if event.type === 'result'}
5469
- <li><span>your handler answered</span>
5470
- <p class="font-mono text-chrome">{event.text}</p></li>
5471
- {:else if event.type === 'text'}
5472
- <li><span>assistant</span><p>{event.text}</p></li>
5473
- {/if}
5474
- {/for}
5645
+ {#if event.type === 'tool'}
5646
+ <li><span>tool call</span>
5647
+ <p class="font-mono text-chrome">{event.name}</p></li>
5648
+ {:else if event.type === 'input'}
5649
+ <li><span>with input</span>
5650
+ <p class="font-mono text-chrome">
5651
+ {JSON.stringify(event.input)}
5652
+ </p></li>
5653
+ {:else if event.type === 'result'}
5654
+ <li><span>your handler answered</span>
5655
+ <p class="font-mono text-chrome">{event.text}</p></li>
5656
+ {:else if event.type === 'text'}
5657
+ <li><span>assistant</span><p>{event.text}</p></li>
5658
+ {/if}
5659
+ {/for}
5475
5660
  </ul>
5476
5661
 
5477
5662
  <div class="row">
5478
- <button disabled={asked} onclick={() => { asked = true }}>Ask</button>
5663
+ <button disabled={asked} onclick={() => {
5664
+ asked = true
5665
+ }}>Ask</button>
5479
5666
  </div>
5480
5667
  ```
5481
5668
 
5482
5669
  ```ts src/assistant/askAssistant.handler.ts
5483
5670
  import {
5484
- type AssistantMessage,
5485
5671
  assistant,
5672
+ type AssistantMessage,
5486
5673
  POST,
5487
5674
  type Provider,
5488
5675
  } from '@abide/abide/server'
@@ -5517,6 +5704,7 @@ export default POST(
5517
5704
 
5518
5705
  ```ts src/assistant/lib/AssistantChat.test.ts
5519
5706
  import { expect, test } from 'bun:test'
5707
+
5520
5708
  import { createApp } from '@abide/abide/server'
5521
5709
 
5522
5710
  test('the model calls the CRM search, through the app', async () => {
@@ -5571,20 +5759,21 @@ test('the model calls the CRM search, through the app', async () => {
5571
5759
  ```abide src/assistant/lib/ClaudeApiRequest.abide
5572
5760
  <script>
5573
5761
  import { reactive } from '@abide/abide'
5762
+
5574
5763
  import askClaude from '../askClaude.handler.ts'
5575
5764
 
5576
5765
  const QUESTION = 'Which company is Ada at?'
5577
5766
 
5578
5767
  let asked = reactive(false)
5579
5768
  const events = reactive(() =>
5580
- asked
5581
- ? askClaude({ messages: [{ role: 'user', content: QUESTION }] })
5582
- : [],
5769
+ asked ? askClaude({ messages: [{ role: 'user', content: QUESTION }] }) : [],
5583
5770
  )
5584
5771
  </script>
5585
5772
 
5586
5773
  <div class="row">
5587
- <button disabled={asked} onclick={() => { asked = true }}>Ask</button>
5774
+ <button disabled={asked} onclick={() => {
5775
+ asked = true
5776
+ }}>Ask</button>
5588
5777
  <span class="status">{QUESTION}</span>
5589
5778
  </div>
5590
5779
 
@@ -5598,12 +5787,13 @@ const events = reactive(() =>
5598
5787
 
5599
5788
  ```ts src/assistant/askClaude.handler.ts
5600
5789
  import {
5601
- type AssistantMessage,
5602
5790
  assistant,
5791
+ type AssistantMessage,
5603
5792
  claudeApi,
5604
5793
  POST,
5605
5794
  request,
5606
5795
  } from '@abide/abide/server'
5796
+
5607
5797
  import standIn from './v1/messages.handler.ts'
5608
5798
 
5609
5799
  export default POST(
@@ -5659,6 +5849,7 @@ export default POST(
5659
5849
 
5660
5850
  ```ts src/assistant/lib/ClaudeApiRequest.test.ts
5661
5851
  import { expect, spyOn, test } from 'bun:test'
5852
+
5662
5853
  import { createApp } from '@abide/abide/server'
5663
5854
 
5664
5855
  test('the body is sent as written, with the transcript', async () => {
@@ -5709,11 +5900,12 @@ Leave `baseURL` out and the request goes to Anthropic. `claudeApi` reads no key
5709
5900
 
5710
5901
  ## `claudeCode` runs on the visitor's own Claude Code
5711
5902
 
5712
- **`claudeCode(config)` — a turn on the reader's own Claude Code** — The assistant on whatever Claude Code is paired with this browser. Ask first and it is refused; connect, run the command in a terminal, and ask again.
5903
+ **`claudeCode(config)` — a turn on the reader's own Claude Code** — The assistant on whatever Claude Code is paired with this browser. Ask first and it is refused; connect, run the command in a terminal or open the link, and ask again.
5713
5904
 
5714
5905
  ```abide src/assistant/lib/ClaudeCodeBridge.abide
5715
5906
  <script>
5716
5907
  import { reactive } from '@abide/abide'
5908
+
5717
5909
  import askClaudeCode from '../askClaudeCode.handler.ts'
5718
5910
  import bridge from '../bridge.handler.ts'
5719
5911
 
@@ -5743,21 +5935,30 @@ const answer = reactive(() => {
5743
5935
  {:else if pairing.at(-1)}
5744
5936
  <pre class="rounded-md border border-rule bg-raised p-snug
5745
5937
  text-chrome">{pairing.at(-1)?.command}</pre>
5938
+ <div class="row">
5939
+ <a href={pairing.at(-1)?.link}>Open in abide bridge</a>
5940
+ <span class="status">on macOS, after
5941
+ <code>bunx @abide/bridge install</code></span>
5942
+ </div>
5746
5943
  {:else}
5747
5944
  <div class="row">
5748
- <button onclick={() => { connecting = true }}>Connect</button>
5945
+ <button onclick={() => {
5946
+ connecting = true
5947
+ }}>Connect</button>
5749
5948
  </div>
5750
5949
  {/if}
5751
5950
 
5752
5951
  <ul class="chat">
5753
5952
  <li data-speaker="you"><span>you</span><p>{QUESTION}</p></li>
5754
5953
  {#if answer}
5755
- <li><span>claude code</span><p>{answer}</p></li>
5756
- {/if}
5954
+ <li><span>claude code</span><p>{answer}</p></li>
5955
+ {/if}
5757
5956
  </ul>
5758
5957
 
5759
5958
  <div class="row">
5760
- <button onclick={() => { asked += 1 }}>Ask</button>
5959
+ <button onclick={() => {
5960
+ asked += 1
5961
+ }}>Ask</button>
5761
5962
  <span class="status">{events.error()?.message ?? ''}</span>
5762
5963
  </div>
5763
5964
  ```
@@ -5776,8 +5977,8 @@ export default GET(() => claudeCode.bridge(), {
5776
5977
 
5777
5978
  ```ts src/assistant/askClaudeCode.handler.ts
5778
5979
  import {
5779
- type AssistantMessage,
5780
5980
  assistant,
5981
+ type AssistantMessage,
5781
5982
  claudeCode,
5782
5983
  POST,
5783
5984
  } from '@abide/abide/server'
@@ -5796,6 +5997,7 @@ export default POST(
5796
5997
 
5797
5998
  ```ts src/assistant/lib/ClaudeCodeBridge.test.ts
5798
5999
  import { expect, test } from 'bun:test'
6000
+
5799
6001
  import { createApp } from '@abide/abide/server'
5800
6002
 
5801
6003
  // A request from one browser: the cookie a page is handed on its first visit.
@@ -5813,7 +6015,7 @@ function fromPage(
5813
6015
  })
5814
6016
  }
5815
6017
 
5816
- test('unpaired: asking is refused, the bridge shows a command', async () => {
6018
+ test('unpaired: refused, with a command and a link', async () => {
5817
6019
  await using app = await createApp()
5818
6020
  const refused = await app.fetch(
5819
6021
  fromPage('/assistant/askClaudeCode', {
@@ -5834,11 +6036,14 @@ test('unpaired: asking is refused, the bridge shows a command', async () => {
5834
6036
  const reader = (status.body as ReadableStream<Uint8Array>).getReader()
5835
6037
  const first = new TextDecoder().decode((await reader.read()).value)
5836
6038
  watching.abort()
5837
- const { paired, command } = JSON.parse(first.split('\n')[0] ?? '')
6039
+ const { paired, command, link } = JSON.parse(first.split('\n')[0] ?? '')
5838
6040
  expect(paired).toBe(false)
5839
6041
  expect(command).toMatch(
5840
6042
  /^bunx @abide\/bridge@\S+ http:\/\/crm.invalid \S+$/,
5841
6043
  )
6044
+ expect(new URL(link).searchParams.get('code')).toBe(
6045
+ command.split(' ').at(-1),
6046
+ )
5842
6047
  })
5843
6048
  ```
5844
6049
 
@@ -5854,13 +6059,21 @@ Pass the same `tools` to `claudeCode.bridge` or `claudeCode.bridgeCommand` and t
5854
6059
 
5855
6060
  The visitor pairs once per visit by running a command, which dials out to the site. Asking with nothing paired is refused 409.
5856
6061
 
6062
+ On a Mac the visitor can pair with a click instead. Each status carries a `link` beside its command, the same pairing in the `abide-bridge` scheme, and a visitor who has run this once opens it from the page:
6063
+
6064
+ ```
6065
+ bunx @abide/bridge install
6066
+ ```
6067
+
6068
+ That builds a small app under their own Application Support folder, nowhere in Applications, and registers it for the scheme. Opening a link asks them every time, naming the site, the tools and the bridge release it asks for, and then runs that release, as the command would. A link only proves the site minted its code, never that this visitor asked for it, so no answer is kept for a site. `bunx @abide/bridge uninstall` takes it away.
6069
+
5857
6070
  The tools the conversation calls run as the visitor, on the sign-in their ask carried, so a tool reads `principal` as any handler does. The pairing belongs to that sign-in too. If someone else signs in at the same browser, their asks are refused 409 and the status offers them a command of their own, so nobody's turn runs on another member's Claude Code.
5858
6071
 
5859
6072
  | Name | What it does for the CRM |
5860
6073
  | --- | --- |
5861
- | `claudeCode.bridge` | Streams this browser's pairing: unpaired with the command to run, then paired. |
5862
- | `BridgeStatus` | One status in that stream, `{ paired, command }`. |
5863
- | `claudeCode.bridgeCommand` | Mints the command alone, for a page that wants the line without the stream. Its code expires unused after ten minutes. |
6074
+ | `claudeCode.bridge` | Streams this browser's pairing: unpaired with the command to run and the link to open, then paired. |
6075
+ | `BridgeStatus` | One status in that stream, `{ paired, command, link }`. |
6076
+ | `claudeCode.bridgeCommand` | Mints the command and the link alone, for a page that wants them without the stream. Its code expires unused after ten minutes. |
5864
6077
  | `/__abide/bridge` | Where the visitor's bridge connects. It opens the first time a command is minted. |
5865
6078
 
5866
6079
  ## `abide call` runs a handler from the terminal
@@ -5909,6 +6122,7 @@ Each card is a file of the reports page, beside the test or the bench that prove
5909
6122
  ```abide src/reports/lib/PipelineSummary.abide
5910
6123
  <script>
5911
6124
  import { reactive } from '@abide/abide'
6125
+
5912
6126
  import getSummary from '../getSummary.handler.ts'
5913
6127
 
5914
6128
  const summary = reactive(() => getSummary())
@@ -5922,6 +6136,7 @@ const summary = reactive(() => getSummary())
5922
6136
 
5923
6137
  ```ts src/reports/getSummary.handler.ts
5924
6138
  import { GET } from '@abide/abide/server'
6139
+
5925
6140
  import { DEALS } from './lib/DEALS.server.ts'
5926
6141
 
5927
6142
  /** Answers at /reports/getSummary; the summary tiles and their test read it. */
@@ -5942,6 +6157,7 @@ export default GET(
5942
6157
 
5943
6158
  ```ts src/reports/lib/PipelineSummary.test.ts
5944
6159
  import { expect, test } from 'bun:test'
6160
+
5945
6161
  import { createApp } from '@abide/abide/server'
5946
6162
 
5947
6163
  // Run from the app's own directory: `createApp` finds the app there.
@@ -5977,6 +6193,7 @@ A `.test.ts` has no address, so a test sits beside the file it tests and is neve
5977
6193
  ```abide src/reports/lib/ClosedTiles.abide
5978
6194
  <script>
5979
6195
  import { reactive } from '@abide/abide'
6196
+
5980
6197
  import { closedTotals } from './closedTotals.ts'
5981
6198
 
5982
6199
  let days = reactive(7)
@@ -6019,8 +6236,10 @@ export function closedTotals(days: number) {
6019
6236
 
6020
6237
  ```ts src/reports/lib/ClosedTiles.test.ts
6021
6238
  import { expect, test } from 'bun:test'
6239
+
6022
6240
  import { flushSync } from '@abide/abide'
6023
6241
  import { measure, mount } from '@abide/harness/abide'
6242
+
6024
6243
  import ClosedTiles from './ClosedTiles.abide'
6025
6244
 
6026
6245
  test('a range switch rewrites two numbers and builds nothing', async () => {
@@ -6076,6 +6295,7 @@ The emulated DOM is JavaScript, so a count from it holds in a browser and a mill
6076
6295
  ```abide src/reports/lib/StageTiles.abide
6077
6296
  <script>
6078
6297
  import { reactive } from '@abide/abide'
6298
+
6079
6299
  import { summarizeStage } from './summarizeStage.ts'
6080
6300
 
6081
6301
  let stage = reactive('qualified')
@@ -6132,9 +6352,11 @@ export function summarizeStage(stage: string) {
6132
6352
 
6133
6353
  ```ts src/reports/lib/StageTiles.test.ts
6134
6354
  import { expect, test } from 'bun:test'
6355
+
6135
6356
  import { flushSync } from '@abide/abide'
6136
6357
  import { mount } from '@abide/harness/abide'
6137
6358
  import { measure, publishedBy } from '@abide/harness/measure'
6359
+
6138
6360
  import StageTiles from './StageTiles.abide'
6139
6361
 
6140
6362
  test('three tiles read one summary, worked out once per switch', async () => {
@@ -6160,6 +6382,7 @@ The three tiles read one derived value, so the summary runs once however many ti
6160
6382
  ```abide src/reports/lib/WonDeals.abide
6161
6383
  <script>
6162
6384
  import { reactive } from '@abide/abide'
6385
+
6163
6386
  import { money } from './money.ts'
6164
6387
 
6165
6388
  let won = reactive([{ company: 'Analytical Engines', amount: 8000 }])
@@ -6199,9 +6422,11 @@ export const money = {
6199
6422
 
6200
6423
  ```ts src/reports/lib/WonDeals.test.ts
6201
6424
  import { expect, spyOn } from 'bun:test'
6425
+
6202
6426
  import { flushSync } from '@abide/abide'
6203
6427
  import { mount } from '@abide/harness/abide'
6204
6428
  import { gate } from '@abide/harness/gate'
6429
+
6205
6430
  import { money } from './money.ts'
6206
6431
  import WonDeals from './WonDeals.abide'
6207
6432
 
@@ -6257,6 +6482,7 @@ With that set, every gate runs a second time with the revert installed, and that
6257
6482
  ```abide src/reports/lib/Forecast.abide
6258
6483
  <script>
6259
6484
  import { reactive } from '@abide/abide'
6485
+
6260
6486
  import { advance, type Deal, forecast } from './forecast.ts'
6261
6487
 
6262
6488
  let deals = reactive<Deal[]>([
@@ -6313,8 +6539,10 @@ export function advance(deals: Deal[], index: number): Deal[] {
6313
6539
 
6314
6540
  ```ts src/reports/lib/Forecast.test.ts
6315
6541
  import { expect, test } from 'bun:test'
6542
+
6316
6543
  import { flushSync } from '@abide/abide'
6317
6544
  import { mount } from '@abide/harness/abide'
6545
+
6318
6546
  import Forecast from './Forecast.abide'
6319
6547
 
6320
6548
  test('advancing a deal moves the forecast by its weighted amount', async () => {
@@ -6330,7 +6558,8 @@ test('advancing a deal moves the forecast by its weighted amount', async () => {
6330
6558
 
6331
6559
  ```ts src/reports/lib/Forecast.bench.ts
6332
6560
  import { reactive } from '@abide/abide'
6333
- import { type Batched, batch } from '@abide/harness/report'
6561
+ import { batch, type Batched } from '@abide/harness/report'
6562
+
6334
6563
  import { advance, type Deal, forecast } from './forecast.ts'
6335
6564
 
6336
6565
  // The forecast as the card keeps it, over a pipeline of `count` deals.
@@ -6392,6 +6621,7 @@ The `bench` script is `harness-bench`, which hands `runBenches` every `*.bench.t
6392
6621
  ```abide src/reports/lib/TopDeals.abide
6393
6622
  <script>
6394
6623
  import { reactive } from '@abide/abide'
6624
+
6395
6625
  import { topFive } from './rankDeals.ts'
6396
6626
 
6397
6627
  let by = reactive<'amount' | 'idle'>('amount')
@@ -6482,6 +6712,7 @@ A faster sort saves at most the part of the click the sort was. Find that part b
6482
6712
  ```abide src/reports/lib/DealsExport.abide
6483
6713
  <script>
6484
6714
  import { props } from '@abide/abide'
6715
+
6485
6716
  import { dealsOf, type ExportedDeal } from './dealsOf.ts'
6486
6717
 
6487
6718
  const { deals = dealsOf(4) } = props<{ deals?: ExportedDeal[] }>()
@@ -6517,8 +6748,10 @@ export function dealsOf(count: number): ExportedDeal[] {
6517
6748
 
6518
6749
  ```ts src/reports/lib/DealsExport.test.ts
6519
6750
  import { expect, test } from 'bun:test'
6751
+
6520
6752
  import { render } from '@abide/abide/server'
6521
6753
  import { bytes } from '@abide/harness/server'
6754
+
6522
6755
  import DealsExport from './DealsExport.abide'
6523
6756
  import { dealsOf } from './dealsOf.ts'
6524
6757
 
@@ -6574,6 +6807,8 @@ abide start --port 8080
6574
6807
 
6575
6808
  `abide check [dir…]` type-checks the `.abide` files as well as the TypeScript, and reports on the line you wrote. `abide lsp` is the same check in your editor. Run it first in CI, because it is the cheapest failure to report.
6576
6809
 
6810
+ `abide format [--check] [dir…]` formats every `.abide` the way `bun fmt` formats everything else: its scripts, styles and expressions under the `[fmt]` options in `bunfig.toml`, and its markup indented by its nesting. `--check` writes nothing and fails where a file would change, and `abide lsp` formats on save the same way.
6811
+
6577
6812
  `abide build` writes content-hashed chunks and a manifest naming them. `abide start [--port <n>]` serves what was built, and builds first where nothing was. Configuration is checked as it starts, so a misconfigured deployment fails to come up.
6578
6813
 
6579
6814
  | Name | What it does for the CRM |
@@ -8184,8 +8419,8 @@ Generated surfaces: what is written in place rather than imported.
8184
8419
  | Name | Signature | Meaning | Rules |
8185
8420
  | --- | --- | --- | --- |
8186
8421
  | `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), [44.33](/rulebook#44.33) |
8187
- | `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), [44.33](/rulebook#44.33) |
8188
- | `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), [44.33](/rulebook#44.33), [44.34](/rulebook#44.34) |
8422
+ | `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | [44.14](/rulebook#44.14), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.40](/rulebook#44.40), [44.41](/rulebook#44.41), [44.42](/rulebook#44.42), [44.43](/rulebook#44.43) |
8423
+ | `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), [44.33](/rulebook#44.33), [44.34](/rulebook#44.34), [44.40](/rulebook#44.40) |
8189
8424
  | `/__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) |
8190
8425
 
8191
8426
  ## Taught in
@@ -8222,7 +8457,7 @@ Generated surfaces: what is written in place rather than imported.
8222
8457
 
8223
8458
  | Name | Signature | Meaning | Rules |
8224
8459
  | --- | --- | --- | --- |
8225
- | `BridgeStatus` | `{ paired: boolean; command: string \| undefined }` | Whether a bridge is paired, and the command that pairs one where none is. | [44.20](/rulebook#44.20) |
8460
+ | `BridgeStatus` | `{ paired: boolean; command: string \| undefined; link: string \| undefined }` | Whether a bridge is paired, and the command and the link that pair one where none is. | [44.20](/rulebook#44.20), [44.40](/rulebook#44.40) |
8226
8461
 
8227
8462
  ## Taught in
8228
8463
 
@@ -10504,6 +10739,7 @@ CLI: what is written in place rather than imported.
10504
10739
  | `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112), [38.122](/rulebook#38.122), [38.124](/rulebook#38.124) |
10505
10740
  | `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
10506
10741
  | `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), [38.123](/rulebook#38.123) |
10742
+ | `abide format [--check] [dir…]` | The formatter over a `.abide` tree. | [38.25](/rulebook#38.25), [38.125](/rulebook#38.125), [38.126](/rulebook#38.126), [38.127](/rulebook#38.127), [38.128](/rulebook#38.128), [38.129](/rulebook#38.129) |
10507
10743
  | `abide routes [--json]` | Every address the app answers, the file answering it, what each matcher takes, and where each hook is filed. | [38.108](/rulebook#38.108), [38.123](/rulebook#38.123) |
10508
10744
  | `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), [38.123](/rulebook#38.123) |
10509
10745
  | `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), [38.123](/rulebook#38.123) |
@@ -10515,7 +10751,7 @@ CLI: what is written in place rather than imported.
10515
10751
  | `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | [38.69](/rulebook#38.69) |
10516
10752
  | `abide compile [--target] [--out] [--platforms]` | One standalone executable. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.44](/rulebook#38.44), [38.45](/rulebook#38.45), [38.46](/rulebook#38.46), [38.47](/rulebook#38.47), [38.48](/rulebook#38.48), [38.49](/rulebook#38.49), [38.50](/rulebook#38.50), [38.51](/rulebook#38.51), [38.52](/rulebook#38.52), [38.56](/rulebook#38.56), [38.93](/rulebook#38.93), [38.97](/rulebook#38.97), [38.123](/rulebook#38.123) |
10517
10753
  | `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.70](/rulebook#38.70), [38.71](/rulebook#38.71), [38.72](/rulebook#38.72), [38.73](/rulebook#38.73), [38.74](/rulebook#38.74), [38.75](/rulebook#38.75), [38.76](/rulebook#38.76), [38.77](/rulebook#38.77), [38.78](/rulebook#38.78), [38.79](/rulebook#38.79), [38.80](/rulebook#38.80), [38.81](/rulebook#38.81), [38.82](/rulebook#38.82), [38.83](/rulebook#38.83), [38.84](/rulebook#38.84), [38.85](/rulebook#38.85), [38.86](/rulebook#38.86), [38.87](/rulebook#38.87), [38.88](/rulebook#38.88), [38.89](/rulebook#38.89), [38.90](/rulebook#38.90), [38.91](/rulebook#38.91), [38.92](/rulebook#38.92), [38.101](/rulebook#38.101), [38.102](/rulebook#38.102), [38.123](/rulebook#38.123) |
10518
- | `abide lsp` | The `.abide` language server, over stdio. | [38.11](/rulebook#38.11), [38.34](/rulebook#38.34), [38.39](/rulebook#38.39), [38.98](/rulebook#38.98), [38.116](/rulebook#38.116), [38.117](/rulebook#38.117), [38.118](/rulebook#38.118) |
10754
+ | `abide lsp` | The `.abide` language server, over stdio. | [38.11](/rulebook#38.11), [38.34](/rulebook#38.34), [38.39](/rulebook#38.39), [38.98](/rulebook#38.98), [38.116](/rulebook#38.116), [38.117](/rulebook#38.117), [38.118](/rulebook#38.118), [38.130](/rulebook#38.130) |
10519
10755
  | `--help` · `-h` · `abide help` | Usage, generated from the command list. | [38.12](/rulebook#38.12), [38.113](/rulebook#38.113), [38.114](/rulebook#38.114) |
10520
10756
  | `--version` · `-v` | The version of abide, or of the app a compiled executable holds. | [38.115](/rulebook#38.115) |
10521
10757