@abide/abide 0.118.0 → 0.119.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/bin/abide.ts +74 -11
- package/docs/REGISTRY.md +2 -1
- package/docs/RULEBOOK.md +22 -0
- package/docs/abide.md +338 -118
- package/package.json +1 -1
- package/scaffold/src/getGreeting.test.ts +1 -0
- package/scaffold/src/index.abide +4 -1
- package/scaffold/src/index.test.ts +2 -0
- package/scaffold/src/layout.abide +6 -6
- package/src/compiler/Lowering.ts +20 -2
- package/src/compiler/PositionTable.ts +1 -0
- package/src/compiler/Refusals.ts +1 -0
- package/src/compiler/TypeSession.ts +11 -8
- package/src/compiler/aliasedSymbol.ts +1 -1
- package/src/compiler/ambientReadsIn.ts +1 -0
- package/src/compiler/appLayout.ts +4 -2
- package/src/compiler/argsDefaultsIn.ts +1 -0
- package/src/compiler/argsSchemaOf.ts +1 -0
- package/src/compiler/checkApp.ts +3 -1
- package/src/compiler/checkedShadow.ts +2 -1
- package/src/compiler/classifyRead.ts +1 -0
- package/src/compiler/classifyTemplate.ts +1 -0
- package/src/compiler/compileApp.ts +4 -2
- package/src/compiler/compiledEntry.ts +1 -0
- package/src/compiler/componentGraph.ts +1 -0
- package/src/compiler/declaredExports.ts +4 -2
- package/src/compiler/emitClient.ts +19 -14
- package/src/compiler/emitComponent.ts +1 -0
- package/src/compiler/emitSource.ts +2 -1
- package/src/compiler/formatAbide.ts +857 -0
- package/src/compiler/formatDiagnosis.ts +1 -0
- package/src/compiler/handlersUnder.ts +2 -1
- package/src/compiler/hookCalls.ts +1 -0
- package/src/compiler/lineOf.ts +17 -0
- package/src/compiler/matchersOf.ts +13 -3
- package/src/compiler/mountsOf.ts +2 -0
- package/src/compiler/paramFile.ts +1 -0
- package/src/compiler/parseAbide.ts +48 -28
- package/src/compiler/projectShadow.ts +10 -3
- package/src/compiler/reactiveOptions.ts +1 -0
- package/src/compiler/reactiveTypes.ts +2 -1
- package/src/compiler/readsIn.ts +3 -2
- package/src/compiler/renderMarkup.ts +3 -2
- package/src/compiler/resolverIn.ts +2 -0
- package/src/compiler/sameTemplate.ts +150 -0
- package/src/compiler/scaffold.ts +4 -11
- package/src/compiler/serveLanguage.ts +46 -19
- package/src/compiler/sharedModuleWrites.ts +2 -1
- package/src/compiler/sourceMapOf.ts +1 -0
- package/src/compiler/splitImports.ts +2 -0
- package/src/compiler/templateWarnings.ts +2 -2
- package/src/compiler/trackingWarnings.ts +1 -0
- package/src/compiler/writeDefaultProject.ts +5 -1
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/abideModules.ts +2 -0
- package/src/server/answerFailure.ts +1 -0
- package/src/server/answerRequest.ts +15 -14
- package/src/server/answerRpc.ts +3 -2
- package/src/server/answerSocket.ts +2 -1
- package/src/server/answerValue.ts +1 -0
- package/src/server/answerWorker.ts +1 -0
- package/src/server/appTree.ts +3 -1
- package/src/server/argsFor.ts +1 -0
- package/src/server/assistant.ts +2 -1
- package/src/server/banner.ts +2 -0
- package/src/server/bridgeRelay.ts +1 -0
- package/src/server/callerOf.ts +1 -0
- package/src/server/claudeApi.ts +1 -0
- package/src/server/claudeCode.ts +1 -0
- package/src/server/clientBundle.ts +1 -0
- package/src/server/coerceArgs.ts +1 -2
- package/src/server/commands.ts +9 -0
- package/src/server/compressAnswer.ts +2 -1
- package/src/server/config.ts +2 -0
- package/src/server/createApp.ts +4 -2
- package/src/server/crossingOrigin.ts +1 -0
- package/src/server/csp.ts +1 -0
- package/src/server/deferredBuilds.ts +1 -0
- package/src/server/derivedArgs.ts +1 -0
- package/src/server/devWorker.ts +2 -1
- package/src/server/faceOf.ts +2 -7
- package/src/server/generatedSurfaces.ts +5 -1
- package/src/server/hooks.ts +1 -0
- package/src/server/iconAccent.ts +1 -1
- package/src/server/installServer.ts +1 -0
- package/src/server/installSourceMaps.ts +1 -0
- package/src/server/launcherPage.ts +2 -2
- package/src/server/manifestOf.ts +1 -0
- package/src/server/muxUpgrade.ts +1 -0
- package/src/server/nativeMenu.ts +2 -0
- package/src/server/principal.ts +2 -1
- package/src/server/remote.ts +1 -0
- package/src/server/render.ts +3 -3
- package/src/server/renderComponent.ts +1 -0
- package/src/server/reportHealth.ts +1 -0
- package/src/server/requestScope.ts +3 -1
- package/src/server/runDesktop.ts +3 -2
- package/src/server/seedBuffer.ts +1 -0
- package/src/server/serveDev.ts +3 -2
- package/src/server/shell.ts +2 -0
- package/src/server/streamEvent.ts +1 -0
- package/src/server/surfaceOf.ts +1 -0
- package/src/server/testBuild.ts +1 -0
- package/src/server/testMount.ts +1 -0
- package/src/server/trace.ts +2 -0
- package/src/server/withAbideSources.ts +1 -0
- package/src/shared/Attributes.ts +12 -8
- package/src/shared/cookies.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/index.ts +1 -5
- package/src/shared/jsonSchema.ts +1 -3
- package/src/shared/menuBridge.ts +1 -2
- package/src/shared/outbox.ts +1 -1
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/Reactive.ts +12 -16
- package/src/shared/reactive/index.ts +3 -4
- package/src/shared/reactive/isReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +2 -2
- package/src/shared/reactive/share.ts +1 -1
- package/src/shared/reactive/watch.ts +4 -4
- package/src/shared/refuse.ts +1 -1
- package/src/shared/rpc.ts +8 -11
- package/src/shared/url.ts +1 -1
- package/src/shared/validateJson.ts +1 -1
- package/src/ui/ArmRegion.ts +1 -0
- package/src/ui/Level.ts +1 -0
- package/src/ui/List.ts +1 -0
- package/src/ui/RawRun.ts +1 -0
- package/src/ui/Router.ts +1 -0
- package/src/ui/TryRegion.ts +1 -0
- package/src/ui/adoptWalk.ts +2 -1
- package/src/ui/bindElement.ts +1 -0
- package/src/ui/bindFiles.ts +1 -0
- package/src/ui/bindGroup.ts +1 -0
- package/src/ui/bindProperty.ts +1 -0
- package/src/ui/bindSpread.ts +1 -0
- package/src/ui/index.ts +1 -6
- package/src/ui/outboxQueue.ts +1 -1
- package/src/ui/parseMarkup.ts +2 -1
- package/src/ui/reconcile.ts +2 -1
- package/src/ui/reconcileHead.ts +1 -0
- package/src/ui/regions.ts +2 -1
- package/src/ui/registerWorker.ts +1 -0
- package/src/ui/rows.ts +1 -0
- package/src/ui/setMarkup.ts +1 -0
- package/src/ui/setText.ts +1 -0
- package/src/ui/socketWire.ts +4 -3
- 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
|
|
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
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
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
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2049
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3706
|
-
|
|
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
|
-
|
|
3954
|
-
|
|
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
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
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={() => {
|
|
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(
|
|
4519
|
-
|
|
4520
|
-
|
|
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
|
-
|
|
4545
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
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={() => {
|
|
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={() => {
|
|
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 () => {
|
|
@@ -5714,6 +5905,7 @@ Leave `baseURL` out and the request goes to Anthropic. `claudeApi` reads no key
|
|
|
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
|
|
|
@@ -5745,19 +5937,23 @@ const answer = reactive(() => {
|
|
|
5745
5937
|
text-chrome">{pairing.at(-1)?.command}</pre>
|
|
5746
5938
|
{:else}
|
|
5747
5939
|
<div class="row">
|
|
5748
|
-
<button onclick={() => {
|
|
5940
|
+
<button onclick={() => {
|
|
5941
|
+
connecting = true
|
|
5942
|
+
}}>Connect</button>
|
|
5749
5943
|
</div>
|
|
5750
5944
|
{/if}
|
|
5751
5945
|
|
|
5752
5946
|
<ul class="chat">
|
|
5753
5947
|
<li data-speaker="you"><span>you</span><p>{QUESTION}</p></li>
|
|
5754
5948
|
{#if answer}
|
|
5755
|
-
|
|
5756
|
-
{/if}
|
|
5949
|
+
<li><span>claude code</span><p>{answer}</p></li>
|
|
5950
|
+
{/if}
|
|
5757
5951
|
</ul>
|
|
5758
5952
|
|
|
5759
5953
|
<div class="row">
|
|
5760
|
-
<button onclick={() => {
|
|
5954
|
+
<button onclick={() => {
|
|
5955
|
+
asked += 1
|
|
5956
|
+
}}>Ask</button>
|
|
5761
5957
|
<span class="status">{events.error()?.message ?? ''}</span>
|
|
5762
5958
|
</div>
|
|
5763
5959
|
```
|
|
@@ -5776,8 +5972,8 @@ export default GET(() => claudeCode.bridge(), {
|
|
|
5776
5972
|
|
|
5777
5973
|
```ts src/assistant/askClaudeCode.handler.ts
|
|
5778
5974
|
import {
|
|
5779
|
-
type AssistantMessage,
|
|
5780
5975
|
assistant,
|
|
5976
|
+
type AssistantMessage,
|
|
5781
5977
|
claudeCode,
|
|
5782
5978
|
POST,
|
|
5783
5979
|
} from '@abide/abide/server'
|
|
@@ -5796,6 +5992,7 @@ export default POST(
|
|
|
5796
5992
|
|
|
5797
5993
|
```ts src/assistant/lib/ClaudeCodeBridge.test.ts
|
|
5798
5994
|
import { expect, test } from 'bun:test'
|
|
5995
|
+
|
|
5799
5996
|
import { createApp } from '@abide/abide/server'
|
|
5800
5997
|
|
|
5801
5998
|
// A request from one browser: the cookie a page is handed on its first visit.
|
|
@@ -5909,6 +6106,7 @@ Each card is a file of the reports page, beside the test or the bench that prove
|
|
|
5909
6106
|
```abide src/reports/lib/PipelineSummary.abide
|
|
5910
6107
|
<script>
|
|
5911
6108
|
import { reactive } from '@abide/abide'
|
|
6109
|
+
|
|
5912
6110
|
import getSummary from '../getSummary.handler.ts'
|
|
5913
6111
|
|
|
5914
6112
|
const summary = reactive(() => getSummary())
|
|
@@ -5922,6 +6120,7 @@ const summary = reactive(() => getSummary())
|
|
|
5922
6120
|
|
|
5923
6121
|
```ts src/reports/getSummary.handler.ts
|
|
5924
6122
|
import { GET } from '@abide/abide/server'
|
|
6123
|
+
|
|
5925
6124
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
5926
6125
|
|
|
5927
6126
|
/** Answers at /reports/getSummary; the summary tiles and their test read it. */
|
|
@@ -5942,6 +6141,7 @@ export default GET(
|
|
|
5942
6141
|
|
|
5943
6142
|
```ts src/reports/lib/PipelineSummary.test.ts
|
|
5944
6143
|
import { expect, test } from 'bun:test'
|
|
6144
|
+
|
|
5945
6145
|
import { createApp } from '@abide/abide/server'
|
|
5946
6146
|
|
|
5947
6147
|
// Run from the app's own directory: `createApp` finds the app there.
|
|
@@ -5977,6 +6177,7 @@ A `.test.ts` has no address, so a test sits beside the file it tests and is neve
|
|
|
5977
6177
|
```abide src/reports/lib/ClosedTiles.abide
|
|
5978
6178
|
<script>
|
|
5979
6179
|
import { reactive } from '@abide/abide'
|
|
6180
|
+
|
|
5980
6181
|
import { closedTotals } from './closedTotals.ts'
|
|
5981
6182
|
|
|
5982
6183
|
let days = reactive(7)
|
|
@@ -6019,8 +6220,10 @@ export function closedTotals(days: number) {
|
|
|
6019
6220
|
|
|
6020
6221
|
```ts src/reports/lib/ClosedTiles.test.ts
|
|
6021
6222
|
import { expect, test } from 'bun:test'
|
|
6223
|
+
|
|
6022
6224
|
import { flushSync } from '@abide/abide'
|
|
6023
6225
|
import { measure, mount } from '@abide/harness/abide'
|
|
6226
|
+
|
|
6024
6227
|
import ClosedTiles from './ClosedTiles.abide'
|
|
6025
6228
|
|
|
6026
6229
|
test('a range switch rewrites two numbers and builds nothing', async () => {
|
|
@@ -6076,6 +6279,7 @@ The emulated DOM is JavaScript, so a count from it holds in a browser and a mill
|
|
|
6076
6279
|
```abide src/reports/lib/StageTiles.abide
|
|
6077
6280
|
<script>
|
|
6078
6281
|
import { reactive } from '@abide/abide'
|
|
6282
|
+
|
|
6079
6283
|
import { summarizeStage } from './summarizeStage.ts'
|
|
6080
6284
|
|
|
6081
6285
|
let stage = reactive('qualified')
|
|
@@ -6132,9 +6336,11 @@ export function summarizeStage(stage: string) {
|
|
|
6132
6336
|
|
|
6133
6337
|
```ts src/reports/lib/StageTiles.test.ts
|
|
6134
6338
|
import { expect, test } from 'bun:test'
|
|
6339
|
+
|
|
6135
6340
|
import { flushSync } from '@abide/abide'
|
|
6136
6341
|
import { mount } from '@abide/harness/abide'
|
|
6137
6342
|
import { measure, publishedBy } from '@abide/harness/measure'
|
|
6343
|
+
|
|
6138
6344
|
import StageTiles from './StageTiles.abide'
|
|
6139
6345
|
|
|
6140
6346
|
test('three tiles read one summary, worked out once per switch', async () => {
|
|
@@ -6160,6 +6366,7 @@ The three tiles read one derived value, so the summary runs once however many ti
|
|
|
6160
6366
|
```abide src/reports/lib/WonDeals.abide
|
|
6161
6367
|
<script>
|
|
6162
6368
|
import { reactive } from '@abide/abide'
|
|
6369
|
+
|
|
6163
6370
|
import { money } from './money.ts'
|
|
6164
6371
|
|
|
6165
6372
|
let won = reactive([{ company: 'Analytical Engines', amount: 8000 }])
|
|
@@ -6199,9 +6406,11 @@ export const money = {
|
|
|
6199
6406
|
|
|
6200
6407
|
```ts src/reports/lib/WonDeals.test.ts
|
|
6201
6408
|
import { expect, spyOn } from 'bun:test'
|
|
6409
|
+
|
|
6202
6410
|
import { flushSync } from '@abide/abide'
|
|
6203
6411
|
import { mount } from '@abide/harness/abide'
|
|
6204
6412
|
import { gate } from '@abide/harness/gate'
|
|
6413
|
+
|
|
6205
6414
|
import { money } from './money.ts'
|
|
6206
6415
|
import WonDeals from './WonDeals.abide'
|
|
6207
6416
|
|
|
@@ -6257,6 +6466,7 @@ With that set, every gate runs a second time with the revert installed, and that
|
|
|
6257
6466
|
```abide src/reports/lib/Forecast.abide
|
|
6258
6467
|
<script>
|
|
6259
6468
|
import { reactive } from '@abide/abide'
|
|
6469
|
+
|
|
6260
6470
|
import { advance, type Deal, forecast } from './forecast.ts'
|
|
6261
6471
|
|
|
6262
6472
|
let deals = reactive<Deal[]>([
|
|
@@ -6313,8 +6523,10 @@ export function advance(deals: Deal[], index: number): Deal[] {
|
|
|
6313
6523
|
|
|
6314
6524
|
```ts src/reports/lib/Forecast.test.ts
|
|
6315
6525
|
import { expect, test } from 'bun:test'
|
|
6526
|
+
|
|
6316
6527
|
import { flushSync } from '@abide/abide'
|
|
6317
6528
|
import { mount } from '@abide/harness/abide'
|
|
6529
|
+
|
|
6318
6530
|
import Forecast from './Forecast.abide'
|
|
6319
6531
|
|
|
6320
6532
|
test('advancing a deal moves the forecast by its weighted amount', async () => {
|
|
@@ -6330,7 +6542,8 @@ test('advancing a deal moves the forecast by its weighted amount', async () => {
|
|
|
6330
6542
|
|
|
6331
6543
|
```ts src/reports/lib/Forecast.bench.ts
|
|
6332
6544
|
import { reactive } from '@abide/abide'
|
|
6333
|
-
import { type Batched
|
|
6545
|
+
import { batch, type Batched } from '@abide/harness/report'
|
|
6546
|
+
|
|
6334
6547
|
import { advance, type Deal, forecast } from './forecast.ts'
|
|
6335
6548
|
|
|
6336
6549
|
// The forecast as the card keeps it, over a pipeline of `count` deals.
|
|
@@ -6392,6 +6605,7 @@ The `bench` script is `harness-bench`, which hands `runBenches` every `*.bench.t
|
|
|
6392
6605
|
```abide src/reports/lib/TopDeals.abide
|
|
6393
6606
|
<script>
|
|
6394
6607
|
import { reactive } from '@abide/abide'
|
|
6608
|
+
|
|
6395
6609
|
import { topFive } from './rankDeals.ts'
|
|
6396
6610
|
|
|
6397
6611
|
let by = reactive<'amount' | 'idle'>('amount')
|
|
@@ -6482,6 +6696,7 @@ A faster sort saves at most the part of the click the sort was. Find that part b
|
|
|
6482
6696
|
```abide src/reports/lib/DealsExport.abide
|
|
6483
6697
|
<script>
|
|
6484
6698
|
import { props } from '@abide/abide'
|
|
6699
|
+
|
|
6485
6700
|
import { dealsOf, type ExportedDeal } from './dealsOf.ts'
|
|
6486
6701
|
|
|
6487
6702
|
const { deals = dealsOf(4) } = props<{ deals?: ExportedDeal[] }>()
|
|
@@ -6517,8 +6732,10 @@ export function dealsOf(count: number): ExportedDeal[] {
|
|
|
6517
6732
|
|
|
6518
6733
|
```ts src/reports/lib/DealsExport.test.ts
|
|
6519
6734
|
import { expect, test } from 'bun:test'
|
|
6735
|
+
|
|
6520
6736
|
import { render } from '@abide/abide/server'
|
|
6521
6737
|
import { bytes } from '@abide/harness/server'
|
|
6738
|
+
|
|
6522
6739
|
import DealsExport from './DealsExport.abide'
|
|
6523
6740
|
import { dealsOf } from './dealsOf.ts'
|
|
6524
6741
|
|
|
@@ -6574,6 +6791,8 @@ abide start --port 8080
|
|
|
6574
6791
|
|
|
6575
6792
|
`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
6793
|
|
|
6794
|
+
`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.
|
|
6795
|
+
|
|
6577
6796
|
`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
6797
|
|
|
6579
6798
|
| Name | What it does for the CRM |
|
|
@@ -10504,6 +10723,7 @@ CLI: what is written in place rather than imported.
|
|
|
10504
10723
|
| `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
10724
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
|
|
10506
10725
|
| `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) |
|
|
10726
|
+
| `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
10727
|
| `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
10728
|
| `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
10729
|
| `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 +10735,7 @@ CLI: what is written in place rather than imported.
|
|
|
10515
10735
|
| `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | [38.69](/rulebook#38.69) |
|
|
10516
10736
|
| `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
10737
|
| `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) |
|
|
10738
|
+
| `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
10739
|
| `--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
10740
|
| `--version` · `-v` | The version of abide, or of the app a compiled executable holds. | [38.115](/rulebook#38.115) |
|
|
10521
10741
|
|