Repository navigation
Expand file tree
/
Copy pathui-l0-constructors.toml
More file actions
737 lines (652 loc) · 33.2 KB
/
Copy pathui-l0-constructors.toml
File metadata and controls
737 lines (652 loc) · 33.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
# L0 constructor catalog — the normative argument contract for
# docs/ui-profile-l0.md. Derived from the three reference cards in
# octos-one/docs/l0/, not invented: every constructor and argument here is one
# they use.
#
# TOKENS ARE LEXICALLY MARKED with a leading dot: `.fill`, `.money`, `.center`.
#
# This supersedes amendment #9's resolution. That rule said a bare identifier
# resolved by the argument's declared shape — which fails the moment one
# argument admits both, and `unit` does: `unit: units` is a path to card state,
# `unit: .pct` is a token. Marking tokens removes the ambiguity at the lexical
# level rather than the semantic one, so a prop named `fill` can never collide
# with `.fill`, and the catalog only has to say which tokens are LEGAL rather
# than disambiguate what a name means.
#
# kinds:
# token one of `tokens`, written with a leading dot
# path a bound path — source, state, prop, copy or loop binder
# event a declared event name (local or received as a prop)
# number/text/bool a literal of that type, or a path of that shape
# data a bound path that renders a FACT. Distinct from `number`
# because `gap: 6` is layout the card may choose and
# `value: 41.2` is a measurement it may not (§4 no-facts)
# any path or literal
# record/collection component props only
# ─── containers ───────────────────────────────────────────────────────────────
# A registered theme-kit component. The host supplies its checked definition;
# an instance names a source-linked placement, not a widget implementation.
[Kit]
component = { kind = "text" }
instance = { kind = "text" }
part = { kind = "text" }
text = { kind = "text" }
placeholder = { kind = "text" }
enabled = { kind = "bool" }
checked = { kind = "bool" }
selected = { kind = "bool" }
index = { kind = "number" }
value = { kind = "number" }
value2 = { kind = "number" }
focused = { kind = "bool" }
password = { kind = "bool" }
[Surface]
pad = { kind = "token", tokens = ["page", "tight", "none"] }
[Photo]
src = { kind = "path" }
pad = { kind = "token", tokens = ["page", "tight", "none"] }
# A map. The card names the TRIP; the widget fetches its own route.
#
# The same correction `AqiContour` and `StockPlot` already took, and for the same
# reason. The shipping nav card calls `sys.navroute` itself, hand-builds a marker
# string, and pushes both into the widget through imperative setters from inside
# a per-frame `tick()` — which is the card doing the widget's job, and is most of
# why that card classifies at L2. A route is not a card's to compute.
[Map]
mode = { kind = "token", tokens = ["plan", "drive", "flat"] }
from = { kind = "path" }
to = { kind = "path" }
via = { kind = "path" }
# A SECOND fixed slot. Not a list: role arguments route through the expression
# grammar, so admitting one there is a change to the whole grammar for one argument.
# The app being replaced has exactly two waypoint slots and hides "add stop" when
# both are full.
via2 = { kind = "path" }
# The live position the camera follows — a source answering `lat`/`lon`, in
# practice `sys.gps`. This is what makes `.drive` mean the chase camera instead of
# the static preview: without it there is no measured position and a follow camera
# could only animate on a timer, drawing motion the user is not making. With it,
# every frame the map spends is one a real fix earned.
at = { kind = "path" }
# Flat or tilted, while driving. R8.1 of the shipping app's contract is a tilted
# 2.5D chase view; `.flat` is the heading-up 2D one. Ignored without `at:`, because
# a preview has no camera to tilt.
view = { kind = "mapview" }
zoom = { kind = "number" }
# The controls the map offers. `.zoom` is a +/- pill; `.all` adds a recenter
# button. The card NAMES the affordance and the theme draws it — which is the whole
# reason these are expressible here at all. The L2 card draws its own pill and
# writes `on_click: || ui.themap.nav_zoom_by("0.7")`: a method call on a named
# widget, and the imperative wiring this profile exists to exclude. "This map can be
# zoomed" is a capability; the button, the glyph and the call are presentation.
controls = { kind = "token", tokens = ["none", "zoom", "all"] }
# The start as TWO NUMBERS, for a trip that begins where the device is. `from:`
# names a source that answers a coordinate, which is right whenever a place was
# searched for; a position captured from the fix has no such place. Without these
# the map geocoded the empty origin — `sys.navroute(sys.searchnum("", …))` — so the
# summary said "from here" and the line drew a route from nowhere.
# The trip whose cost labels the drawn route — a source, so the bubble and the line
# describe one journey.
summary = { kind = "path" }
from_lat = { kind = "data" }
from_lon = { kind = "data" }
# A text field — the one role that lets a card receive something the user typed.
#
# `text` is a path into declared card state, never a free binding, and
# `on_commit` carries the typed value as `$value` to a declared transition. So
# text enters through the same total, declared path a tap does, and §4's
# `user-copy` class already names what it is. Without this, a card cannot have a
# search box, which is the other half of why nav could not be written here.
[Field]
text = { kind = "path" }
placeholder = { kind = "data" }
on_commit = { kind = "event" }
# Per KEYSTROKE, where on_commit is per return — results while you type. Costs a
# card re-resolve per character: 18-19 ms on a OnePlus 6 planning screen.
on_change = { kind = "event" }
width = { kind = "width" }
# Where a panel sits when the card is a MAP.
#
# A map card is an overlay: the map fills it and the panels float over it (see
# `l0_surface_map`). `.top` is the band a turn instruction belongs in and `.bottom`
# is the summary sheet — which is how the app this replaces arranges its driving
# screen, and how every map app does. Ignored by a card with no map, where panels
# simply stack.
[Panel]
dock = { kind = "token", tokens = ["top", "bottom", "right"] }
[Card]
on_tap = { kind = "event" }
value = { kind = "any" }
tint = { kind = "token" }
# `width` says which column absorbs the leftover space. A row of columns has to
# divide the line somehow, and only the card knows which one should yield.
[Col]
align = { kind = "token", tokens = ["start", "center", "end"] }
gap = { kind = "number" }
width = { kind = "width" }
# `width` says whether this row FILLS. It fills by default, because a list row
# must — and that defeats a centred parent, which was the one thing a card could
# not say about a row.
[Row]
width = { kind = "width" }
align = { kind = "token", tokens = ["start", "center", "end", "baseline"] }
gap = { kind = "number" }
on_tap = { kind = "event" }
value = { kind = "any" }
[Grid]
cols = { kind = "number" }
# Content that a swipe reveals.
#
# Inside a docked panel, everything in a `Reveal` starts hidden and a swipe up on the
# panel shows it; a swipe down hides it again. That is the shipping nav app's drive
# sheet: a compact time-remaining chip by default, `End` revealed on demand.
#
# It is not card STATE. A state change re-resolves the card, which re-parses the
# document and rebuilds the `MapView` inside it — measured at up to 327 ms of frozen
# map. The reveal is a visibility toggle the renderer wires directly, exactly as the
# L2 card does with `ui.endrow.set_visible`, so the map is never touched.
[Reveal]
[Rule]
# A flexible blank: absorbs the leftover height of the column it sits in.
# Before a bottom bar it pins the bar to the bottom; around a centered stack it
# centers the stack vertically. Zero arguments — its whole meaning is "the
# height that is left".
[Space]
# A full-bleed inverse section band — a month strip, a dark app-bar stripe.
[Band]
text = { kind = "text" }
# One chat message; the side is who said it, the theme styles the parties.
[Bubble]
text = { kind = "text" }
side = { kind = "token" }
# One entry of an in-card chat (profile §5.15): a `sys.chat` row drawn as a
# bubble on the side its role says. Both arguments read the SAME row —
# `ChatEntry(text: m.text, role: m.role)` inside `for m in convo.entries` — so
# the role the host gave a message always labels that message. `role` is
# `user` (the user's side), `model` (the app's agent: the kit marks it
# AI-written) or `host` (a notice from the host). Text is plain, never markup.
[ChatEntry]
text = { kind = "text" }
role = { kind = "path" }
# The floating round action button, pinned over the page corner.
[Fab]
name = { kind = "token" }
# The bottom tab bar and its tabs, pinned to the page floor.
[TabBar]
[Tab]
icon = { kind = "token" }
label = { kind = "text" }
active = { kind = "token" }
# ─── text roles ───────────────────────────────────────────────────────────────
# Seven ROLES. A role says what a piece of text is FOR; the active theme decides
# what it looks like (profile §1.1). `TextHero` is "the one number this card
# exists to show", not white-at-62pt — a different theme answers the same role
# differently, and a card written today should survive that.
#
# A role is a constructor rather than a `font` argument for the same reason: a
# model-authored font_family once dropped CJK coverage and rendered 上海 as tofu.
# Naming the role lets the theme carry the whole glyph fallback chain.
[TextHero]
text = { kind = "text" }
value = { kind = "data" }
unit = { kind = "unit" }
format = { kind = "format" }
on_tap = { kind = "event" }
[TextTitle]
text = { kind = "text" }
width = { kind = "width" }
[TextBody]
text = { kind = "text" }
width = { kind = "width" }
[TextStat]
value = { kind = "data" }
format = { kind = "format" }
tint = { kind = "path" }
[TextRow]
text = { kind = "text" }
width = { kind = "width" }
# The small tracked-caps line above a hero — "T O K Y O". The card supplies
# the words; the caps and the tracking are the theme's.
[TextEyebrow]
text = { kind = "text" }
[TextCaption]
text = { kind = "text" }
value = { kind = "data" }
glyph = { kind = "text" }
suffix = { kind = "text" }
unit = { kind = "unit" }
width = { kind = "width" }
[TextValue]
value = { kind = "data" }
unit = { kind = "unit" }
format = { kind = "format" }
tint = { kind = "path" }
[Tile]
label = { kind = "text" }
value = { kind = "data" }
unit = { kind = "unit" }
format = { kind = "format" }
glyph = { kind = "text" }
shape = { kind = "token" }
[Icon]
name = { kind = "token" }
size = { kind = "token" }
[Avatar]
text = { kind = "text" }
[Chip]
text = { kind = "text" }
on_tap = { kind = "event" }
value = { kind = "any" }
active = { kind = "bool" } # a predicate is a bool operand
# What the action MEANS, not what it looks like. `.danger` is the one that ends
# something — the theme decides that reads red and full-width, because a card that
# named a colour would be stating presentation, which §4 keeps out of the ledger.
tone = { kind = "token", tokens = ["normal", "primary", "danger"] }
# `.fit` on a danger chip names the compact row-scoped variant; without it a
# danger chip is a screen action and spans the sheet (nav's Stop).
width = { kind = "token", tokens = ["fill", "fit", "day", "rank", "temp", "label"] }
# ─── data-visualisation roles ─────────────────────────────────────────────────
# Every argument is a path. These render live data and hold no authored values —
# the class of bug that put an invented `condition` and invented `wmin`/`wmax`
# into shipped cards.
#
# These six cannot be theme compositions (profile §1.1): a gradient bar or a sun
# arc is a fragment shader parameterised by data, and `octoscript-render` notes that
# a DSL node cannot carry shader source, because MPSL compiles at build time.
#
# HOW they reach a backend is unsettled. An earlier version of this comment said
# each backend implements them "exactly as it already does for Map", which
# asserted a contract that does not exist: five of the six are absent from
# octoscript-render's tag table, and its fallback drops the node. Six of six are
# implemented in octos-one today and one in Octoscript-Makepad, so the pipeline
# should be proven there before the vocabulary is fixed.
#
# The card still names only a role and its data. Colour, thickness and cap shape
# remain the backend's, so §1.1 holds here as everywhere else.
[WeatherIcon]
cond = { kind = "path" }
size = { kind = "token", tokens = ["hero", "row", "tile"] }
[TempBar]
lo = { kind = "path" }
hi = { kind = "path" }
min = { kind = "path" }
max = { kind = "path" }
[SunArc]
rise = { kind = "path" }
set = { kind = "path" }
now = { kind = "path" }
[MoonPhase]
phase = { kind = "path" }
illum = { kind = "path" }
# The card supplies a LOCATION, never the field. An earlier version took
# `field`/`index`/`band`, which described an interface the widget deliberately
# abandoned: supplying the field meant sixteen scalar uniforms in the card, and
# as the widget's own comment puts it, "that put a GPU uniform layout into the
# authoring language — it is not a widget contract, it is an ABI". The widget
# fetches its own field now, so a card cannot express one wrongly.
# Live satellite cloud imagery (卫星云图) over a place. Names WHERE; the helper
# answers the image, so a card shows the sky without stating what is in it.
[Satellite]
lat = { kind = "path" }
lon = { kind = "path" }
[AqiContour]
lat = { kind = "path" }
lon = { kind = "path" }
span = { kind = "number" }
# Likewise: the card names WHICH series, and the widget fetches it. Passing
# `points`/`min`/`max` made the card responsible for a scaled series it has no
# way to compute — §4's no-facts rule with the arithmetic hidden in the host.
# Several countries' series on one axis, with a legend. Names WHAT to plot;
# the widget fetches it and scales every series to one viewport.
# A list row's image: a fixed 16:9 tile. `Photo` fills its width (it is a
# backdrop); a row needs a thumbnail beside its text.
[Thumb]
src = { kind = "path" }
shape = { kind = "token" }
# One YouTube search result. `embed` is a player url ready for sys.link — a
# card cannot build one, because L0 has no string concatenation.
[sources."sys.video"]
args = ["query", "count", "fields"]
answers = ["id", "title", "channel", "length", "views", "age", "thumb", "embed"]
[IndicatorPlot]
countries = { kind = "path" }
indicator = { kind = "path" }
years = { kind = "path" }
[StockPlot]
symbol = { kind = "path" }
range = { kind = "unit" }
# ─── shared token sets ────────────────────────────────────────────────────────
# Named kinds referenced above, so a token set is defined once rather than
# repeated at every argument that admits it.
[kinds.unit]
# Admits a token OR a path: `unit: .pct` is fixed, `unit: units` follows card
# state. This is the argument that forced tokens to be lexically marked.
#
# `unit` APPENDS. It is for a value that arrives as a bare number, and a source
# that already formats its answer must not be given one: `sys.route("duration")`
# answers "30 min", so `unit: .duration` on it renders "30 min min". Check what
# the source answers with before adding a unit — `sys.navstep("eta")` answers a
# bare "30" and does need it.
#
# `index` was here and is gone: an index is dimensionless, so there is no honest
# suffix to render — the tile's LABEL says which index it is. A token this list
# admits and no lowering decorates renders nothing, silently.
accepts = ["token", "path"]
tokens = ["c", "f", "pct", "speed", "pressure", "distance", "money", "duration"]
[kinds.format]
accepts = ["token"]
tokens = ["money", "signed_money", "signed_pct", "compact", "ratio", "time", "date"]
# Flat or tilted, while driving. A token OR a path, so a card can fix the camera or
# let its own state choose — which is what makes an on-map 2D/3D toggle a state and a
# guard rather than a whole `Map` per branch.
[kinds.mapview]
accepts = ["token", "path"]
tokens = ["flat", "tilted"]
[kinds.width]
accepts = ["token", "number"]
tokens = ["fill", "fit", "day", "rank", "temp", "label"]
# ─── source capabilities ──────────────────────────────────────────────────────
# The CLOSED set of helpers a `source` may name. Like the constructor catalog
# above, this is derived from the reference cards rather than invented.
#
# Without it, `source_plan` hands an arbitrary dotted name to the host as the
# function to resolve, and a card can write
#
# source leak sys.shell(command: "cat /etc/passwd")
#
# which parses clean and produces a well-formed request. Confinement then rests
# entirely on the host having its own allowlist — the policy boundary §1 claims
# L0 removes. A card must not be able to NAME a capability it was not granted,
# and that is a property the profile can decide rather than delegate.
#
# `args` lists the argument names each helper accepts. An argument outside the
# list is rejected: a helper that silently ignores an unknown argument is how a
# card asks for something it did not get and renders as though it did.
[sources."sys.geocode"]
args = ["name"]
answers = ["lat", "lon", "name", "country", "admin1", "timezone", "population"]
# Live topic detail from Wikipedia's page-summary endpoint. Declared with the
# subject only — `source d sys.wiki(query: "Dune")` — and read as `d.extract`,
# `d.title`, the same shape as any other record source.
[sources."sys.wiki"]
args = ["query"]
answers = ["title", "extract", "description"]
[sources."sys.weather"]
# `day` selects which day the DAILY fields describe: 0 (default) is today,
# 1 is tomorrow. The current-conditions fields (temp, feels, humidity, wind,
# pressure) have no future form — there is no "current" tomorrow — so a card
# that names day: 1 must read `hi`/`lo` instead of `temp`, and the lowering
# refuses to translate a current field under a future day rather than serve
# today's reading beneath a label that says otherwise.
args = ["lat", "lon", "days", "fields", "aggregate", "day"]
answers = ["temp", "feels", "hi", "lo", "cond", "humidity", "wind", "pressure", "uv", "visibility", "precip", "dayname", "days"]
aggregates = ["min_lo", "max_hi"]
[sources."sys.daylight"]
args = ["lat", "lon"]
answers = ["rise", "set", "now"]
[sources."sys.airquality"]
args = ["lat", "lon"]
answers = ["aqi", "pm25", "pm10", "ozone"]
[sources."sys.moonphase"]
args = ["lat", "lon"]
answers = ["phase", "illumination", "name"]
[sources."sys.photo"]
# `query` is a SCENE, not a place. Asked for "Beijing" a generative image model
# invents a city, and it invented one that reads as Taipei — a fabricated
# depiction of a real place, rendering as a plausible photograph, with nothing on
# screen saying it is synthetic. A card that names a place in its title must not
# name it here.
#
# `cond` is what keeps the backdrop honest about the day: pass the forecast's
# condition and the host folds it into the prompt, so a card reading 92 % rain
# does not sit under a sunlit wallpaper. The SCENE is the card's words; the
# WEATHER is the host's fact, and neither writes the other's half.
args = ["query", "cond"]
answers = []
[sources."sys.locale"]
args = []
answers = ["lang", "temp_unit"]
# Nearby venues of one category, around a point. The card names WHERE and WHAT
# KIND; the host answers with a collection.
#
# `sys.places` in the app-card framework is indexed — `sys.places(lat, lon, cat,
# i, "name")` — with a companion `sys.placesnum` for the count and a `-9999`
# sentinel while the fetch is in flight. Neither survives into L0 and neither
# should: a card iterates a declared collection here, and §5.9's `$state` says
# "not yet" without a magic number standing in for it.
# The device's own position. No arguments: a card does not get to ask where
# something else is.
[sources."sys.gps"]
args = []
answers = ["lat", "lon", "accuracy", "ok"]
# Free-text place search, for what a `Field` collected. The query is a path into
# declared state, so the card cannot search for something it did not first
# receive from the user.
[sources."sys.search"]
args = ["query", "count", "fields"]
answers = ["id", "name", "label", "query", "lat", "lon", "distance"]
# A trip's facts — duration, distance, the step list. The ROUTE ITSELF is the
# `Map` widget's to fetch; this is what the card needs to write on the screen
# beside it, and asking twice is cheaper than making the card carry a polyline.
# COORDINATES, not places: a route needs four numbers and an argument carries one
# value, so place names could never resolve into a call.
[sources."sys.route"]
args = ["from_lat", "from_lon", "to_lat", "to_lon", "via", "mode", "fields"]
answers = ["duration", "distance", "steps"]
# Where you are ON a route: the next instruction, and what is left of the trip.
#
# This is the half of navigation a route cannot answer. `sys.route` describes the
# whole trip and never changes as you drive it; this takes the SAME four trip
# coordinates plus the device's own two, and answers relative to them.
#
# `at_lat`/`at_lon` are what keep it honest. The original nav app fed its
# instruction a timer — `sys.navsecs(period) * speed` — so the card announced
# turns for a vehicle moving at an assumed 34 mph whether or not anything moved.
# Progress here is measured: the position is projected onto the route, so an
# instruction only advances because the device did.
[sources."sys.step"]
args = ["from_lat", "from_lon", "to_lat", "to_lon", "at_lat", "at_lon", "via", "fields"]
answers = ["instruction", "remaining", "progress", "eta"]
[sources."sys.places"]
args = ["lat", "lon", "category", "count", "fields"]
answers = ["id", "name", "distance", "lat", "lon", "category"]
[sources."sys.news"]
args = ["count", "offset", "fields"]
answers = ["id", "title", "author", "points", "comments", "url"]
[sources."sys.quakes"]
# The USGS M2.5+ feed of the last 24 hours, newest first. Row-shaped like
# `sys.news`, and for the same reason: a feed is a list, and `offset` is what
# lets a card show a lead event above the rest without repeating it.
#
# `id` is the row's index as a string. USGS ids are opaque and the card only
# needs identity for a `key`, which is what a `for` requires.
args = ["count", "offset", "fields"]
answers = ["id", "mag", "place", "depth", "ago", "lat", "lon"]
[sources."sys.news_item"]
args = ["id", "fields"]
answers = ["id", "title", "author", "points", "comments", "url"]
[sources."sys.news_digest"]
# A search-shaped news feed: `query` and `language` choose what is fetched,
# where `sys.news` only pages a fixed feed. Row-shaped, so a card lists it with
# a `for` over `id`.
args = ["query", "language", "count", "fields"]
answers = ["id", "title", "summary", "publisher", "url", "published_at"]
[sources."sys.news_status"]
# The state of the fetch behind `sys.news_digest`, for the same query. A digest
# arrives after the card is already on screen, so without this a card has no
# way to say whether it is waiting, empty or failed, and would render a blank
# list either way.
args = ["query", "language", "fields"]
answers = ["status", "message", "count"]
[sources."sys.dataset"]
# One prepared research dataset, addressed by `id`. Flat rather than row-shaped:
# the fields are the slots of a single briefing (a headline pair of metrics,
# three picks with their sources, one evidence note), so a card reads them
# directly instead of looping.
args = ["id", "fields"]
answers = ["title", "subtitle", "summary", "coverage", "status", "as_of", "metric1_label", "metric1_value", "metric2_label", "metric2_value", "pick1_title", "pick1_body", "pick1_source", "url1", "pick2_title", "pick2_body", "pick2_source", "url2", "pick3_title", "pick3_body", "pick3_source", "url3", "evidence_title", "evidence_body"]
[sources."sys.digest"]
# A digest the HOST holds for the card's own app: the findings of one agent run
# (a workflow template's result), with the sources it read. The card states
# nothing of its own; every sentence and every link is in the digest.
#
# `app` is a literal app id and must be the app that published the card. Only
# the host knows the publisher, so the host refuses any other and a card can
# never read another app's digests. `id` names one stored run: a literal
# (checked against `[A-Za-z0-9_-]{1,64}`) or a path into state.
#
# One shape whatever template produced the run. `status` is `ready`,
# `partial`, `failed`, `missing` or `expired`; a missing or expired digest is an
# empty record with `$state` `.failed`, never an error. `points` rows are
# `{id, text, label, cite, citations}`: `citations` index into `sources`, and
# `cite` is the same as 1-based text ("1, 3") for a card to show. `sources` rows
# are `{id, n, title, source, url, published_at}`. Model text carries no URLs;
# a link comes only from `sources[].url`. Host-answered only: no backend call.
#
# `summary` and a point's `text` and `label` are MODEL TEXT (§4.2): a text slot
# may show them, marked AI-written, and nothing may act on them.
args = ["app", "id", "fields"]
# The row fields are pooled with the record's, as `sys.weather`'s days are: a
# loop over `d.points` reads only what `fields:` asked for.
answers = ["status", "topic", "language", "summary", "retrieved_at", "count", "points", "sources", "id", "text", "label", "cite", "citations", "n", "title", "source", "url", "published_at"]
[sources."sys.mail_draft"]
# Host-bound Mail draft (profile §5.16). Same app/id checks as sys.digest.
# A written source declares exactly one of to/subject/body; the host accepts
# only actual Field UserInput, checks the bound account/revision, and persists.
# These content fields plus suggestion_body remain model text to the checker.
args = ["app", "id", "fields"]
answers = ["draft_id", "revision", "to", "subject", "body", "status", "chat_thread", "ai_written", "suggestion_id", "suggestion_body"]
writes = ["set"]
[sources."sys.mail_review"]
# Host-bound review request, not approval or a transport call. Set requests
# review of the bound draft; clear cancels review before a send is claimed.
args = ["app", "id", "fields"]
answers = ["status", "operation_id", "draft_id", "revision"]
writes = ["set", "clear"]
[sources."sys.chat"]
# A conversation the HOST holds for the card's own app (profile §5.15): the
# transcript between the user and the app's agent, and the one way to talk to
# it from inside a card.
#
# Scoped like `sys.digest`: `app` is a literal app id and must be the app that
# published the card, and `thread` names one conversation — a literal checked
# against `[A-Za-z0-9_-]{1,64}`, or a path into state.
#
# `entries` rows are `{id, role, text, at}`. `role` is `user`, `model` or
# `host`; `text` is plain text whoever wrote it, and is MODEL TEXT to the
# checker (§4.2): a text slot may show it, and nothing may act on it. Draw a
# transcript with `ChatEntry`, which marks the model's entries AI-written.
#
# `append` is the one write: the payload is what the user committed in a
# `Field`, recorded as a `user` entry. The host runs the agent and appends its
# reply as a `model` entry; a card can never write one. Host-answered only.
args = ["app", "thread", "fields"]
answers = ["status", "count", "entries", "id", "role", "text", "at"]
writes = ["append"]
[sources."sys.movers"]
# `symbols` names the universe to rank; omitted means the whole market. Without
# it a themed request ("top AI movers") could only render market-wide gainers
# under a themed title.
args = ["count", "fields", "symbols"]
answers = ["ticker", "name", "last", "change", "pct", "open", "high", "low", "prev", "volume", "mktcap", "pe", "currency", "exchange"]
[sources."sys.quote"]
args = ["ticker", "fields"]
answers = ["ticker", "name", "last", "change", "pct", "open", "high", "low", "prev", "volume", "mktcap", "pe", "currency", "exchange"]
[sources."sys.series"]
args = ["ticker", "range", "points", "fields", "aggregate"]
# `points` was here and nothing could answer it: a series is not a value, and
# `StockPlot` fetches its own. A catalog entry nothing can deliver is a card the
# checker accepts and the screen renders as em dashes.
answers = ["min", "max"]
aggregates = ["min", "max"]
# ─── durable capabilities (profile §5.12) ─────────────────────────────────────
# Backed by a store rather than by a fetch, and the only ones a transition may
# write. What is STORED is references — a ticker, a token — never the values
# rendered beside them: §4's no-facts rule does not stop applying because the
# data went to disk, and a stored price is wrong a second after it is written.
#
# `sys.watchlist` takes no selector because it IS the user's list. The host reads
# the stored tickers, fetches each quote, and returns the rows — so a card asks
# for the fields it wants to show and never learns a store exists.
# The reading list — §5.12 for NEWS. What is stored is the story's Algolia id,
# an identity the item endpoint serves forever; title, points and the rest are
# fetched by id at read time, so a bookmark saved last month shows today's
# comment count and never a stale copy.
[sources."sys.reading"]
args = ["fields"]
answers = ["id", "title", "author", "points", "comments", "url"]
writes = ["append", "remove"]
# Followed topics. The store keeps only the topic word; top_title/top_points/
# top_id are the first hit of a search run when the row is read.
[sources."sys.topics"]
args = ["fields"]
answers = ["name", "top_title", "top_points", "top_id"]
writes = ["append", "remove"]
# The in-app reader. Writing a url opens the host's native web overlay over
# the card; clear (or system back, host-owned) closes it. `url` answers the
# page currently open, "" when closed.
[sources."sys.link"]
args = ["fields"]
answers = ["url"]
writes = ["set", "clear"]
# One country's reading of a World Bank indicator (keyless). Rows are indexed
# in the order the card listed its countries, so row 0 is the first one named.
# The SERIES itself belongs to IndicatorPlot, which fetches it: a card that
# carried the numbers would be stating facts that go stale on revision.
[sources."sys.indicator"]
args = ["countries", "indicator", "years", "fields"]
answers = ["name", "latest", "first", "change", "min", "max", "year", "title"]
[sources."sys.watchlist"]
# `ticker` turns the source into a membership probe: `has` answers "1" when
# that ticker is in the user's list, else "0" — what lets a quote page show
# Add or Remove for the stock it is looking at.
args = ["ticker", "fields"]
answers = ["ticker", "name", "last", "change", "pct", "open", "high", "low", "prev", "volume", "mktcap", "pe", "currency", "exchange", "has"]
writes = ["append", "remove"]
# READ-ONLY for now. A preference write must name WHICH preference, and a
# transition targets a bare source name — `prefs: set($value)` cannot say
# `units`. That needs a dotted target, which does not exist yet, so a card may
# read a preference and not yet change one.
[sources."sys.prefs"]
args = ["fields"]
# `home`/`work`/`mode` joined `units`/`range` when preferences became writable:
# a place the user calls home, one they call work (both stored as the place's
# NAME — identity, never coordinates the search can't refind), and a default
# travel mode.
answers = ["units", "range", "home", "work", "mode"]
# A write names its key through the DECLARATION: `set`/`clear` are accepted only
# on a prefs source declaring exactly one field, and that field is the key the
# write lands under. `source home_pref sys.prefs(fields: [home])` +
# `home_pref: set($value)` writes `home`; a multi-field source stays read-only.
writes = ["set", "clear"]
# Free-text TICKER lookup. `sys.search` is the PLACE search and answers a
# different question; a card asking a geocoder for a company gets nonsense, so
# they are named apart.
#
# Verified against the live endpoint: `longname` is null for many listings, so
# `name` falls back to the short one. `kind` separates an equity from a crypto
# or an ETF — a search for "nvid" returns all three, and a card that cannot say
# which is offering the user a coin.
[sources."sys.symbol_search"]
args = ["query", "count", "fields"]
answers = ["ticker", "name", "exchange", "kind"]
# The user's saved PLACES. Same shape as `sys.watchlist` — no selector, because
# it IS the list — and the host joins each stored place to a live reading.
#
# A place is stored as a NAME and nothing else — the purest form of the rule
# that a durable collection keeps references, never facts. Coordinates and every
# reading are resolved by the host: geocode the name, then read the weather at
# what comes back, each fetch URL-cached.
#
# An earlier design stored `name|lat|lon` to save the geocode. The card cannot
# produce that: L0 has no way to build a composite value, which is exactly the
# point of it having no expression form. The language was right.
[sources."sys.cities"]
args = ["fields", "unit"]
answers = ["name", "lat", "lon", "temp", "feels", "feels_delta", "hi", "lo", "cond", "humidity", "wind"]
writes = ["append", "remove"]
# Pure conversion; supported pairs and coefficients are owned by the runtime.
# direction is fwd (default) or rev. Unsupported pairs have no value.
[sources."sys.convert"]
args = ["amount", "from", "to", "direction", "fields"]
answers = ["amount", "value"]