@listen-fire/handbook 0.12.8 → 0.13.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.
@@ -2,20 +2,55 @@
2
2
  "capturedAt": "2026-09-04T11:55:00.772Z",
3
3
  "capturedFrom": {
4
4
  "mode": "demand",
5
- "sources": 88
5
+ "sources": 88,
6
+ "supplements": {
7
+ "github": {
8
+ "capturedAt": "2026-11-01",
9
+ "via": "lf catalog over a project connecting the real @lf/github module against its fake (modules/github/__test__/fake_github.ts): the whole github entry"
10
+ },
11
+ "slack": {
12
+ "capturedAt": "2026-11-01",
13
+ "via": "lf catalog over a project connecting the real @lf/slack module against fake-channels: the whole slack entry (the fake's workspace names its channels)"
14
+ },
15
+ "forms": {
16
+ "capturedAt": "2026-10-31",
17
+ "via": "lf catalog over a project with the real @lf/forms module (offline): the whole forms entry"
18
+ },
19
+ "email, cron, manual": {
20
+ "capturedAt": "2026-10-31",
21
+ "via": "lf catalog over a project connecting the real @lf/email module, with the cron and manual units (0.13: each listens on its edge, Emails, Ticks and Invocations; their settings vocabulary and event positions are gone); the platform capture's fields the local projection lacks are kept"
22
+ },
23
+ "attio": {
24
+ "capturedAt": "2026-11-01",
25
+ "via": "lf catalog over a project connecting the real @lf/attio module against fake-channels (0.13: each object's records list Changes, landing on its own change type, CompanyChange, PersonChange, DealChange and FundChange; the Webhook Event, its variants and the events settings are gone); the rest of the attio entry is the platform capture"
26
+ },
27
+ "telegram, whatsapp, system": {
28
+ "capturedAt": "2026-11-01",
29
+ "via": "lf catalog over a project connecting the real @lf/telegram, @lf/whatsapp and @lf/system modules (offline): the whole of each entry"
30
+ }
31
+ }
6
32
  },
7
33
  "specs": {
8
34
  "email": {
9
35
  "constructionArgs": [],
10
36
  "canFire": true,
11
- "triggerConfig": [
12
- "key"
13
- ],
14
- "triggerConfigRequired": [
15
- "key"
16
- ],
17
- "triggerConfigRequiredWhy": {
18
- "key": "the key is this listener's address, inbox+<key>@example.com; without one nothing can be sent to it and it never fires"
37
+ "listenSettingsBecome": {
38
+ "path": [
39
+ "Emails"
40
+ ],
41
+ "aliases": [
42
+ "e"
43
+ ],
44
+ "paramFrom": "Email",
45
+ "keys": {
46
+ "key": {
47
+ "hop": 0,
48
+ "field": "Key",
49
+ "op": "eq",
50
+ "absent": "",
51
+ "lowercase": true
52
+ }
53
+ }
19
54
  }
20
55
  },
21
56
  "attio": {
@@ -26,17 +61,7 @@
26
61
  "required": true
27
62
  }
28
63
  ],
29
- "canFire": true,
30
- "triggerConfig": [
31
- "events"
32
- ],
33
- "triggerConfigOptions": {
34
- "events": [
35
- "record.created",
36
- "record.updated",
37
- "record.deleted"
38
- ]
39
- }
64
+ "canFire": true
40
65
  },
41
66
  "slack": {
42
67
  "constructionArgs": [
@@ -47,21 +72,130 @@
47
72
  }
48
73
  ],
49
74
  "canFire": true,
50
- "triggerConfig": [
51
- "events",
52
- "channels"
53
- ],
54
- "triggerConfigOptions": {
55
- "events": [
56
- "app_mention",
57
- "message",
58
- "reaction_added"
59
- ]
75
+ "listenSettingsBecome": {
76
+ "path": [
77
+ "Channels",
78
+ "Messages"
79
+ ],
80
+ "aliases": [
81
+ "ch",
82
+ "m"
83
+ ],
84
+ "paramFrom": "Message",
85
+ "keys": {
86
+ "channels": {
87
+ "hop": 0,
88
+ "field": "Name",
89
+ "op": "in"
90
+ },
91
+ "events": {
92
+ "hop": 1,
93
+ "field": "Mentions App",
94
+ "op": "eq",
95
+ "values": {
96
+ "app_mention": true
97
+ },
98
+ "absent": [
99
+ "message",
100
+ "reaction_added"
101
+ ]
102
+ }
103
+ },
104
+ "byEvent": {
105
+ "app_mention": {
106
+ "path": [
107
+ "Channels",
108
+ "Messages"
109
+ ],
110
+ "aliases": [
111
+ "ch",
112
+ "m"
113
+ ],
114
+ "paramFrom": "Message",
115
+ "keys": {
116
+ "channels": {
117
+ "hop": 0,
118
+ "field": "Name",
119
+ "op": "in"
120
+ },
121
+ "events": {
122
+ "hop": 1,
123
+ "field": "Mentions App",
124
+ "op": "eq",
125
+ "values": {
126
+ "app_mention": true
127
+ }
128
+ }
129
+ },
130
+ "alsoArrivesOn": [
131
+ [
132
+ "Channels",
133
+ "Messages",
134
+ "Replies"
135
+ ]
136
+ ]
137
+ },
138
+ "message": {
139
+ "path": [
140
+ "Channels",
141
+ "Messages"
142
+ ],
143
+ "aliases": [
144
+ "ch",
145
+ "m"
146
+ ],
147
+ "paramFrom": "Message",
148
+ "keys": {
149
+ "channels": {
150
+ "hop": 0,
151
+ "field": "Name",
152
+ "op": "in"
153
+ }
154
+ },
155
+ "alsoArrivesOn": [
156
+ [
157
+ "Channels",
158
+ "Messages",
159
+ "Replies"
160
+ ]
161
+ ]
162
+ },
163
+ "reaction_added": {
164
+ "path": [
165
+ "Channels",
166
+ "Messages",
167
+ "Reactions"
168
+ ],
169
+ "aliases": [
170
+ "ch",
171
+ "m",
172
+ "r"
173
+ ],
174
+ "paramFrom": "Reaction",
175
+ "keys": {
176
+ "channels": {
177
+ "hop": 0,
178
+ "field": "Name",
179
+ "op": "in"
180
+ }
181
+ }
182
+ }
183
+ }
60
184
  }
61
185
  },
62
186
  "manual": {
63
187
  "constructionArgs": [],
64
- "canFire": true
188
+ "canFire": true,
189
+ "listenSettingsBecome": {
190
+ "aliases": [
191
+ "i"
192
+ ],
193
+ "keys": {},
194
+ "paramFrom": "Invocation",
195
+ "path": [
196
+ "Invocations"
197
+ ]
198
+ }
65
199
  },
66
200
  "ask": {
67
201
  "constructionArgs": []
@@ -69,22 +203,26 @@
69
203
  "cron": {
70
204
  "constructionArgs": [],
71
205
  "canFire": true,
72
- "triggerConfig": [
73
- "events",
74
- "schedule",
75
- "timezone"
76
- ],
77
- "triggerConfigOptions": {
78
- "events": [
79
- "tick"
206
+ "listenSettingsBecome": {
207
+ "aliases": [
208
+ "t"
209
+ ],
210
+ "keys": {
211
+ "schedule": {
212
+ "field": "Schedule",
213
+ "hop": 0,
214
+ "op": "eq"
215
+ },
216
+ "timezone": {
217
+ "field": "Zone",
218
+ "hop": 0,
219
+ "op": "eq"
220
+ }
221
+ },
222
+ "paramFrom": "Tick",
223
+ "path": [
224
+ "Ticks"
80
225
  ]
81
- },
82
- "triggerConfigRequired": [
83
- "schedule"
84
- ],
85
- "triggerConfigFormats": {
86
- "schedule": "cron",
87
- "timezone": "timezone"
88
226
  }
89
227
  },
90
228
  "telegram": {
@@ -92,46 +230,133 @@
92
230
  {
93
231
  "name": "credentials",
94
232
  "kind": "credential",
95
- "required": true
233
+ "required": false
96
234
  }
97
235
  ],
98
- "canFire": true
236
+ "canFire": true,
237
+ "listenSettingsBecome": {
238
+ "path": [
239
+ "Messages"
240
+ ],
241
+ "aliases": [
242
+ "m"
243
+ ],
244
+ "paramFrom": "Message",
245
+ "keys": {}
246
+ }
99
247
  },
100
248
  "whatsapp": {
101
- "constructionArgs": [],
102
- "canFire": true,
103
- "triggerConfig": [
104
- "events"
249
+ "constructionArgs": [
250
+ {
251
+ "name": "credentials",
252
+ "kind": "credential",
253
+ "required": true
254
+ }
105
255
  ],
106
- "triggerConfigOptions": {
107
- "events": [
108
- "message",
109
- "reaction",
110
- "location"
111
- ]
112
- },
113
- "defaultEvents": [
114
- "message"
115
- ]
256
+ "canFire": true,
257
+ "listenSettingsBecome": {
258
+ "path": [
259
+ "Messages"
260
+ ],
261
+ "aliases": [
262
+ "m"
263
+ ],
264
+ "paramFrom": "Message",
265
+ "keys": {},
266
+ "byEvent": {
267
+ "location": {
268
+ "path": [
269
+ "Locations"
270
+ ],
271
+ "aliases": [
272
+ "l"
273
+ ],
274
+ "paramFrom": "Location",
275
+ "keys": {}
276
+ },
277
+ "message": {
278
+ "path": [
279
+ "Messages"
280
+ ],
281
+ "aliases": [
282
+ "m"
283
+ ],
284
+ "paramFrom": "Message",
285
+ "keys": {}
286
+ },
287
+ "reaction": {
288
+ "path": [
289
+ "Reactions"
290
+ ],
291
+ "aliases": [
292
+ "r"
293
+ ],
294
+ "paramFrom": "Reaction",
295
+ "keys": {}
296
+ }
297
+ }
298
+ }
116
299
  },
117
300
  "system": {
118
301
  "constructionArgs": [],
119
302
  "canFire": true,
120
- "triggerConfig": [
121
- "events",
122
- "pollIntervalSeconds"
303
+ "listenSettingsBecome": {
304
+ "path": [
305
+ "Events"
306
+ ],
307
+ "aliases": [
308
+ "e"
309
+ ],
310
+ "paramFrom": "Run Failed",
311
+ "keys": {
312
+ "events": {
313
+ "hop": 0,
314
+ "field": "Kind",
315
+ "op": "in",
316
+ "absent": "Run Failed"
317
+ }
318
+ }
319
+ }
320
+ },
321
+ "github": {
322
+ "constructionArgs": [
323
+ {
324
+ "name": "credentials",
325
+ "kind": "credential",
326
+ "required": true
327
+ }
123
328
  ],
124
- "triggerConfigOptions": {
125
- "events": [
126
- "Run Failed",
127
- "Validation Issue",
128
- "Deprecated Version",
129
- "Release Applied"
130
- ]
131
- },
132
- "defaultEvents": [
133
- "Run Failed"
134
- ]
329
+ "canFire": true,
330
+ "listenSettingsBecome": {
331
+ "path": [
332
+ "Notifications"
333
+ ],
334
+ "aliases": [
335
+ "n"
336
+ ],
337
+ "paramFrom": "Notification",
338
+ "keys": {
339
+ "events": {
340
+ "hop": 0,
341
+ "field": "Reason",
342
+ "op": "in"
343
+ },
344
+ "participating": {
345
+ "hop": 0,
346
+ "field": "Participating",
347
+ "op": "eq",
348
+ "values": {
349
+ "false": null,
350
+ "true": true
351
+ }
352
+ }
353
+ }
354
+ }
355
+ },
356
+ "forms": {
357
+ "constructionArgs": [],
358
+ "description": "A form at a link, answered once. A write makes it; the node the write names in Fields says what it asks, and its answer lands on the form’s Submissions, typed by that node.",
359
+ "schemaShape": "introspected"
135
360
  }
136
361
  },
137
362
  "schemas": {
@@ -145,12 +370,27 @@
145
370
  "To": "text",
146
371
  "HTML Body": "text",
147
372
  "Plain Body": "text",
148
- "Body": "text"
373
+ "Body": "text",
374
+ "Key": {
375
+ "kind": "owned",
376
+ "name": "InboxKey",
377
+ "owner": "email",
378
+ "allowPattern": "^[a-z0-9.!#$%&'*+/=?^_`{|}~-]*$"
379
+ }
149
380
  },
150
381
  "edges": {
151
382
  "Attachments": {
152
383
  "target": "Attachment",
153
- "sequenced": "document"
384
+ "sequenced": "document",
385
+ "description": "Files attached to the email (zero or more)."
386
+ }
387
+ },
388
+ "propertyCapabilities": {
389
+ "Key": {
390
+ "filterOperators": [
391
+ "eq",
392
+ "in"
393
+ ]
154
394
  }
155
395
  }
156
396
  },
@@ -165,14 +405,24 @@
165
405
  "edges": {}
166
406
  }
167
407
  },
168
- "collections": {},
169
- "writableRoots": {},
170
- "eventPosition": "Email",
171
- "eventPositions": [
172
- {
173
- "position": "Email"
408
+ "collections": {
409
+ "Emails": {
410
+ "target": "Email",
411
+ "capability": {
412
+ "filter": "native",
413
+ "supportsLimit": false
414
+ },
415
+ "readable": false,
416
+ "listenable": true,
417
+ "bounded": false,
418
+ "boundedBy": [
419
+ "Key"
420
+ ],
421
+ "delivery": "connection",
422
+ "addressed": true
174
423
  }
175
- ]
424
+ },
425
+ "writableRoots": {}
176
426
  },
177
427
  "attio": {
178
428
  "positions": {
@@ -236,6 +486,18 @@
236
486
  "Files": {
237
487
  "target": "File",
238
488
  "writable": true
489
+ },
490
+ "Changes": {
491
+ "target": "CompanyChange",
492
+ "readable": false,
493
+ "listenable": true,
494
+ "identity": "Id",
495
+ "delivery": "connection",
496
+ "carries": [
497
+ "Companies"
498
+ ],
499
+ "attributes": true,
500
+ "description": "What happens to a company in Attio, each change once: created, an attribute updated (`Field` names it), deleted."
239
501
  }
240
502
  },
241
503
  "propertyCapabilities": {
@@ -337,6 +599,18 @@
337
599
  "Files": {
338
600
  "target": "File",
339
601
  "writable": true
602
+ },
603
+ "Changes": {
604
+ "target": "PersonChange",
605
+ "readable": false,
606
+ "listenable": true,
607
+ "identity": "Id",
608
+ "delivery": "connection",
609
+ "carries": [
610
+ "People"
611
+ ],
612
+ "attributes": true,
613
+ "description": "What happens to a person in Attio, each change once: created, an attribute updated (`Field` names it), deleted."
340
614
  }
341
615
  },
342
616
  "propertyCapabilities": {
@@ -384,13 +658,39 @@
384
658
  },
385
659
  "Deals": {
386
660
  "properties": {},
387
- "edges": {},
661
+ "edges": {
662
+ "Changes": {
663
+ "target": "DealChange",
664
+ "readable": false,
665
+ "listenable": true,
666
+ "identity": "Id",
667
+ "delivery": "connection",
668
+ "carries": [
669
+ "Deals"
670
+ ],
671
+ "attributes": true,
672
+ "description": "What happens to a deal in Attio, each change once: created, an attribute updated (`Field` names it), deleted."
673
+ }
674
+ },
388
675
  "undescribed": true,
389
676
  "deletable": true
390
677
  },
391
678
  "Funds": {
392
679
  "properties": {},
393
- "edges": {},
680
+ "edges": {
681
+ "Changes": {
682
+ "target": "FundChange",
683
+ "readable": false,
684
+ "listenable": true,
685
+ "identity": "Id",
686
+ "delivery": "connection",
687
+ "carries": [
688
+ "Funds"
689
+ ],
690
+ "attributes": true,
691
+ "description": "What happens to a fund in Attio, each change once: created, an attribute updated (`Field` names it), deleted."
692
+ }
693
+ },
394
694
  "undescribed": true
395
695
  },
396
696
  "Pipeline": {
@@ -523,65 +823,6 @@
523
823
  },
524
824
  "edges": {}
525
825
  },
526
- "Webhook Event": {
527
- "properties": {
528
- "action": {
529
- "kind": "enum",
530
- "options": [
531
- "record.created",
532
- "record.updated",
533
- "record.deleted"
534
- ]
535
- },
536
- "Workspace": "text",
537
- "Object": "text",
538
- "Record": "text",
539
- "List": "text",
540
- "Entry": "text",
541
- "Task": "text",
542
- "Note": "text",
543
- "Comment": "text",
544
- "Attribute": "text"
545
- },
546
- "edges": {
547
- "Companies": {
548
- "target": "Companies",
549
- "requiresLiveRecord": true
550
- },
551
- "People": {
552
- "target": "People",
553
- "requiresLiveRecord": true
554
- },
555
- "Deals": {
556
- "target": "Deals",
557
- "requiresLiveRecord": true
558
- },
559
- "Funds": {
560
- "target": "Funds",
561
- "requiresLiveRecord": true
562
- },
563
- "List": {
564
- "target": "List"
565
- },
566
- "Pipeline": {
567
- "target": "Pipeline",
568
- "requiresLiveRecord": true
569
- },
570
- "VC Deal Flow": {
571
- "target": "VC Deal Flow",
572
- "requiresLiveRecord": true
573
- },
574
- "Task": {
575
- "target": "Task"
576
- },
577
- "Note": {
578
- "target": "Note"
579
- },
580
- "Comment": {
581
- "target": "Comment"
582
- }
583
- }
584
- },
585
826
  "Companies List": {
586
827
  "properties": {
587
828
  "Name": "text",
@@ -592,180 +833,174 @@
592
833
  "listName"
593
834
  ]
594
835
  },
595
- "[\"Webhook Event\",[[\"action\",\"record.created\"]]]": {
836
+ "People List": {
837
+ "properties": {},
838
+ "edges": {},
839
+ "undescribed": true
840
+ },
841
+ "CompanyChange": {
596
842
  "properties": {
597
- "action": {
843
+ "Id": "text",
844
+ "Kind": {
845
+ "kind": "enum",
846
+ "options": [
847
+ "created",
848
+ "updated",
849
+ "deleted"
850
+ ]
851
+ },
852
+ "Field": {
598
853
  "kind": "enum",
599
854
  "options": [
600
- "record.created",
601
- "record.updated",
602
- "record.deleted"
855
+ "Name",
856
+ "Domains",
857
+ "Description",
858
+ "Categories",
859
+ "Team Size",
860
+ "Team"
603
861
  ]
604
862
  },
605
- "Workspace": "text",
606
- "Object": "text",
607
- "Record": "text",
608
- "List": "text",
609
- "Entry": "text",
610
- "Task": "text",
611
- "Note": "text",
612
- "Comment": "text",
613
- "Attribute": "text"
863
+ "Record": "text"
614
864
  },
615
865
  "edges": {
616
- "Companies": {
866
+ "Company": {
617
867
  "target": "Companies",
618
- "requiresLiveRecord": true
619
- },
620
- "People": {
621
- "target": "People",
622
- "requiresLiveRecord": true
623
- },
624
- "Deals": {
625
- "target": "Deals",
626
- "requiresLiveRecord": true
627
- },
628
- "Funds": {
629
- "target": "Funds",
630
- "requiresLiveRecord": true
631
- },
632
- "List": {
633
- "target": "List"
634
- },
635
- "Pipeline": {
636
- "target": "Pipeline",
637
- "requiresLiveRecord": true
638
- },
639
- "VC Deal Flow": {
640
- "target": "VC Deal Flow",
641
- "requiresLiveRecord": true
642
- },
643
- "Task": {
644
- "target": "Task"
645
- },
646
- "Note": {
647
- "target": "Note"
648
- },
649
- "Comment": {
650
- "target": "Comment"
868
+ "requiresLiveRecord": true,
869
+ "description": "The company that changed, read from Attio."
651
870
  }
652
871
  },
653
- "displayName": "Webhook Event where action=record.created"
654
- },
655
- "People List": {
656
- "properties": {},
657
- "edges": {},
658
- "undescribed": true
872
+ "description": "A change to a company in Attio: what happened (`Kind`) and, on an update, which attribute (`Field`). Walk `Company` for the company as it is now.",
873
+ "fieldDocs": {
874
+ "Id": "Names the change: the same on every redelivery of it.",
875
+ "Kind": "What happened to the company.",
876
+ "Field": "On an update, the attribute that changed, by its title (`Stage`).",
877
+ "Record": "The company's record id."
878
+ }
659
879
  },
660
- "[\"Webhook Event\",[[\"action\",\"record.updated\"]]]": {
880
+ "PersonChange": {
661
881
  "properties": {
662
- "action": {
882
+ "Id": "text",
883
+ "Kind": {
663
884
  "kind": "enum",
664
885
  "options": [
665
- "record.created",
666
- "record.updated",
667
- "record.deleted"
886
+ "created",
887
+ "updated",
888
+ "deleted"
668
889
  ]
669
890
  },
670
- "Workspace": "text",
671
- "Object": "text",
672
- "Record": "text",
673
- "List": "text",
674
- "Entry": "text",
675
- "Task": "text",
676
- "Note": "text",
677
- "Comment": "text",
678
- "Attribute": "text"
891
+ "Field": {
892
+ "kind": "enum",
893
+ "options": [
894
+ "Name",
895
+ "Email",
896
+ "Job Title",
897
+ "Company"
898
+ ]
899
+ },
900
+ "Record": "text"
679
901
  },
680
902
  "edges": {
681
- "Companies": {
682
- "target": "Companies",
683
- "requiresLiveRecord": true
684
- },
685
- "People": {
903
+ "Person": {
686
904
  "target": "People",
687
- "requiresLiveRecord": true
688
- },
689
- "Deals": {
690
- "target": "Deals",
691
- "requiresLiveRecord": true
692
- },
693
- "Funds": {
694
- "target": "Funds",
695
- "requiresLiveRecord": true
696
- },
697
- "List": {
698
- "target": "List"
699
- },
700
- "Pipeline": {
701
- "target": "Pipeline",
702
- "requiresLiveRecord": true
703
- },
704
- "VC Deal Flow": {
705
- "target": "VC Deal Flow",
706
- "requiresLiveRecord": true
707
- },
708
- "Task": {
709
- "target": "Task"
710
- },
711
- "Note": {
712
- "target": "Note"
713
- },
714
- "Comment": {
715
- "target": "Comment"
905
+ "requiresLiveRecord": true,
906
+ "description": "The person that changed, read from Attio."
716
907
  }
717
908
  },
718
- "displayName": "Webhook Event where action=record.updated"
909
+ "description": "A change to a person in Attio: what happened (`Kind`) and, on an update, which attribute (`Field`). Walk `Person` for the person as it is now.",
910
+ "fieldDocs": {
911
+ "Id": "Names the change: the same on every redelivery of it.",
912
+ "Kind": "What happened to the person.",
913
+ "Field": "On an update, the attribute that changed, by its title (`Stage`).",
914
+ "Record": "The person's record id."
915
+ }
719
916
  },
720
- "[\"Webhook Event\",[[\"action\",\"record.deleted\"]]]": {
917
+ "DealChange": {
721
918
  "properties": {
722
- "action": {
919
+ "Id": "text",
920
+ "Kind": {
723
921
  "kind": "enum",
724
922
  "options": [
725
- "record.created",
726
- "record.updated",
727
- "record.deleted"
923
+ "created",
924
+ "updated",
925
+ "deleted"
728
926
  ]
729
927
  },
730
- "Workspace": "text",
731
- "Object": "text",
732
- "Record": "text",
733
- "List": "text",
734
- "Entry": "text",
735
- "Task": "text",
736
- "Note": "text",
737
- "Comment": "text",
738
- "Attribute": "text"
928
+ "Field": {
929
+ "kind": "enum",
930
+ "options": [
931
+ "Name",
932
+ "Stage",
933
+ "Value",
934
+ "Company"
935
+ ]
936
+ },
937
+ "Record": "text"
739
938
  },
740
939
  "edges": {
741
- "List": {
742
- "target": "List"
743
- },
744
- "Task": {
745
- "target": "Task"
746
- },
747
- "Note": {
748
- "target": "Note"
749
- },
750
- "Comment": {
751
- "target": "Comment"
940
+ "Deal": {
941
+ "target": "Deals",
942
+ "requiresLiveRecord": true,
943
+ "description": "The deal that changed, read from Attio."
752
944
  }
753
945
  },
754
- "displayName": "Webhook Event where action=record.deleted"
755
- }
756
- },
757
- "collections": {
758
- "Companies": {
759
- "target": "Companies",
760
- "capability": {
761
- "filter": "native",
762
- "order": "bounded",
763
- "supportsLimit": true
946
+ "description": "A change to a deal in Attio: what happened (`Kind`) and, on an update, which attribute (`Field`). Walk `Deal` for the deal as it is now.",
947
+ "fieldDocs": {
948
+ "Id": "Names the change: the same on every redelivery of it.",
949
+ "Kind": "What happened to the deal.",
950
+ "Field": "On an update, the attribute that changed, by its title (`Stage`).",
951
+ "Record": "The deal's record id."
764
952
  }
765
953
  },
766
- "People": {
767
- "target": "People",
768
- "capability": {
954
+ "FundChange": {
955
+ "properties": {
956
+ "Id": "text",
957
+ "Kind": {
958
+ "kind": "enum",
959
+ "options": [
960
+ "created",
961
+ "updated",
962
+ "deleted"
963
+ ]
964
+ },
965
+ "Field": {
966
+ "kind": "enum",
967
+ "options": [
968
+ "Name",
969
+ "Vintage",
970
+ "GPs",
971
+ "Advisors"
972
+ ]
973
+ },
974
+ "Record": "text"
975
+ },
976
+ "edges": {
977
+ "Fund": {
978
+ "target": "Funds",
979
+ "requiresLiveRecord": true,
980
+ "description": "The fund that changed, read from Attio."
981
+ }
982
+ },
983
+ "description": "A change to a fund in Attio: what happened (`Kind`) and, on an update, which attribute (`Field`). Walk `Fund` for the fund as it is now.",
984
+ "fieldDocs": {
985
+ "Id": "Names the change: the same on every redelivery of it.",
986
+ "Kind": "What happened to the fund.",
987
+ "Field": "On an update, the attribute that changed, by its title (`Stage`).",
988
+ "Record": "The fund's record id."
989
+ }
990
+ }
991
+ },
992
+ "collections": {
993
+ "Companies": {
994
+ "target": "Companies",
995
+ "capability": {
996
+ "filter": "native",
997
+ "order": "bounded",
998
+ "supportsLimit": true
999
+ }
1000
+ },
1001
+ "People": {
1002
+ "target": "People",
1003
+ "capability": {
769
1004
  "filter": "native",
770
1005
  "order": "bounded",
771
1006
  "supportsLimit": true
@@ -794,13 +1029,6 @@
794
1029
  "target": "VC Deal Flow"
795
1030
  }
796
1031
  },
797
- "unions": {
798
- "Webhook Event": [
799
- "[\"Webhook Event\",[[\"action\",\"record.created\"]]]",
800
- "[\"Webhook Event\",[[\"action\",\"record.updated\"]]]",
801
- "[\"Webhook Event\",[[\"action\",\"record.deleted\"]]]"
802
- ]
803
- },
804
1032
  "writableRoots": {
805
1033
  "Companies": {
806
1034
  "fields": {
@@ -1146,22 +1374,19 @@
1146
1374
  }
1147
1375
  }
1148
1376
  },
1149
- "supportsInPlaceUpdate": true,
1150
- "eventPosition": "Webhook Event",
1151
- "eventPositions": [
1152
- {
1153
- "position": "Webhook Event"
1154
- }
1155
- ]
1377
+ "supportsInPlaceUpdate": true
1156
1378
  },
1157
1379
  "slack": {
1158
1380
  "positions": {
1159
1381
  "Message": {
1160
1382
  "properties": {
1383
+ "Id": "text",
1161
1384
  "Message": "text",
1162
1385
  "User": "text",
1163
1386
  "Channel": "text",
1164
- "Timestamp": "date"
1387
+ "Timestamp": "date",
1388
+ "Topic": "text",
1389
+ "Mentions App": "boolean"
1165
1390
  },
1166
1391
  "edges": {
1167
1392
  "Files": {
@@ -1172,8 +1397,43 @@
1172
1397
  "target": "Message",
1173
1398
  "sequenced": "chronological",
1174
1399
  "writable": true,
1175
- "awaitable": true,
1176
- "watchable": true
1400
+ "listenable": true,
1401
+ "cursor": false
1402
+ },
1403
+ "Interactions": {
1404
+ "target": "Interaction",
1405
+ "readable": false,
1406
+ "listenable": true,
1407
+ "cursor": false,
1408
+ "identity": "Id",
1409
+ "delivery": "connection",
1410
+ "carries": [
1411
+ "Channels",
1412
+ "Messages"
1413
+ ],
1414
+ "literalsFrom": [
1415
+ {
1416
+ "at": [
1417
+ "Actions",
1418
+ "Value"
1419
+ ],
1420
+ "from": "Buttons[].Value"
1421
+ }
1422
+ ],
1423
+ "description": "A press on one of this message's Buttons (or a control in its Blocks): `await msg-[:Interactions]->` waits for the next one, and `listen to chat-[:Channels]->-[m:Messages WHERE m.Topic == \"lunch-poll\"]->-[:Interactions]->` runs once per press on any message written with that Topic."
1424
+ },
1425
+ "Reactions": {
1426
+ "target": "Reaction",
1427
+ "readable": false,
1428
+ "writable": true,
1429
+ "listenable": true,
1430
+ "cursor": false,
1431
+ "delivery": "connection",
1432
+ "carries": [
1433
+ "Channels"
1434
+ ],
1435
+ "attributes": true,
1436
+ "description": "React to this message with an emoji, or hear a reaction added: `listen to chat-[ch:Channels WHERE ch.Name == \"deals\"]->-[:Messages]->-[:Reactions]->`. A reaction arrives with its channel, not its message, so a listen narrows it by channel; reading a message's reactions back is not offered (a call per message)."
1177
1437
  },
1178
1438
  "Author": {
1179
1439
  "target": "User"
@@ -1196,13 +1456,95 @@
1196
1456
  },
1197
1457
  "writeOnlyProperties": [
1198
1458
  "File",
1199
- "Blocks"
1200
- ]
1459
+ "Blocks",
1460
+ "Buttons"
1461
+ ],
1462
+ "description": "A Slack message — what a listen on a channel's `Messages` delivers, whole: read `m.Message` straight off it, reply along its `Replies` edge, react to it along its `Reactions` edge. Created only along an edge: post via a Channel's `Messages` edge, reply via a message's `Replies` edge — the channel and thread come from the parent, never from fields.",
1463
+ "fieldDocs": {
1464
+ "Id": "Names this message in its workspace: its channel's id and its Slack timestamp (`C…:1700000000.000100`).",
1465
+ "Message": "The message body. Required unless a File is attached or Blocks are set (at least one of Message / File / Blocks). Use Slack-native formatting (*bold*, _italic_, <url|text>).",
1466
+ "File": "Optional file to upload with the message — the Message text rides as the file caption, one visible Slack message. Send-side only: a received message's files are on its Files edge — read `msg-[:Files]->.File`. A message is one of File, Blocks or Buttons.",
1467
+ "Blocks": "Slack Block Kit blocks, exactly as in Slack's docs — assembled verbatim and passed through (no wrapping, no typed shape here; a malformed block surfaces as a loud Slack error at run time). For plain buttons write Buttons instead. A press on any control in the blocks arrives on the message's Interactions, its value in the Action's Value: a button's or option's `value`, a picker's pick. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). A message is one of File, Blocks or Buttons.",
1468
+ "Buttons": "Buttons under the Message text, each `{ Text: \"Approve\", Value: \"approve\" }`: Text is the label, Value what a press answers with. Wait for a press with `await msg-[:Interactions]->`; `only(press-[:Actions]->).Value` is one of the Values written. Needs Message text; never with File or Blocks.",
1469
+ "Channel": "The C… channel id (read-only — a post is written along a Channel's `Messages` edge).",
1470
+ "Timestamp": "When the message was posted.",
1471
+ "Topic": "A marker of your own for a message with Buttons (`Topic: \"lunch-poll\"`), so a listen can tell its presses from other messages' presses: `listen to chat-[:Channels]->-[m:Messages WHERE m.Topic == \"lunch-poll\"]->-[:Interactions]->`. Only a message with Buttons takes one: Slack keeps no other data on a message that comes back with a press, so the Topic is stored as the id of the block its buttons sit in (`lf:<topic>`), and read back from there — on the pressed message and on a listed one alike. It is written only with Buttons, and block ids starting `lf:` are reserved for it: a message whose own Blocks use one is refused.",
1472
+ "Mentions App": "Whether the message @-mentions the connected app: `listen to chat-[:Channels]->-[m:Messages WHERE m.`Mentions App` == true]->` runs when somebody asks it something."
1473
+ },
1474
+ "fieldIds": {
1475
+ "Id": "id",
1476
+ "Message": "text",
1477
+ "User": "user",
1478
+ "Channel": "channel",
1479
+ "Timestamp": "timestamp",
1480
+ "Topic": "topic",
1481
+ "Mentions App": "mentions_app"
1482
+ }
1201
1483
  },
1202
1484
  "File": {
1203
- "properties": {},
1485
+ "properties": {
1486
+ "File Id": "text",
1487
+ "Name": "text",
1488
+ "Content Type": "text",
1489
+ "URL": "text",
1490
+ "Size": "number",
1491
+ "File": "file"
1492
+ },
1204
1493
  "edges": {},
1205
- "undescribed": true
1494
+ "fieldIds": {
1495
+ "File Id": "id",
1496
+ "Name": "name",
1497
+ "Content Type": "contentType",
1498
+ "URL": "url",
1499
+ "Size": "size",
1500
+ "File": "data"
1501
+ }
1502
+ },
1503
+ "Interaction": {
1504
+ "properties": {
1505
+ "Id": "text",
1506
+ "At": "date"
1507
+ },
1508
+ "edges": {
1509
+ "Actions": {
1510
+ "target": "Action",
1511
+ "sequenced": "document",
1512
+ "description": "The controls used — one for a button press."
1513
+ },
1514
+ "By": {
1515
+ "target": "User",
1516
+ "description": "The member who pressed."
1517
+ },
1518
+ "Message": {
1519
+ "target": "Message",
1520
+ "description": "The message pressed, as Slack sent it with the press: its text, its `Timestamp`, its `Topic`."
1521
+ }
1522
+ },
1523
+ "description": "Someone pressing a button (or using another control) on a message — what `await msg-[:Interactions]->` binds, and what a listen on a message's `Interactions` runs on. `Actions` are the controls used, `By` the member who pressed, `Message` the message pressed.",
1524
+ "fieldDocs": {
1525
+ "Id": "Names this press (Slack's trigger id).",
1526
+ "At": "When the press was made."
1527
+ },
1528
+ "fieldIds": {
1529
+ "Id": "id",
1530
+ "At": "pressed_at"
1531
+ }
1532
+ },
1533
+ "Action": {
1534
+ "properties": {
1535
+ "Value": "text",
1536
+ "Text": "text"
1537
+ },
1538
+ "edges": {},
1539
+ "description": "One control used in a press: the value it answered with, and its label.",
1540
+ "fieldDocs": {
1541
+ "Value": "What the control answered: a button's Value (one of the Buttons written), a select's chosen option value, a picker's date or time.",
1542
+ "Text": "The label of the button, or of the option chosen."
1543
+ },
1544
+ "fieldIds": {
1545
+ "Value": "value",
1546
+ "Text": "text"
1547
+ }
1206
1548
  },
1207
1549
  "Reaction": {
1208
1550
  "properties": {
@@ -1220,6 +1562,17 @@
1220
1562
  "Reactor": {
1221
1563
  "target": "User"
1222
1564
  }
1565
+ },
1566
+ "description": "An emoji reaction added to a message: what a listen on a message's `Reactions` runs on. Walk `Message` to the message it's on, `Channel` to the channel, `Reactor` to the user. Also the reaction WRITE: created along a message's `Reactions` edge, which is how a movement reacts to the message it was handed.",
1567
+ "fieldDocs": {
1568
+ "Emoji": "The reaction emoji name (e.g. \"thumbsup\"), without colons. On a write along `msg-[:Reactions]->`: the single emoji to react with — required.",
1569
+ "Channel": "The C… channel id the reaction is in (read-only — walk the `Channel` edge for the Channel node).",
1570
+ "Reactor": "The U… id of the user who added the reaction (read-only — walk the `Reactor` edge for the User node)."
1571
+ },
1572
+ "fieldIds": {
1573
+ "Emoji": "emoji",
1574
+ "Channel": "channel",
1575
+ "Reactor": "reactor"
1223
1576
  }
1224
1577
  },
1225
1578
  "Channel": {
@@ -1243,20 +1596,51 @@
1243
1596
  "capability": {
1244
1597
  "filter": "bounded",
1245
1598
  "order": "bounded",
1246
- "supportsLimit": true
1599
+ "supportsLimit": true,
1600
+ "naturalOrder": {
1601
+ "field": "Timestamp",
1602
+ "direction": "desc"
1603
+ }
1247
1604
  },
1248
1605
  "sequenced": "chronological",
1249
- "writable": true
1606
+ "writable": true,
1607
+ "listenable": true,
1608
+ "cursor": false,
1609
+ "delivery": "connection",
1610
+ "carries": [
1611
+ "Channels"
1612
+ ]
1250
1613
  },
1251
1614
  "Members": {
1252
1615
  "target": "User"
1253
1616
  }
1617
+ },
1618
+ "description": "A Slack channel the connected bot can see. Select one with WHERE on `Name`, then walk its `Messages` (history the bot can read) or `Members`. A WHERE on a message's `Timestamp` narrows the history at Slack rather than after the fetch. Listen on its `Messages` for each new message (`listen to chat-[ch:Channels WHERE ch.Name == \"deals\"]->-[:Messages]->`): each arrives with its channel, so the channel's `Name` is judged on the message itself.",
1619
+ "fieldDocs": {
1620
+ "Name": "This workspace has 3 channels — every one of them is offered as a value for this field, so use the field's own values rather than guessing or assuming a channel is missing."
1621
+ },
1622
+ "fieldIds": {
1623
+ "Id": "id",
1624
+ "Name": "name",
1625
+ "Topic": "topic",
1626
+ "Is Private": "is_private"
1254
1627
  }
1255
1628
  },
1256
1629
  "User": {
1257
- "properties": {},
1630
+ "properties": {
1631
+ "Id": "text",
1632
+ "Name": "text",
1633
+ "Real Name": "text",
1634
+ "Email": "text"
1635
+ },
1258
1636
  "edges": {},
1259
- "undescribed": true
1637
+ "description": "A Slack workspace member.",
1638
+ "fieldIds": {
1639
+ "Id": "id",
1640
+ "Name": "name",
1641
+ "Real Name": "real_name",
1642
+ "Email": "email"
1643
+ }
1260
1644
  }
1261
1645
  },
1262
1646
  "collections": {
@@ -1276,19 +1660,31 @@
1276
1660
  "Blocks": {
1277
1661
  "kind": "list",
1278
1662
  "of": "json"
1279
- }
1663
+ },
1664
+ "Buttons": {
1665
+ "kind": "list",
1666
+ "of": "json"
1667
+ },
1668
+ "Topic": "text"
1280
1669
  },
1281
1670
  "resultShape": {
1282
1671
  "externalId": "text",
1283
1672
  "url": "text",
1673
+ "Id": "text",
1284
1674
  "Message": "text",
1285
1675
  "User": "text",
1286
1676
  "Channel": "text",
1287
1677
  "Timestamp": "date",
1678
+ "Topic": "text",
1679
+ "Mentions App": "boolean",
1288
1680
  "File": "file",
1289
1681
  "Blocks": {
1290
1682
  "kind": "list",
1291
1683
  "of": "json"
1684
+ },
1685
+ "Buttons": {
1686
+ "kind": "list",
1687
+ "of": "json"
1292
1688
  }
1293
1689
  },
1294
1690
  "edges": {
@@ -1300,8 +1696,43 @@
1300
1696
  "target": "Message",
1301
1697
  "sequenced": "chronological",
1302
1698
  "writable": true,
1303
- "awaitable": true,
1304
- "watchable": true
1699
+ "listenable": true,
1700
+ "cursor": false
1701
+ },
1702
+ "Interactions": {
1703
+ "target": "Interaction",
1704
+ "readable": false,
1705
+ "listenable": true,
1706
+ "cursor": false,
1707
+ "identity": "Id",
1708
+ "delivery": "connection",
1709
+ "carries": [
1710
+ "Channels",
1711
+ "Messages"
1712
+ ],
1713
+ "literalsFrom": [
1714
+ {
1715
+ "at": [
1716
+ "Actions",
1717
+ "Value"
1718
+ ],
1719
+ "from": "Buttons[].Value"
1720
+ }
1721
+ ],
1722
+ "description": "A press on one of this message's Buttons (or a control in its Blocks): `await msg-[:Interactions]->` waits for the next one, and `listen to chat-[:Channels]->-[m:Messages WHERE m.Topic == \"lunch-poll\"]->-[:Interactions]->` runs once per press on any message written with that Topic."
1723
+ },
1724
+ "Reactions": {
1725
+ "target": "Reaction",
1726
+ "readable": false,
1727
+ "writable": true,
1728
+ "listenable": true,
1729
+ "cursor": false,
1730
+ "delivery": "connection",
1731
+ "carries": [
1732
+ "Channels"
1733
+ ],
1734
+ "attributes": true,
1735
+ "description": "React to this message with an emoji, or hear a reaction added: `listen to chat-[ch:Channels WHERE ch.Name == \"deals\"]->-[:Messages]->-[:Reactions]->`. A reaction arrives with its channel, not its message, so a listen narrows it by channel; reading a message's reactions back is not offered (a call per message)."
1305
1736
  },
1306
1737
  "Author": {
1307
1738
  "target": "User"
@@ -1312,8 +1743,10 @@
1312
1743
  },
1313
1744
  "fieldDocs": {
1314
1745
  "Message": "The message body. Required unless a File is attached or Blocks are set (at least one of Message / File / Blocks). Use Slack-native formatting (*bold*, _italic_, <url|text>).",
1315
- "File": "Optional file to upload with the message — the Message text rides as the file caption, one visible Slack message. Send-side only: a received message's files are on its Files edge — read `msg-[:Files]->.File`. A message is either a File or Blocks, never both.",
1316
- "Blocks": "Slack Block Kit blocks, exactly as in Slack's docs — assembled verbatim and passed through (no wrapping, no typed shape here; a malformed block surfaces as a loud Slack error at run time). To make a control DO something, mint a callback (`c = callback({ … })`) and put `\"${c.id}\"` in a button's or select option's `value` — one callback per control, since its body is what that control means. A `datepicker`/`timepicker`/text input has no `value` field and no value until the tap, so carry the id on the element's `action_id` instead — e.g. `action_id: \"${c.id}\"` — and the picked value binds to the callback's first parameter. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). Example, a section plus an actions block with a button and a static_select:\n[\n { type: \"section\", text: { type: \"mrkdwn\", text: \"*${deal.Name}*\" } },\n { type: \"actions\", elements: [\n { type: \"button\", text: { type: \"plain_text\", text: \"Approve\" },\n value: \"${approve.id}\" },\n { type: \"static_select\",\n placeholder: { type: \"plain_text\", text: \"Tier\" },\n options: [\n { text: { type: \"plain_text\", text: \"Seed\" }, value: \"${seed.id}\" },\n ] },\n ] },\n]\nA message is either a File or Blocks, never both."
1746
+ "File": "Optional file to upload with the message — the Message text rides as the file caption, one visible Slack message. Send-side only: a received message's files are on its Files edge — read `msg-[:Files]->.File`. A message is one of File, Blocks or Buttons.",
1747
+ "Blocks": "Slack Block Kit blocks, exactly as in Slack's docs — assembled verbatim and passed through (no wrapping, no typed shape here; a malformed block surfaces as a loud Slack error at run time). For plain buttons write Buttons instead. A press on any control in the blocks arrives on the message's Interactions, its value in the Action's Value: a button's or option's `value`, a picker's pick. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). A message is one of File, Blocks or Buttons.",
1748
+ "Buttons": "Buttons under the Message text, each `{ Text: \"Approve\", Value: \"approve\" }`: Text is the label, Value what a press answers with. Wait for a press with `await msg-[:Interactions]->`; `only(press-[:Actions]->).Value` is one of the Values written. Needs Message text; never with File or Blocks.",
1749
+ "Topic": "A marker of your own for a message with Buttons (`Topic: \"lunch-poll\"`), so a listen can tell its presses from other messages' presses: `listen to chat-[:Channels]->-[m:Messages WHERE m.Topic == \"lunch-poll\"]->-[:Interactions]->`. Only a message with Buttons takes one: Slack keeps no other data on a message that comes back with a press, so the Topic is stored as the id of the block its buttons sit in (`lf:<topic>`), and read back from there — on the pressed message and on a listed one alike. It is written only with Buttons, and block ids starting `lf:` are reserved for it: a message whose own Blocks use one is refused."
1317
1750
  },
1318
1751
  "writeUnion": {
1319
1752
  "variants": [
@@ -1330,27 +1763,49 @@
1330
1763
  "Message",
1331
1764
  "Blocks"
1332
1765
  ]
1766
+ },
1767
+ {
1768
+ "name": "a post with buttons",
1769
+ "fields": [
1770
+ "Message",
1771
+ "Buttons",
1772
+ "Topic"
1773
+ ]
1333
1774
  }
1334
1775
  ]
1335
1776
  }
1336
- }
1337
- },
1338
- "eventPosition": "Message",
1339
- "eventPositions": [
1340
- {
1341
- "position": "Message",
1342
- "on": [
1343
- "message",
1344
- "app_mention"
1345
- ]
1346
1777
  },
1347
- {
1348
- "position": "Reaction",
1349
- "on": [
1350
- "reaction_added"
1351
- ]
1778
+ "Reaction": {
1779
+ "fields": {
1780
+ "Emoji": "text"
1781
+ },
1782
+ "resultShape": {
1783
+ "externalId": "text",
1784
+ "url": "text",
1785
+ "Emoji": "text",
1786
+ "Channel": "text",
1787
+ "Reactor": "text"
1788
+ },
1789
+ "edges": {
1790
+ "Message": {
1791
+ "target": "Message"
1792
+ },
1793
+ "Channel": {
1794
+ "target": "Channel"
1795
+ },
1796
+ "Reactor": {
1797
+ "target": "User"
1798
+ }
1799
+ },
1800
+ "requiredFields": [
1801
+ "Emoji"
1802
+ ],
1803
+ "fieldDocs": {
1804
+ "Emoji": "The reaction emoji name (e.g. \"thumbsup\"), without colons. On a write along `msg-[:Reactions]->`: the single emoji to react with — required."
1805
+ }
1352
1806
  }
1353
- ]
1807
+ },
1808
+ "supportsInPlaceUpdate": true
1354
1809
  },
1355
1810
  "manual": {
1356
1811
  "positions": {
@@ -1378,14 +1833,15 @@
1378
1833
  "edges": {}
1379
1834
  }
1380
1835
  },
1381
- "collections": {},
1382
- "writableRoots": {},
1383
- "eventPosition": "Invocation",
1384
- "eventPositions": [
1385
- {
1386
- "position": "Invocation"
1836
+ "collections": {
1837
+ "Invocations": {
1838
+ "target": "Invocation",
1839
+ "readable": false,
1840
+ "listenable": true,
1841
+ "delivery": "connection"
1387
1842
  }
1388
- ]
1843
+ },
1844
+ "writableRoots": {}
1389
1845
  },
1390
1846
  "ask": {
1391
1847
  "positions": {
@@ -1539,9 +1995,7 @@
1539
1995
  "target": "Check Response",
1540
1996
  "sequenced": "arrival",
1541
1997
  "writable": true,
1542
- "awaitable": true,
1543
- "resolvesEmpty": true,
1544
- "watchable": true
1998
+ "listenable": true
1545
1999
  }
1546
2000
  },
1547
2001
  "requiredFields": [
@@ -1598,9 +2052,7 @@
1598
2052
  "target": "Provide Response",
1599
2053
  "sequenced": "arrival",
1600
2054
  "writable": true,
1601
- "awaitable": true,
1602
- "resolvesEmpty": true,
1603
- "watchable": true,
2055
+ "listenable": true,
1604
2056
  "genericOver": {
1605
2057
  "field": "Answer Type",
1606
2058
  "onNonLiteral": "error"
@@ -1653,9 +2105,7 @@
1653
2105
  "target": "Choose Response",
1654
2106
  "sequenced": "arrival",
1655
2107
  "writable": true,
1656
- "awaitable": true,
1657
- "resolvesEmpty": true,
1658
- "watchable": true,
2108
+ "listenable": true,
1659
2109
  "genericOver": {
1660
2110
  "field": "Options"
1661
2111
  }
@@ -1707,9 +2157,7 @@
1707
2157
  "target": "Select Response",
1708
2158
  "sequenced": "arrival",
1709
2159
  "writable": true,
1710
- "awaitable": true,
1711
- "resolvesEmpty": true,
1712
- "watchable": true,
2160
+ "listenable": true,
1713
2161
  "genericOver": {
1714
2162
  "field": "Options"
1715
2163
  }
@@ -1753,9 +2201,7 @@
1753
2201
  "target": "Review Response",
1754
2202
  "sequenced": "arrival",
1755
2203
  "writable": true,
1756
- "awaitable": true,
1757
- "resolvesEmpty": true,
1758
- "watchable": true
2204
+ "listenable": true
1759
2205
  }
1760
2206
  },
1761
2207
  "requiredFields": [
@@ -1794,9 +2240,7 @@
1794
2240
  "target": "Draft Response",
1795
2241
  "sequenced": "arrival",
1796
2242
  "writable": true,
1797
- "awaitable": true,
1798
- "resolvesEmpty": true,
1799
- "watchable": true
2243
+ "listenable": true
1800
2244
  }
1801
2245
  },
1802
2246
  "requiredFields": [
@@ -1843,9 +2287,7 @@
1843
2287
  "target": "Correct Response",
1844
2288
  "sequenced": "arrival",
1845
2289
  "writable": true,
1846
- "awaitable": true,
1847
- "resolvesEmpty": true,
1848
- "watchable": true
2290
+ "listenable": true
1849
2291
  }
1850
2292
  },
1851
2293
  "requiredFields": [
@@ -1894,9 +2336,7 @@
1894
2336
  "target": "Form Response",
1895
2337
  "sequenced": "arrival",
1896
2338
  "writable": true,
1897
- "awaitable": true,
1898
- "resolvesEmpty": true,
1899
- "watchable": true,
2339
+ "listenable": true,
1900
2340
  "genericOver": {
1901
2341
  "field": "Fields",
1902
2342
  "onNonLiteral": "warn"
@@ -2162,24 +2602,59 @@
2162
2602
  "Tick": {
2163
2603
  "properties": {
2164
2604
  "Fired at": "date",
2165
- "Schedule": "text"
2605
+ "Schedule": {
2606
+ "kind": "owned",
2607
+ "name": "Cron",
2608
+ "owner": "cron"
2609
+ },
2610
+ "Zone": {
2611
+ "kind": "owned",
2612
+ "name": "Zone",
2613
+ "owner": "cron"
2614
+ }
2166
2615
  },
2167
- "edges": {}
2616
+ "edges": {},
2617
+ "propertyCapabilities": {
2618
+ "Schedule": {
2619
+ "filterOperators": [
2620
+ "eq",
2621
+ "in"
2622
+ ]
2623
+ },
2624
+ "Zone": {
2625
+ "filterOperators": [
2626
+ "eq",
2627
+ "in"
2628
+ ]
2629
+ }
2630
+ }
2168
2631
  }
2169
2632
  },
2170
- "collections": {},
2171
- "writableRoots": {},
2172
- "eventPosition": "Tick",
2173
- "eventPositions": [
2174
- {
2175
- "position": "Tick"
2633
+ "collections": {
2634
+ "Ticks": {
2635
+ "target": "Tick",
2636
+ "capability": {
2637
+ "filter": "native",
2638
+ "supportsLimit": false
2639
+ },
2640
+ "readable": false,
2641
+ "listenable": true,
2642
+ "cursor": true,
2643
+ "bounded": false,
2644
+ "boundedBy": [
2645
+ "Schedule"
2646
+ ],
2647
+ "delivery": "polled",
2648
+ "pollEverySeconds": 60
2176
2649
  }
2177
- ]
2650
+ },
2651
+ "writableRoots": {}
2178
2652
  },
2179
2653
  "telegram": {
2180
2654
  "positions": {
2181
2655
  "Message": {
2182
2656
  "properties": {
2657
+ "Id": "text",
2183
2658
  "Message Id": "text",
2184
2659
  "Text": "text",
2185
2660
  "Chat Id": "text",
@@ -2191,33 +2666,103 @@
2191
2666
  },
2192
2667
  "edges": {
2193
2668
  "Attachments": {
2194
- "target": "Attachment"
2669
+ "target": "Attachment",
2670
+ "description": "Files attached to the message — documents, photos, voice notes, audio (zero or more)."
2195
2671
  },
2196
2672
  "Replies": {
2197
2673
  "target": "Message",
2198
2674
  "readable": false,
2199
- "writable": true
2675
+ "writable": true,
2676
+ "description": "Send a reply threaded under this message (write-only — the Bot API cannot list replies). The chat and the reply target come from this message."
2200
2677
  },
2201
2678
  "Sender": {
2202
- "target": "Linked User"
2679
+ "target": "Linked User",
2680
+ "description": "The linked user who sent this message — resolves only for a sender who completed the connect handshake (private chats); a group chat or an unlinked sender resolves to nothing."
2203
2681
  }
2204
2682
  },
2205
2683
  "writeOnlyProperties": [
2206
2684
  "Reply Markup"
2207
- ]
2685
+ ],
2686
+ "description": "A Telegram message — what a listen on `Messages` delivers, whole: read `e.`Text`` straight off the arrival, reply along its `Replies` edge. Created only along an edge: DM a linked user via their `Messages` edge, reply via a message's `Replies` edge — the chat and the reply target come from the parent, never from fields.",
2687
+ "fieldDocs": {
2688
+ "Id": "Names an arriving message across chats: `<Chat Id>:<Message Id>`, since Telegram numbers a message within its chat. The same on every redelivery.",
2689
+ "Message Id": "The Telegram message id, within its chat.",
2690
+ "Text": "The message text. On an inbound message: the received text (or the media caption; empty for caption-less media). On a send: the message body — required.",
2691
+ "Reply Markup": "Telegram's `reply_markup`, exactly as the Bot API spells it — passed through verbatim (no wrapping, no typed shape here; a malformed keyboard surfaces as a loud Telegram error at run time). Usually an inline keyboard: rows of buttons, each `{ text, url }` (opens a link). To make a tap DO something, write a form from the `@lf/forms` adapter (`form = write forms-[:Forms]-> { Title: …, Fields: … }`), put its `form.Url` on a `url` button, and `await form-[:Submissions]->`: the form also asks for anything a person has to fill in, which Telegram does not capture at tap time. A `{ text, callback_data }` button reaches no run. Buttons compose with everything else on the send. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). Example:\n{ inline_keyboard: [\n [ { text: \"Answer\", url: \"${form.Url}\" } ],\n] }",
2692
+ "Chat Id": "The id of the chat the message belongs to — the value to send a reply back to, and what a listen narrows to one chat by (`m.`Chat Id` == \"42\"`).",
2693
+ "Chat Type": "private / group / supergroup / channel.",
2694
+ "Sender Id": "The numeric id of the user who sent the message.",
2695
+ "Sender Username": "The @username of the sender, when set.",
2696
+ "Sender First Name": "The sender's first name.",
2697
+ "Date": "When the message was sent (ISO-8601)."
2698
+ },
2699
+ "fieldIds": {
2700
+ "Id": "id",
2701
+ "Message Id": "message_id",
2702
+ "Text": "text",
2703
+ "Chat Id": "chat_id",
2704
+ "Chat Type": "chat_type",
2705
+ "Sender Id": "sender_id",
2706
+ "Sender Username": "sender_username",
2707
+ "Sender First Name": "sender_first_name",
2708
+ "Date": "date"
2709
+ }
2208
2710
  },
2209
2711
  "Attachment": {
2210
- "properties": {},
2712
+ "properties": {
2713
+ "File Id": "text",
2714
+ "Name": "text",
2715
+ "Content Type": "text",
2716
+ "Size": "number",
2717
+ "File": "file"
2718
+ },
2211
2719
  "edges": {},
2212
- "undescribed": true
2720
+ "fieldDocs": {
2721
+ "File Id": "The Bot API file id — resolves to the file bytes."
2722
+ },
2723
+ "fieldIds": {
2724
+ "File Id": "file_id",
2725
+ "Name": "name",
2726
+ "Content Type": "contentType",
2727
+ "Size": "size",
2728
+ "File": "data"
2729
+ }
2213
2730
  },
2214
2731
  "Linked User": {
2215
- "properties": {},
2216
- "edges": {},
2217
- "undescribed": true
2732
+ "properties": {
2733
+ "Chat Id": "text",
2734
+ "Email": "text",
2735
+ "Linked At": "text"
2736
+ },
2737
+ "edges": {
2738
+ "Messages": {
2739
+ "target": "Message",
2740
+ "readable": false,
2741
+ "writable": true,
2742
+ "description": "Send a message into this person's private bot chat (write-only — the Bot API cannot list a chat's history). The chat id is the linked user's own id."
2743
+ }
2744
+ },
2745
+ "description": "A person who linked Telegram via the connect handshake. Their `Chat Id` is the value a send targets (private chat id = user id).",
2746
+ "fieldDocs": {
2747
+ "Chat Id": "The private chat id a send targets (= the Telegram user id).",
2748
+ "Email": "The Listen-Fire account email this Telegram user linked as.",
2749
+ "Linked At": "When the link handshake completed (ISO-8601)."
2750
+ },
2751
+ "fieldIds": {
2752
+ "Chat Id": "chat_id",
2753
+ "Email": "email",
2754
+ "Linked At": "linked_at"
2755
+ }
2218
2756
  }
2219
2757
  },
2220
2758
  "collections": {
2759
+ "Messages": {
2760
+ "target": "Message",
2761
+ "readable": false,
2762
+ "listenable": true,
2763
+ "identity": "Id",
2764
+ "delivery": "connection"
2765
+ },
2221
2766
  "Linked Users": {
2222
2767
  "target": "Linked User"
2223
2768
  }
@@ -2232,6 +2777,7 @@
2232
2777
  "resultShape": {
2233
2778
  "externalId": "text",
2234
2779
  "url": "text",
2780
+ "Id": "text",
2235
2781
  "Message Id": "text",
2236
2782
  "Text": "text",
2237
2783
  "Chat Id": "text",
@@ -2244,15 +2790,18 @@
2244
2790
  },
2245
2791
  "edges": {
2246
2792
  "Attachments": {
2247
- "target": "Attachment"
2793
+ "target": "Attachment",
2794
+ "description": "Files attached to the message — documents, photos, voice notes, audio (zero or more)."
2248
2795
  },
2249
2796
  "Replies": {
2250
2797
  "target": "Message",
2251
2798
  "readable": false,
2252
- "writable": true
2799
+ "writable": true,
2800
+ "description": "Send a reply threaded under this message (write-only — the Bot API cannot list replies). The chat and the reply target come from this message."
2253
2801
  },
2254
2802
  "Sender": {
2255
- "target": "Linked User"
2803
+ "target": "Linked User",
2804
+ "description": "The linked user who sent this message — resolves only for a sender who completed the connect handshake (private chats); a group chat or an unlinked sender resolves to nothing."
2256
2805
  }
2257
2806
  },
2258
2807
  "requiredFields": [
@@ -2260,16 +2809,11 @@
2260
2809
  ],
2261
2810
  "fieldDocs": {
2262
2811
  "Text": "The message text. On an inbound message: the received text (or the media caption; empty for caption-less media). On a send: the message body — required.",
2263
- "Reply Markup": "Telegram's `reply_markup`, exactly as the Bot API spells it — passed through verbatim (no wrapping, no typed shape here; a malformed keyboard surfaces as a loud Telegram error at run time). Usually an inline keyboard: rows of buttons, each `{ text, url }` (opens a link) or `{ text, callback_data }` (a tap this bot handles, capped by Telegram at 64 BYTES). To make a tap DO something, mint a callback (`c = callback({ … })`) and put `\"${c.id}\"` in `callback_data` — it fits the 64-byte cap comfortably — one callback per button, since its body is what that button means. Telegram captures nothing at tap time, so a callback fired from a keyboard takes no values; anything a person has to fill in needs a `url` button instead. Buttons compose with everything else on the send. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). Example:\n{ inline_keyboard: [\n [ { text: \"Approve\", callback_data: \"${approve.id}\" },\n { text: \"Decline\", callback_data: \"${decline.id}\" } ],\n] }"
2812
+ "Reply Markup": "Telegram's `reply_markup`, exactly as the Bot API spells it — passed through verbatim (no wrapping, no typed shape here; a malformed keyboard surfaces as a loud Telegram error at run time). Usually an inline keyboard: rows of buttons, each `{ text, url }` (opens a link). To make a tap DO something, write a form from the `@lf/forms` adapter (`form = write forms-[:Forms]-> { Title: …, Fields: … }`), put its `form.Url` on a `url` button, and `await form-[:Submissions]->`: the form also asks for anything a person has to fill in, which Telegram does not capture at tap time. A `{ text, callback_data }` button reaches no run. Buttons compose with everything else on the send. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). Example:\n{ inline_keyboard: [\n [ { text: \"Answer\", url: \"${form.Url}\" } ],\n] }"
2264
2813
  }
2265
2814
  }
2266
2815
  },
2267
- "eventPosition": "Message",
2268
- "eventPositions": [
2269
- {
2270
- "position": "Message"
2271
- }
2272
- ]
2816
+ "supportsInPlaceUpdate": true
2273
2817
  },
2274
2818
  "whatsapp": {
2275
2819
  "positions": {
@@ -2285,45 +2829,95 @@
2285
2829
  },
2286
2830
  "edges": {
2287
2831
  "Attachments": {
2288
- "target": "Attachment"
2832
+ "target": "Attachment",
2833
+ "description": "Media attached to the message (zero or more)."
2289
2834
  },
2290
2835
  "Replies": {
2291
2836
  "target": "Message",
2292
2837
  "readable": false,
2293
- "writable": true
2838
+ "writable": true,
2839
+ "description": "Send a reply threaded to this message (write-only — Meta has no read API). The recipient derives from this message; sends only reach a conversation someone opened by messaging the number (Meta's 24-hour service window)."
2294
2840
  },
2295
2841
  "Reactions": {
2296
2842
  "target": "Reaction",
2297
2843
  "readable": false,
2298
- "writable": true
2844
+ "writable": true,
2845
+ "description": "React to this message with an emoji (write-only)."
2299
2846
  },
2300
2847
  "Typing": {
2301
2848
  "target": "Typing",
2302
2849
  "ephemeral": true,
2303
2850
  "readable": false,
2304
- "writable": true
2851
+ "writable": true,
2852
+ "description": "Show the typing indicator (and mark this message read) while a longer step runs. An action, not a record — nothing is created; do not bind the result."
2305
2853
  }
2306
2854
  },
2307
2855
  "writeOnlyProperties": [
2308
2856
  "File",
2309
2857
  "Interactive"
2310
- ]
2858
+ ],
2859
+ "description": "A single inbound WhatsApp message — what a listen on `Messages` delivers, whole: read `e.`Body`` straight off the arrival, reply along its `Replies` edge.",
2860
+ "fieldDocs": {
2861
+ "Message Id": "The WhatsApp message id — the value a reply or reaction addresses.",
2862
+ "From": "The sender's phone number, digits with the country code first and no plus (`447700900001`): the conversation a message is in, so a listen narrows to one by it.",
2863
+ "To": "Inbound: the team's own WhatsApp business number (the message's recipient). On a send: the other party's phone number, derived from the message the write is anchored on (an inbound parent's From, a sent parent's To) — read-only either way.",
2864
+ "Body": "The message text. Empty for media-only messages. At least one of Body / File on a send (the Body captions the file).",
2865
+ "File": "A file to send (image / PDF / audio / video, routed by content type). At least one of Body / File. Send-side only: a received message's media is on its Attachments edge — read `msg-[:Attachments]->.File`.",
2866
+ "Interactive": "WhatsApp's `interactive` object, exactly as the Cloud API spells it — passed through verbatim (no wrapping, no typed shape here; a malformed object surfaces as a loud WhatsApp error at run time, not at save time). Sending this REPLACES a plain-text send: the interactive type carries its own `body.text`, so the message's words go inside `interactive.body.text`, never in `Body` on the same send. Reply buttons: up to 3, each `{ type: \"reply\", reply: { id, title } }` (`title` capped at 20 characters); a reply button's tap reaches no run. To make a tap DO something, write a form from the `@lf/forms` adapter (`form = write forms-[:Forms]-> { Title: …, Fields: … }`), send its `form.Url` on a url button (`type: \"cta_url\"`), and `await form-[:Submissions]->`: the form also asks for anything a person has to fill in, which WhatsApp does not capture at tap time. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). Example:\n{ type: \"cta_url\",\n body: { text: \"Ship the release?\" },\n action: { name: \"cta_url\", parameters: { display_text: \"Answer\", url: \"${form.Url}\" } } }",
2867
+ "WhatsApp Id": "The sender's WhatsApp account id.",
2868
+ "Profile Name": "The sender's WhatsApp profile display name.",
2869
+ "Timestamp": "When the message was received (ISO-8601), when known."
2870
+ },
2871
+ "fieldIds": {
2872
+ "Message Id": "messageId",
2873
+ "From": "from",
2874
+ "To": "to",
2875
+ "Body": "body",
2876
+ "WhatsApp Id": "waId",
2877
+ "Profile Name": "profileName",
2878
+ "Timestamp": "timestamp"
2879
+ }
2311
2880
  },
2312
2881
  "Attachment": {
2313
- "properties": {},
2314
- "edges": {},
2315
- "undescribed": true
2316
- },
2317
- "Reaction": {
2318
2882
  "properties": {
2319
- "Emoji": "text",
2320
- "Reacted Message Id": "text",
2883
+ "Name": "text",
2884
+ "Content Type": "text",
2885
+ "URL": "text",
2886
+ "File": "file"
2887
+ },
2888
+ "edges": {},
2889
+ "fieldIds": {
2890
+ "Name": "name",
2891
+ "Content Type": "contentType",
2892
+ "URL": "url",
2893
+ "File": "data"
2894
+ }
2895
+ },
2896
+ "Reaction": {
2897
+ "properties": {
2898
+ "Emoji": "text",
2899
+ "Reacted Message Id": "text",
2321
2900
  "Message Id": "text",
2322
2901
  "From": "text",
2323
2902
  "Profile Name": "text",
2324
2903
  "Timestamp": "text"
2325
2904
  },
2326
- "edges": {}
2905
+ "edges": {},
2906
+ "description": "Someone reacted to a message in the team-number conversation. What a listen on `Reactions` delivers. Also the reaction WRITE: created along a message's `Reactions` edge.",
2907
+ "fieldDocs": {
2908
+ "Emoji": "The reaction emoji. Inbound: empty when the reaction was removed. On a write along msg-[:Reactions]->: the single emoji to react with — required.",
2909
+ "Reacted Message Id": "The message the reaction is on.",
2910
+ "Message Id": "The reaction event's own id.",
2911
+ "From": "The reactor's phone number."
2912
+ },
2913
+ "fieldIds": {
2914
+ "Emoji": "emoji",
2915
+ "Reacted Message Id": "reactedMessageId",
2916
+ "Message Id": "messageId",
2917
+ "From": "from",
2918
+ "Profile Name": "profileName",
2919
+ "Timestamp": "timestamp"
2920
+ }
2327
2921
  },
2328
2922
  "Location": {
2329
2923
  "properties": {
@@ -2336,15 +2930,52 @@
2336
2930
  "Profile Name": "text",
2337
2931
  "Timestamp": "text"
2338
2932
  },
2339
- "edges": {}
2933
+ "edges": {},
2934
+ "description": "Someone shared a location with the team number. What a listen on `Locations` delivers.",
2935
+ "fieldDocs": {
2936
+ "Name": "The place name, when the sender picked one."
2937
+ },
2938
+ "fieldIds": {
2939
+ "Latitude": "latitude",
2940
+ "Longitude": "longitude",
2941
+ "Name": "name",
2942
+ "Address": "address",
2943
+ "Message Id": "messageId",
2944
+ "From": "from",
2945
+ "Profile Name": "profileName",
2946
+ "Timestamp": "timestamp"
2947
+ }
2340
2948
  },
2341
2949
  "Typing": {
2342
2950
  "properties": {},
2343
2951
  "edges": {},
2344
- "undescribed": true
2952
+ "description": "The typing-indicator action, written along a message's `Typing` edge (`write msg-[:Typing]-> {}` — no fields; Meta's API is on-only, so there is no off to write). An action, not a record: nothing is created and nothing reads back. WhatsApp clears the indicator itself when your reply sends, or after ~25 seconds."
2953
+ }
2954
+ },
2955
+ "collections": {
2956
+ "Messages": {
2957
+ "target": "Message",
2958
+ "readable": false,
2959
+ "listenable": true,
2960
+ "identity": "Message Id",
2961
+ "delivery": "connection"
2962
+ },
2963
+ "Reactions": {
2964
+ "target": "Reaction",
2965
+ "readable": false,
2966
+ "listenable": true,
2967
+ "identity": "Message Id",
2968
+ "delivery": "connection",
2969
+ "attributes": true
2970
+ },
2971
+ "Locations": {
2972
+ "target": "Location",
2973
+ "readable": false,
2974
+ "listenable": true,
2975
+ "identity": "Message Id",
2976
+ "delivery": "connection"
2345
2977
  }
2346
2978
  },
2347
- "collections": {},
2348
2979
  "writableRoots": {},
2349
2980
  "createShapes": {
2350
2981
  "Message": {
@@ -2368,29 +2999,33 @@
2368
2999
  },
2369
3000
  "edges": {
2370
3001
  "Attachments": {
2371
- "target": "Attachment"
3002
+ "target": "Attachment",
3003
+ "description": "Media attached to the message (zero or more)."
2372
3004
  },
2373
3005
  "Replies": {
2374
3006
  "target": "Message",
2375
3007
  "readable": false,
2376
- "writable": true
3008
+ "writable": true,
3009
+ "description": "Send a reply threaded to this message (write-only — Meta has no read API). The recipient derives from this message; sends only reach a conversation someone opened by messaging the number (Meta's 24-hour service window)."
2377
3010
  },
2378
3011
  "Reactions": {
2379
3012
  "target": "Reaction",
2380
3013
  "readable": false,
2381
- "writable": true
3014
+ "writable": true,
3015
+ "description": "React to this message with an emoji (write-only)."
2382
3016
  },
2383
3017
  "Typing": {
2384
3018
  "target": "Typing",
2385
3019
  "ephemeral": true,
2386
3020
  "readable": false,
2387
- "writable": true
3021
+ "writable": true,
3022
+ "description": "Show the typing indicator (and mark this message read) while a longer step runs. An action, not a record — nothing is created; do not bind the result."
2388
3023
  }
2389
3024
  },
2390
3025
  "fieldDocs": {
2391
3026
  "Body": "The message text. Empty for media-only messages. At least one of Body / File on a send (the Body captions the file).",
2392
3027
  "File": "A file to send (image / PDF / audio / video, routed by content type). At least one of Body / File. Send-side only: a received message's media is on its Attachments edge — read `msg-[:Attachments]->.File`.",
2393
- "Interactive": "WhatsApp's `interactive` object, exactly as the Cloud API spells it — passed through verbatim (no wrapping, no typed shape here; a malformed object surfaces as a loud WhatsApp error at run time, not at save time). Sending this REPLACES a plain-text send: the interactive type carries its own `body.text`, so the message's words go inside `interactive.body.text`, never in `Body` on the same send. Usually reply buttons: up to 3, each `{ type: \"reply\", reply: { id, title } }` (`title` capped at 20 characters). To make a tap DO something, mint a callback (`c = callback({ … })`) and put `\"${c.id}\"` in `reply.id` — WhatsApp allows up to 256 characters there, roomy for it — one callback per button, since its body is what that button means. WhatsApp captures nothing at tap time, so a callback fired from a button takes no values; anything a person has to fill in needs a plain-text reply instead. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). Example:\n{ type: \"button\",\n body: { text: \"Ship the release?\" },\n action: { buttons: [\n { type: \"reply\", reply: { id: \"${approve.id}\", title: \"Approve\" } },\n { type: \"reply\", reply: { id: \"${reject.id}\", title: \"Reject\" } } ] } }"
3028
+ "Interactive": "WhatsApp's `interactive` object, exactly as the Cloud API spells it — passed through verbatim (no wrapping, no typed shape here; a malformed object surfaces as a loud WhatsApp error at run time, not at save time). Sending this REPLACES a plain-text send: the interactive type carries its own `body.text`, so the message's words go inside `interactive.body.text`, never in `Body` on the same send. Reply buttons: up to 3, each `{ type: \"reply\", reply: { id, title } }` (`title` capped at 20 characters); a reply button's tap reaches no run. To make a tap DO something, write a form from the `@lf/forms` adapter (`form = write forms-[:Forms]-> { Title: …, Fields: … }`), send its `form.Url` on a url button (`type: \"cta_url\"`), and `await form-[:Submissions]->`: the form also asks for anything a person has to fill in, which WhatsApp does not capture at tap time. Build strings with \"${…}\" interpolation (never `+`, which is numeric addition). Example:\n{ type: \"cta_url\",\n body: { text: \"Ship the release?\" },\n action: { name: \"cta_url\", parameters: { display_text: \"Answer\", url: \"${form.Url}\" } } }"
2394
3029
  }
2395
3030
  },
2396
3031
  "Reaction": {
@@ -2415,87 +3050,642 @@
2415
3050
  }
2416
3051
  }
2417
3052
  },
2418
- "eventPosition": "Message",
2419
- "eventPositions": [
2420
- {
2421
- "position": "Message",
2422
- "on": [
2423
- "message"
2424
- ]
2425
- },
2426
- {
2427
- "position": "Reaction",
2428
- "on": [
2429
- "reaction"
2430
- ]
2431
- },
2432
- {
2433
- "position": "Location",
2434
- "on": [
2435
- "location"
2436
- ]
2437
- }
2438
- ]
3053
+ "supportsInPlaceUpdate": true
2439
3054
  },
2440
3055
  "system": {
2441
3056
  "positions": {
2442
- "Run Failed": {
3057
+ "Event": {
2443
3058
  "properties": {
3059
+ "Event Id": "text",
3060
+ "Kind": {
3061
+ "kind": "enum",
3062
+ "options": [
3063
+ "Run Failed",
3064
+ "Run Paused",
3065
+ "Validation Issue",
3066
+ "Deprecated Version",
3067
+ "Release Applied",
3068
+ "Wait Lost Its Record",
3069
+ "Arrival Not Judged"
3070
+ ]
3071
+ },
2444
3072
  "Automation": "text",
2445
3073
  "Automation Id": "text",
2446
3074
  "Run Id": "text",
2447
3075
  "Version": "text",
2448
3076
  "Reason": "text",
2449
3077
  "Url": "text",
2450
- "At": "date"
3078
+ "At": "date",
3079
+ "Adapter": "text",
3080
+ "Edge": "text",
3081
+ "Record Id": "text",
3082
+ "Listen": "text",
3083
+ "Delivery Id": "text",
3084
+ "Arrival Id": "text",
3085
+ "Attempt": "number"
2451
3086
  },
2452
- "edges": {}
2453
- },
2454
- "Validation Issue": {
2455
- "properties": {},
2456
3087
  "edges": {},
2457
- "undescribed": true
3088
+ "propertyCapabilities": {
3089
+ "Kind": {
3090
+ "filterOperators": [
3091
+ "eq",
3092
+ "in"
3093
+ ]
3094
+ }
3095
+ },
3096
+ "description": "Something that happened to the host’s runs, waits or releases. `Kind` says which.",
3097
+ "fieldDocs": {
3098
+ "Event Id": "Names the event: the same on every poll that finds it.",
3099
+ "Kind": "Which thing happened. A listen names the kinds it wants: `e.Kind == \"Run Failed\"`, or `e.Kind IN [...]`.",
3100
+ "Automation": "The program the event is about (its movement); empty when it concerns none.",
3101
+ "Automation Id": "That program’s id (on listen-fire, `<file>:<movement>`).",
3102
+ "Run Id": "The run the event is about; empty when it concerns none.",
3103
+ "Version": "The release involved; empty when none is.",
3104
+ "Reason": "What happened, in words: for a failed run, why it failed.",
3105
+ "Url": "A link to where a person sees it; empty where the host has no such page.",
3106
+ "At": "When it happened (UTC).",
3107
+ "Adapter": "For a wait that lost its record: the module its record is in. Empty on every other kind.",
3108
+ "Edge": "For a wait that lost its record: the edge it waits on. For an arrival not judged: the edge it arrived on. Empty on every other kind.",
3109
+ "Record Id": "For a wait that lost its record: the deleted record’s id. For an arrival not judged: the record that arrived. Empty on every other kind.",
3110
+ "Listen": "For an arrival not judged: the listen that could not judge it, as the host names it. Empty on every other kind.",
3111
+ "Delivery Id": "For an arrival not judged: the delivery it came in, which the host keeps for a while (on listen-fire `deliveries.retainDays`, 14 days by default); empty when the host kept none. Empty on every other kind.",
3112
+ "Arrival Id": "For an arrival not judged: the arrival, as the listen names it (its edge, record, identity and version). Empty on every other kind.",
3113
+ "Attempt": "For an arrival not judged: 0 when it was delivered, one more for each replay (`listen-fire listens replay`) that still could not judge it. 0 on every other kind."
3114
+ }
3115
+ }
3116
+ },
3117
+ "collections": {
3118
+ "Events": {
3119
+ "target": "Event",
3120
+ "capability": {
3121
+ "filter": "native",
3122
+ "supportsLimit": false
3123
+ },
3124
+ "readable": false,
3125
+ "listenable": true,
3126
+ "cursor": true,
3127
+ "identity": "Event Id",
3128
+ "delivery": "polled",
3129
+ "pollEverySeconds": 60,
3130
+ "attributes": true
3131
+ }
3132
+ },
3133
+ "writableRoots": {},
3134
+ "supportsInPlaceUpdate": true
3135
+ },
3136
+ "github": {
3137
+ "positions": {
3138
+ "Repository": {
3139
+ "properties": {
3140
+ "Name": "text",
3141
+ "Full Name": "text",
3142
+ "Owner": "text",
3143
+ "Description": "text",
3144
+ "Private": "boolean",
3145
+ "Fork": "boolean",
3146
+ "Archived": "boolean",
3147
+ "Default Branch": "text",
3148
+ "HTML URL": "text",
3149
+ "Id": "text",
3150
+ "Created At": "date",
3151
+ "Updated At": "date",
3152
+ "Pushed At": "date"
3153
+ },
3154
+ "edges": {
3155
+ "Pull Requests": {
3156
+ "target": "Pull Request",
3157
+ "capability": {
3158
+ "filter": "native",
3159
+ "order": "bounded",
3160
+ "supportsLimit": true,
3161
+ "naturalOrder": {
3162
+ "field": "Created At",
3163
+ "direction": "desc"
3164
+ }
3165
+ },
3166
+ "sequenced": "chronological",
3167
+ "description": "Pull requests, open and closed, newest first. `State` reaches GitHub; other filters run over what comes back, so pin `State` == \"open\" on a busy repository."
3168
+ },
3169
+ "Issues": {
3170
+ "target": "Issue",
3171
+ "capability": {
3172
+ "filter": "native",
3173
+ "order": "bounded",
3174
+ "supportsLimit": true,
3175
+ "naturalOrder": {
3176
+ "field": "Created At",
3177
+ "direction": "desc"
3178
+ }
3179
+ },
3180
+ "sequenced": "chronological",
3181
+ "description": "Issues (not pull requests), open and closed, newest first. `State`, `Author`, a label in `Label Names`, an assignee in `Assignees` and an `Updated At` lower bound reach GitHub."
3182
+ }
3183
+ },
3184
+ "propertyCapabilities": {
3185
+ "Full Name": {
3186
+ "filterOperators": [
3187
+ "eq"
3188
+ ]
3189
+ }
3190
+ },
3191
+ "description": "A repository: its pull requests and its issues.",
3192
+ "fieldDocs": {
3193
+ "Full Name": "`owner/name`. An `==` in a `Repositories` WHERE fetches that repository directly.",
3194
+ "Owner": "The owner’s login (`owner.login`)."
3195
+ },
3196
+ "fieldIds": {
3197
+ "Name": "name",
3198
+ "Full Name": "full_name",
3199
+ "Owner": "owner",
3200
+ "Description": "description",
3201
+ "Private": "private",
3202
+ "Fork": "fork",
3203
+ "Archived": "archived",
3204
+ "Default Branch": "default_branch",
3205
+ "HTML URL": "html_url",
3206
+ "Id": "id",
3207
+ "Created At": "created_at",
3208
+ "Updated At": "updated_at",
3209
+ "Pushed At": "pushed_at"
3210
+ }
2458
3211
  },
2459
- "Deprecated Version": {
2460
- "properties": {},
3212
+ "Notification": {
3213
+ "properties": {
3214
+ "Id": "text",
3215
+ "Reason": {
3216
+ "kind": "enum",
3217
+ "options": [
3218
+ "agent_session_finished",
3219
+ "approval_requested",
3220
+ "assign",
3221
+ "author",
3222
+ "ci_activity",
3223
+ "comment",
3224
+ "invitation",
3225
+ "manual",
3226
+ "member_feature_requested",
3227
+ "mention",
3228
+ "review_requested",
3229
+ "security_advisory_credit",
3230
+ "security_alert",
3231
+ "state_change",
3232
+ "subscribed",
3233
+ "team_mention"
3234
+ ]
3235
+ },
3236
+ "Participating": "boolean",
3237
+ "Unread": "boolean",
3238
+ "Updated At": "date",
3239
+ "Last Read At": "date",
3240
+ "Title": "text",
3241
+ "Subject Type": {
3242
+ "kind": "enum",
3243
+ "options": [
3244
+ "PullRequest",
3245
+ "Issue",
3246
+ "Commit",
3247
+ "Release",
3248
+ "Discussion",
3249
+ "CheckSuite",
3250
+ "RepositoryVulnerabilityAlert",
3251
+ "RepositoryDependabotAlertsThread"
3252
+ ],
3253
+ "open": {}
3254
+ }
3255
+ },
3256
+ "edges": {
3257
+ "Repository": {
3258
+ "target": "Repository",
3259
+ "required": true,
3260
+ "description": "The repository the thread is in."
3261
+ },
3262
+ "Pull Request": {
3263
+ "target": "Pull Request",
3264
+ "description": "The pull request it is about; nothing when `Subject Type` is not `PullRequest`."
3265
+ },
3266
+ "Issue": {
3267
+ "target": "Issue",
3268
+ "description": "The issue it is about; nothing when `Subject Type` is not `Issue`."
3269
+ }
3270
+ },
3271
+ "propertyCapabilities": {
3272
+ "Reason": {
3273
+ "filterOperators": [
3274
+ "eq",
3275
+ "in"
3276
+ ]
3277
+ },
3278
+ "Participating": {
3279
+ "filterOperators": [
3280
+ "eq"
3281
+ ]
3282
+ },
3283
+ "Updated At": {
3284
+ "filterOperators": [
3285
+ "gt",
3286
+ "gte",
3287
+ "lt",
3288
+ "lte"
3289
+ ]
3290
+ }
3291
+ },
3292
+ "description": "A notification thread: why you were told (`Reason`), what about (`Title`, `Subject Type`), and where. Walk `Pull Request` or `Issue` for the subject itself.",
3293
+ "fieldDocs": {
3294
+ "Id": "The thread id.",
3295
+ "Reason": "Why you got it (`review_requested`, `mention`, `assign`, …). A thread’s reason can change as it moves.",
3296
+ "Participating": "Whether you take part in the thread or are mentioned in it: GitHub tells only by its `participating` filter, so every thread, walked or heard, is stamped with it from one extra call (made only when there are threads).",
3297
+ "Title": "The subject’s title (`subject.title`).",
3298
+ "Subject Type": "What it is about (`subject.type`): `PullRequest`, `Issue`, `Release`, `Discussion`, `Commit`, `CheckSuite`, …"
3299
+ },
3300
+ "fieldIds": {
3301
+ "Id": "id",
3302
+ "Reason": "reason",
3303
+ "Participating": "participating",
3304
+ "Unread": "unread",
3305
+ "Updated At": "updated_at",
3306
+ "Last Read At": "last_read_at",
3307
+ "Title": "title",
3308
+ "Subject Type": "subject_type"
3309
+ }
3310
+ },
3311
+ "Pull Request": {
3312
+ "properties": {
3313
+ "Number": "number",
3314
+ "Title": "text",
3315
+ "Body": "text",
3316
+ "State": {
3317
+ "kind": "enum",
3318
+ "options": [
3319
+ "open",
3320
+ "closed"
3321
+ ]
3322
+ },
3323
+ "Author": "text",
3324
+ "HTML URL": "text",
3325
+ "Created At": "date",
3326
+ "Updated At": "date",
3327
+ "Closed At": "date",
3328
+ "Assignees": {
3329
+ "kind": "list",
3330
+ "of": "text"
3331
+ },
3332
+ "Label Names": {
3333
+ "kind": "list",
3334
+ "of": "text"
3335
+ },
3336
+ "Id": "text",
3337
+ "Draft": "boolean",
3338
+ "Merged": "boolean",
3339
+ "Merged At": "date",
3340
+ "Requested Reviewers": {
3341
+ "kind": "list",
3342
+ "of": "text"
3343
+ },
3344
+ "Head Ref": "text",
3345
+ "Base Ref": "text"
3346
+ },
3347
+ "edges": {
3348
+ "Comments": {
3349
+ "target": "Comment",
3350
+ "capability": {
3351
+ "filter": "bounded",
3352
+ "order": "bounded",
3353
+ "supportsLimit": true,
3354
+ "naturalOrder": {
3355
+ "field": "Created At",
3356
+ "direction": "asc"
3357
+ }
3358
+ },
3359
+ "sequenced": "chronological",
3360
+ "writable": true,
3361
+ "description": "The conversation, oldest first (a pull request’s are its issue comments, not its review comments). Comment here: `write pr-[:Comments]-> { Body: \"…\" }`."
3362
+ },
3363
+ "Labels": {
3364
+ "target": "Label",
3365
+ "capability": {
3366
+ "filter": "bounded",
3367
+ "order": "bounded",
3368
+ "supportsLimit": true
3369
+ },
3370
+ "writable": true,
3371
+ "description": "The labels it carries. Add one: `write issue-[:Labels]-> { Name: \"bug\" }` (GitHub creates a label the repository does not have yet; adding one it already carries changes nothing)."
3372
+ },
3373
+ "Repository": {
3374
+ "target": "Repository",
3375
+ "required": true,
3376
+ "description": "The repository it belongs to."
3377
+ }
3378
+ },
3379
+ "propertyCapabilities": {
3380
+ "State": {
3381
+ "filterOperators": [
3382
+ "eq"
3383
+ ]
3384
+ }
3385
+ },
3386
+ "description": "A pull request. Comment on it along `Comments`; label it along `Labels`.",
3387
+ "fieldDocs": {
3388
+ "Author": "The login of who opened it (`user.login`).",
3389
+ "Assignees": "The assignees’ logins.",
3390
+ "Label Names": "The names of its labels; `Labels` are the labels themselves.",
3391
+ "Merged": "Closed by merging (`merged_at` is set).",
3392
+ "Requested Reviewers": "The logins asked to review it.",
3393
+ "Head Ref": "The branch it merges from (`head.ref`).",
3394
+ "Base Ref": "The branch it merges into (`base.ref`)."
3395
+ },
3396
+ "fieldIds": {
3397
+ "Number": "number",
3398
+ "Title": "title",
3399
+ "Body": "body",
3400
+ "State": "state",
3401
+ "Author": "author",
3402
+ "HTML URL": "html_url",
3403
+ "Created At": "created_at",
3404
+ "Updated At": "updated_at",
3405
+ "Closed At": "closed_at",
3406
+ "Assignees": "assignees",
3407
+ "Label Names": "label_names",
3408
+ "Id": "id",
3409
+ "Draft": "draft",
3410
+ "Merged": "merged",
3411
+ "Merged At": "merged_at",
3412
+ "Requested Reviewers": "requested_reviewers",
3413
+ "Head Ref": "head_ref",
3414
+ "Base Ref": "base_ref"
3415
+ }
3416
+ },
3417
+ "Issue": {
3418
+ "properties": {
3419
+ "Number": "number",
3420
+ "Title": "text",
3421
+ "Body": "text",
3422
+ "State": {
3423
+ "kind": "enum",
3424
+ "options": [
3425
+ "open",
3426
+ "closed"
3427
+ ]
3428
+ },
3429
+ "Author": "text",
3430
+ "HTML URL": "text",
3431
+ "Created At": "date",
3432
+ "Updated At": "date",
3433
+ "Closed At": "date",
3434
+ "Assignees": {
3435
+ "kind": "list",
3436
+ "of": "text"
3437
+ },
3438
+ "Label Names": {
3439
+ "kind": "list",
3440
+ "of": "text"
3441
+ },
3442
+ "Id": "text",
3443
+ "State Reason": "text",
3444
+ "Comment Count": "number"
3445
+ },
3446
+ "edges": {
3447
+ "Comments": {
3448
+ "target": "Comment",
3449
+ "capability": {
3450
+ "filter": "bounded",
3451
+ "order": "bounded",
3452
+ "supportsLimit": true,
3453
+ "naturalOrder": {
3454
+ "field": "Created At",
3455
+ "direction": "asc"
3456
+ }
3457
+ },
3458
+ "sequenced": "chronological",
3459
+ "writable": true,
3460
+ "description": "The conversation, oldest first (a pull request’s are its issue comments, not its review comments). Comment here: `write pr-[:Comments]-> { Body: \"…\" }`."
3461
+ },
3462
+ "Labels": {
3463
+ "target": "Label",
3464
+ "capability": {
3465
+ "filter": "bounded",
3466
+ "order": "bounded",
3467
+ "supportsLimit": true
3468
+ },
3469
+ "writable": true,
3470
+ "description": "The labels it carries. Add one: `write issue-[:Labels]-> { Name: \"bug\" }` (GitHub creates a label the repository does not have yet; adding one it already carries changes nothing)."
3471
+ },
3472
+ "Repository": {
3473
+ "target": "Repository",
3474
+ "required": true,
3475
+ "description": "The repository it belongs to."
3476
+ }
3477
+ },
3478
+ "propertyCapabilities": {
3479
+ "State": {
3480
+ "filterOperators": [
3481
+ "eq"
3482
+ ]
3483
+ },
3484
+ "Author": {
3485
+ "filterOperators": [
3486
+ "eq"
3487
+ ]
3488
+ },
3489
+ "Updated At": {
3490
+ "filterOperators": [
3491
+ "gt",
3492
+ "gte"
3493
+ ]
3494
+ },
3495
+ "Assignees": {
3496
+ "filterOperators": [
3497
+ "contains"
3498
+ ]
3499
+ },
3500
+ "Label Names": {
3501
+ "filterOperators": [
3502
+ "contains"
3503
+ ]
3504
+ }
3505
+ },
3506
+ "description": "An issue. Comment on it along `Comments`; label it along `Labels`.",
3507
+ "fieldDocs": {
3508
+ "Author": "The login of who opened it (`user.login`).",
3509
+ "Assignees": "The assignees’ logins.",
3510
+ "Label Names": "The names of its labels; `Labels` are the labels themselves.",
3511
+ "State Reason": "Why it was closed (`completed`, `not_planned`, …), or reopened.",
3512
+ "Comment Count": "How many comments it has (`comments`)."
3513
+ },
3514
+ "fieldIds": {
3515
+ "Number": "number",
3516
+ "Title": "title",
3517
+ "Body": "body",
3518
+ "State": "state",
3519
+ "Author": "author",
3520
+ "HTML URL": "html_url",
3521
+ "Created At": "created_at",
3522
+ "Updated At": "updated_at",
3523
+ "Closed At": "closed_at",
3524
+ "Assignees": "assignees",
3525
+ "Label Names": "label_names",
3526
+ "Id": "id",
3527
+ "State Reason": "state_reason",
3528
+ "Comment Count": "comments"
3529
+ }
3530
+ },
3531
+ "Comment": {
3532
+ "properties": {
3533
+ "Body": "text",
3534
+ "Author": "text",
3535
+ "Author Association": "text",
3536
+ "HTML URL": "text",
3537
+ "Created At": "date",
3538
+ "Updated At": "date",
3539
+ "Id": "text"
3540
+ },
2461
3541
  "edges": {},
2462
- "undescribed": true
3542
+ "description": "A comment in a pull request’s or an issue’s conversation.",
3543
+ "fieldDocs": {
3544
+ "Body": "The comment, in GitHub Markdown.",
3545
+ "Author": "The commenter’s login (`user.login`).",
3546
+ "Author Association": "`OWNER`, `MEMBER`, `CONTRIBUTOR`, `NONE`, …"
3547
+ },
3548
+ "fieldIds": {
3549
+ "Body": "body",
3550
+ "Author": "author",
3551
+ "Author Association": "author_association",
3552
+ "HTML URL": "html_url",
3553
+ "Created At": "created_at",
3554
+ "Updated At": "updated_at",
3555
+ "Id": "id"
3556
+ }
2463
3557
  },
2464
- "Release Applied": {
2465
- "properties": {},
3558
+ "Label": {
3559
+ "properties": {
3560
+ "Name": "text",
3561
+ "Color": "text",
3562
+ "Description": "text",
3563
+ "Id": "text"
3564
+ },
2466
3565
  "edges": {},
2467
- "undescribed": true
3566
+ "description": "A label on a pull request or an issue.",
3567
+ "fieldDocs": {
3568
+ "Color": "Hex, without the `#`."
3569
+ },
3570
+ "fieldIds": {
3571
+ "Name": "name",
3572
+ "Color": "color",
3573
+ "Description": "description",
3574
+ "Id": "id"
3575
+ }
3576
+ }
3577
+ },
3578
+ "collections": {
3579
+ "Repositories": {
3580
+ "target": "Repository"
3581
+ },
3582
+ "Notifications": {
3583
+ "target": "Notification",
3584
+ "listenable": true,
3585
+ "cursor": true,
3586
+ "identity": "Id",
3587
+ "version": "Updated At",
3588
+ "delivery": "polled",
3589
+ "pollEverySeconds": 60
2468
3590
  }
2469
3591
  },
2470
- "collections": {},
2471
3592
  "writableRoots": {},
2472
- "eventPosition": "Run Failed",
2473
- "eventPositions": [
2474
- {
2475
- "position": "Run Failed",
2476
- "on": [
2477
- "Run Failed"
2478
- ]
2479
- },
2480
- {
2481
- "position": "Validation Issue",
2482
- "on": [
2483
- "Validation Issue"
2484
- ]
2485
- },
2486
- {
2487
- "position": "Deprecated Version",
2488
- "on": [
2489
- "Deprecated Version"
2490
- ]
3593
+ "createShapes": {
3594
+ "Comment": {
3595
+ "fields": {
3596
+ "Body": "text"
3597
+ },
3598
+ "resultShape": {
3599
+ "externalId": "text",
3600
+ "url": "text",
3601
+ "Body": "text",
3602
+ "Author": "text",
3603
+ "Author Association": "text",
3604
+ "HTML URL": "text",
3605
+ "Created At": "date",
3606
+ "Updated At": "date",
3607
+ "Id": "text"
3608
+ },
3609
+ "requiredFields": [
3610
+ "Body"
3611
+ ],
3612
+ "fieldDocs": {
3613
+ "Body": "The comment, in GitHub Markdown."
3614
+ },
3615
+ "uniquenessAuthorable": false
2491
3616
  },
2492
- {
2493
- "position": "Release Applied",
2494
- "on": [
2495
- "Release Applied"
2496
- ]
3617
+ "Label": {
3618
+ "fields": {
3619
+ "Name": "text"
3620
+ },
3621
+ "resultShape": {
3622
+ "externalId": "text",
3623
+ "url": "text",
3624
+ "Name": "text",
3625
+ "Color": "text",
3626
+ "Description": "text",
3627
+ "Id": "text"
3628
+ },
3629
+ "requiredFields": [
3630
+ "Name"
3631
+ ],
3632
+ "uniquenessAuthorable": false
2497
3633
  }
2498
- ]
3634
+ },
3635
+ "supportsInPlaceUpdate": true
3636
+ },
3637
+ "forms": {
3638
+ "positions": {
3639
+ "Submission": {
3640
+ "properties": {
3641
+ "Id": "text"
3642
+ },
3643
+ "edges": {},
3644
+ "description": "An answer to a form: the fields of the node the form asked for, and its Id.",
3645
+ "fieldDocs": {
3646
+ "Id": "The answer’s id: a form takes one, so its form’s."
3647
+ }
3648
+ }
3649
+ },
3650
+ "collections": {
3651
+ "Forms": {
3652
+ "target": "Form"
3653
+ }
3654
+ },
3655
+ "writableRoots": {
3656
+ "Form": {
3657
+ "fields": {
3658
+ "Title": "text",
3659
+ "Fields": "json"
3660
+ },
3661
+ "resultShape": {
3662
+ "externalId": "text",
3663
+ "url": "text",
3664
+ "Url": "text",
3665
+ "Title": "text",
3666
+ "Fields": "json"
3667
+ },
3668
+ "edges": {
3669
+ "Submissions": {
3670
+ "target": "Submission",
3671
+ "readable": false,
3672
+ "listenable": true,
3673
+ "identity": "Id",
3674
+ "answerShapeFrom": "Fields",
3675
+ "description": "The form’s one answer, with the fields of the node the write named."
3676
+ }
3677
+ },
3678
+ "requiredFields": [
3679
+ "Title",
3680
+ "Fields"
3681
+ ],
3682
+ "fieldDocs": {
3683
+ "Title": "What the form asks, shown above its fields.",
3684
+ "Fields": "The node the form asks for (`Fields: Refund`): its fields are the form’s, and its submissions have them."
3685
+ }
3686
+ }
3687
+ },
3688
+ "supportsInPlaceUpdate": true
2499
3689
  }
2500
3690
  }
2501
3691
  }