@stina/extension-api 0.52.0 → 0.56.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.
@@ -518,6 +518,228 @@ export const ListPropsSchema = z
518
518
  .passthrough()
519
519
  .describe('List component')
520
520
 
521
+ // =============================================================================
522
+ // Purpose-built Display Components
523
+ // =============================================================================
524
+
525
+ /**
526
+ * The skies the weather components know how to draw. Closed on purpose: the
527
+ * icon is chosen from this list, and a string outside it has no picture.
528
+ */
529
+ export const WeatherConditionSchema = z
530
+ .enum([
531
+ 'clear',
532
+ 'partly-cloudy',
533
+ 'cloudy',
534
+ 'overcast',
535
+ 'fog',
536
+ 'light-rain',
537
+ 'rain',
538
+ 'heavy-rain',
539
+ 'sleet',
540
+ 'light-snow',
541
+ 'snow',
542
+ 'heavy-snow',
543
+ 'thunderstorm',
544
+ 'hail',
545
+ 'windy',
546
+ ])
547
+ .describe('Weather condition')
548
+
549
+ export const WeatherWindSchema = z
550
+ .object({
551
+ speed: z.number().describe('Sustained wind speed'),
552
+ unit: z.string().optional().describe('Unit written after the speed. Defaults to m/s'),
553
+ direction: z.string().optional().describe('Compass direction, in the caller\'s own words'),
554
+ gust: z.number().optional().describe('Gust speed, in the same unit'),
555
+ })
556
+ .passthrough()
557
+ .describe('Wind reading')
558
+
559
+ export const WeatherNowPropsSchema = z
560
+ .object({
561
+ component: z.literal('WeatherNow'),
562
+ place: z.string().describe('Where the reading is from'),
563
+ condition: WeatherConditionSchema,
564
+ conditionLabel: z.string().optional().describe('The condition in the user\'s own language'),
565
+ temperature: z.number().describe('Current temperature'),
566
+ feelsLike: z.number().optional().describe('Apparent temperature'),
567
+ unit: z.string().optional().describe('Written after every temperature. Defaults to °'),
568
+ wind: WeatherWindSchema.optional(),
569
+ night: z.boolean().optional().describe('Draw the night variant of the icon'),
570
+ summary: z.string().optional().describe('One line of context under the numbers'),
571
+ style: ExtensionComponentStyleSchema.optional(),
572
+ })
573
+ .passthrough()
574
+ .describe('Current weather component')
575
+
576
+ export const WeatherForecastStepSchema = z
577
+ .object({
578
+ label: z.string().describe('Short step label, e.g. 14 or Tors'),
579
+ condition: WeatherConditionSchema,
580
+ conditionLabel: z.string().optional(),
581
+ high: z.number().describe('The warmer temperature, or the only one'),
582
+ low: z.number().optional().describe('The colder temperature'),
583
+ night: z.boolean().optional(),
584
+ precipitation: z.number().min(0).max(100).optional().describe('Chance of precipitation, 0-100'),
585
+ current: z.boolean().optional().describe('The step the reader is at; drawn larger'),
586
+ })
587
+ .passthrough()
588
+ .describe('One forecast step')
589
+
590
+ export const WeatherForecastPropsSchema = z
591
+ .object({
592
+ component: z.literal('WeatherForecast'),
593
+ title: z.string().optional().describe('Heading above the row'),
594
+ icon: z.string().optional().describe('Icon shown to the left of the title'),
595
+ unit: z.string().optional().describe('Written after every temperature. Defaults to °'),
596
+ steps: z.array(WeatherForecastStepSchema).min(1).describe('Steps, hourly or daily'),
597
+ style: ExtensionComponentStyleSchema.optional(),
598
+ })
599
+ .passthrough()
600
+ .describe('Weather forecast component')
601
+
602
+ export const ChartKindSchema = z.enum(['line', 'bar', 'area']).describe('How the chart draws')
603
+
604
+ export const ChartSeriesSchema = z
605
+ .object({
606
+ name: z.string().optional().describe('Named in the legend'),
607
+ points: z
608
+ .array(z.number().nullable())
609
+ .describe('One value per label. null is a gap, drawn as one'),
610
+ })
611
+ .passthrough()
612
+ .describe('One chart series')
613
+
614
+ /**
615
+ * Eight series is the ceiling because the host palette has eight slots, assigned
616
+ * in a fixed order so a series keeps its colour wherever it appears. A ninth
617
+ * would need a generated hue, and a generated hue is how a chart stops being
618
+ * readable to colour-blind viewers.
619
+ */
620
+ export const ChartPropsSchema = z
621
+ .object({
622
+ component: z.literal('Chart'),
623
+ chart: ChartKindSchema.optional().describe('Defaults to line'),
624
+ series: z.array(ChartSeriesSchema).min(1).max(8).describe('At most eight series'),
625
+ labels: z.array(z.string()).optional().describe('One per point, along the horizontal axis'),
626
+ title: z.string().optional().describe('Heading above the plot'),
627
+ icon: z.string().optional().describe('Icon shown to the left of the title'),
628
+ unit: z.string().optional().describe('Written after every value'),
629
+ zeroBaseline: z.boolean().optional().describe('Force the value axis to include zero'),
630
+ height: z.number().min(4).max(24).optional().describe('Plot height in rem. Defaults to 9'),
631
+ style: ExtensionComponentStyleSchema.optional(),
632
+ })
633
+ .passthrough()
634
+ .describe('Chart component')
635
+
636
+ export const StatTrendSchema = z.enum(['up', 'down', 'flat']).describe('Direction of change')
637
+
638
+ export const StatTilePropsSchema = z
639
+ .object({
640
+ component: z.literal('StatTile'),
641
+ label: z.string().describe('What the number is'),
642
+ value: z.union([z.string(), z.number()]).describe('The number itself'),
643
+ unit: z.string().optional().describe('Written after the value in smaller type'),
644
+ caption: z.string().optional().describe('One quiet line under the number'),
645
+ icon: z.string().optional().describe('Icon name'),
646
+ trend: StatTrendSchema.optional(),
647
+ trendLabel: z.string().optional().describe('The change in words'),
648
+ trendIsGood: z.boolean().optional().describe('Whether up is the good direction. Defaults to true'),
649
+ style: ExtensionComponentStyleSchema.optional(),
650
+ })
651
+ .passthrough()
652
+ .describe('Single-number stat tile component')
653
+
654
+ export const KeyValueRowSchema = z
655
+ .object({
656
+ label: z.string(),
657
+ value: z.string(),
658
+ icon: z.string().optional().describe('Icon name'),
659
+ })
660
+ .passthrough()
661
+ .describe('One key/value row')
662
+
663
+ export const KeyValueListPropsSchema = z
664
+ .object({
665
+ component: z.literal('KeyValueList'),
666
+ rows: z.array(KeyValueRowSchema).min(1).describe('Facts in two aligned columns'),
667
+ style: ExtensionComponentStyleSchema.optional(),
668
+ })
669
+ .passthrough()
670
+ .describe('Key/value list component')
671
+
672
+ export const TimelineVariantSchema = z
673
+ .enum(['default', 'accent', 'success', 'warning', 'danger'])
674
+ .describe('How much a timeline entry stands out')
675
+
676
+ export const TimelineEntrySchema = z
677
+ .object({
678
+ time: z.string().describe('When it happens, worded as it should be read'),
679
+ title: z.string(),
680
+ description: z.string().optional().describe('A line under the title'),
681
+ icon: z.string().optional().describe('Icon name'),
682
+ duration: z.string().optional().describe('How long it lasts, in words'),
683
+ badge: z.string().optional().describe('Trailing badge'),
684
+ current: z.boolean().optional().describe('The entry the reader is at'),
685
+ past: z.boolean().optional().describe('Already behind us; drawn recessive'),
686
+ variant: TimelineVariantSchema.optional(),
687
+ })
688
+ .passthrough()
689
+ .describe('One timeline entry')
690
+
691
+ export const TimelinePropsSchema = z
692
+ .object({
693
+ component: z.literal('Timeline'),
694
+ title: z.string().optional().describe('Heading above the rail'),
695
+ icon: z.string().optional().describe('Icon shown to the left of the title'),
696
+ entries: z.array(TimelineEntrySchema).min(1).describe('Entries, in the order they happen'),
697
+ style: ExtensionComponentStyleSchema.optional(),
698
+ })
699
+ .passthrough()
700
+ .describe('Vertical timeline component')
701
+
702
+ export const NoteVariantSchema = z
703
+ .enum(['default', 'accent', 'success', 'warning', 'danger'])
704
+ .describe('How much a note stands out')
705
+
706
+ export const NotePropsSchema = z
707
+ .object({
708
+ component: z.literal('Note'),
709
+ title: z.string().optional(),
710
+ icon: z.string().optional().describe('Icon shown to the left of the title'),
711
+ badge: z.string().optional().describe('Trailing badge next to the title'),
712
+ content: z.string().describe('The body, as markdown'),
713
+ footer: z.string().optional().describe('One quiet line at the bottom'),
714
+ variant: NoteVariantSchema.optional(),
715
+ style: ExtensionComponentStyleSchema.optional(),
716
+ })
717
+ .passthrough()
718
+ .describe('Titled block of prose component')
719
+
720
+ export const CalendarEventStatusSchema = z
721
+ .enum(['confirmed', 'tentative', 'cancelled'])
722
+ .describe('Whether an event is going ahead')
723
+
724
+ export const CalendarEventPropsSchema = z
725
+ .object({
726
+ component: z.literal('CalendarEvent'),
727
+ title: z.string(),
728
+ start: z.string().describe('ISO 8601 with an offset'),
729
+ end: z.string().optional().describe('ISO 8601 with an offset'),
730
+ allDay: z.boolean().optional(),
731
+ location: z.string().optional(),
732
+ organizer: z.string().optional(),
733
+ attendees: z.array(z.string()).optional(),
734
+ calendar: z.string().optional().describe('Which calendar it sits in; shown as a badge'),
735
+ status: CalendarEventStatusSchema.optional(),
736
+ recurrence: z.string().optional().describe('How it repeats, in words'),
737
+ notes: z.string().optional(),
738
+ style: ExtensionComponentStyleSchema.optional(),
739
+ })
740
+ .passthrough()
741
+ .describe('Single calendar event component')
742
+
521
743
  // =============================================================================
522
744
  // Type Exports
523
745
  // =============================================================================
@@ -562,3 +784,22 @@ export type ConditionalGroupProps = z.infer<typeof ConditionalGroupPropsSchema>
562
784
  export type FrameVariant = z.infer<typeof FrameVariantSchema>
563
785
  export type FrameProps = z.infer<typeof FramePropsSchema>
564
786
  export type ListProps = z.infer<typeof ListPropsSchema>
787
+ export type WeatherCondition = z.infer<typeof WeatherConditionSchema>
788
+ export type WeatherWind = z.infer<typeof WeatherWindSchema>
789
+ export type WeatherNowProps = z.infer<typeof WeatherNowPropsSchema>
790
+ export type WeatherForecastStep = z.infer<typeof WeatherForecastStepSchema>
791
+ export type WeatherForecastProps = z.infer<typeof WeatherForecastPropsSchema>
792
+ export type ChartKind = z.infer<typeof ChartKindSchema>
793
+ export type ChartSeries = z.infer<typeof ChartSeriesSchema>
794
+ export type ChartProps = z.infer<typeof ChartPropsSchema>
795
+ export type StatTrend = z.infer<typeof StatTrendSchema>
796
+ export type StatTileProps = z.infer<typeof StatTilePropsSchema>
797
+ export type KeyValueRow = z.infer<typeof KeyValueRowSchema>
798
+ export type KeyValueListProps = z.infer<typeof KeyValueListPropsSchema>
799
+ export type TimelineVariant = z.infer<typeof TimelineVariantSchema>
800
+ export type TimelineEntry = z.infer<typeof TimelineEntrySchema>
801
+ export type TimelineProps = z.infer<typeof TimelinePropsSchema>
802
+ export type NoteVariant = z.infer<typeof NoteVariantSchema>
803
+ export type NoteProps = z.infer<typeof NotePropsSchema>
804
+ export type CalendarEventStatus = z.infer<typeof CalendarEventStatusSchema>
805
+ export type CalendarEventProps = z.infer<typeof CalendarEventPropsSchema>
@@ -118,6 +118,25 @@ export {
118
118
  MarkdownPropsSchema,
119
119
  ModalPropsSchema,
120
120
  ConditionalGroupPropsSchema,
121
+ WeatherConditionSchema,
122
+ WeatherWindSchema,
123
+ WeatherNowPropsSchema,
124
+ WeatherForecastStepSchema,
125
+ WeatherForecastPropsSchema,
126
+ ChartKindSchema,
127
+ ChartSeriesSchema,
128
+ ChartPropsSchema,
129
+ StatTrendSchema,
130
+ StatTilePropsSchema,
131
+ KeyValueRowSchema,
132
+ KeyValueListPropsSchema,
133
+ TimelineVariantSchema,
134
+ TimelineEntrySchema,
135
+ TimelinePropsSchema,
136
+ NoteVariantSchema,
137
+ NotePropsSchema,
138
+ CalendarEventStatusSchema,
139
+ CalendarEventPropsSchema,
121
140
  type ExtensionComponentData,
122
141
  type ExtensionComponentStyle,
123
142
  type ExtensionActionCall,
@@ -156,3 +175,14 @@ export {
156
175
  type ModalProps,
157
176
  type ConditionalGroupProps,
158
177
  } from './components.schema.js'
178
+
179
+ // Chat card profile - the subset of the DSL a tool may put in a conversation
180
+ export {
181
+ ChatCardSchema,
182
+ ChatCardComponentSchema,
183
+ CHAT_CARD_COMPONENTS,
184
+ validateChatCard,
185
+ describeChatCardProfile,
186
+ type ChatCardComponent,
187
+ type ChatCardValidation,
188
+ } from './card.schema.js'
@@ -536,3 +536,339 @@ export interface ListProps extends ExtensionComponentData {
536
536
  /** Child components to render as list items. Supports iteration. */
537
537
  children: ExtensionComponentChildren
538
538
  }
539
+
540
+ // =============================================================================
541
+ // Purpose-built display components
542
+ // =============================================================================
543
+
544
+ /*
545
+ * The components below differ in kind from the ones above. Those are building
546
+ * blocks — a stack, a label, a button — and what they mean is whatever the
547
+ * author arranges them into. These name a *subject*: weather, a measurement,
548
+ * a series over time. The caller supplies the facts and the host decides how
549
+ * they look, which is what lets the same declaration render as HTML on the web
550
+ * and as native geometry on a phone without either side guessing.
551
+ *
552
+ * That is also why they take no colors and no layout. A chart that let its
553
+ * author pick series colors would be eight different charts across eight
554
+ * extensions; the palette is the host's, assigned in a fixed order, and it has
555
+ * been validated for colour-vision deficiency against the app's own surfaces.
556
+ */
557
+
558
+ /**
559
+ * A sky the DSL knows how to draw.
560
+ *
561
+ * A closed list rather than free text: the drawing has to be chosen from it, and
562
+ * an unrecognised string would leave a hole where the icon goes. Providers name
563
+ * conditions in wildly different vocabularies, so the caller maps theirs onto
564
+ * this list and passes the provider's own words as `conditionLabel` — the enum
565
+ * picks the picture, the label is what gets read.
566
+ */
567
+ export type WeatherCondition =
568
+ | 'clear'
569
+ | 'partly-cloudy'
570
+ | 'cloudy'
571
+ | 'overcast'
572
+ | 'fog'
573
+ | 'light-rain'
574
+ | 'rain'
575
+ | 'heavy-rain'
576
+ | 'sleet'
577
+ | 'light-snow'
578
+ | 'snow'
579
+ | 'heavy-snow'
580
+ | 'thunderstorm'
581
+ | 'hail'
582
+ | 'windy'
583
+
584
+ /** Wind, as shown by the weather components. */
585
+ export interface WeatherWind {
586
+ speed: number
587
+ /** Written after the speed. Defaults to `m/s`. */
588
+ unit?: string
589
+ /** Compass direction as the caller words it, e.g. `NV` or `sydväst`. */
590
+ direction?: string
591
+ /** Gust speed, in the same unit. Shown next to the sustained speed when present. */
592
+ gust?: number
593
+ }
594
+
595
+ /** The extension API properties for the WeatherNow component. */
596
+ export interface WeatherNowProps extends ExtensionComponentData {
597
+ component: 'WeatherNow'
598
+ /** Where the reading is from. */
599
+ place: string
600
+ condition: WeatherCondition
601
+ /** The condition in the user's own language. Falls back to the host's wording for `condition`. */
602
+ conditionLabel?: string
603
+ temperature: number
604
+ /** What it feels like, when that differs enough to be worth saying. */
605
+ feelsLike?: number
606
+ /** Written after every temperature. Defaults to `°`. */
607
+ unit?: string
608
+ wind?: WeatherWind
609
+ /** Draws the night variant of the icon. Only affects conditions that have one. */
610
+ night?: boolean
611
+ /** One line of context under the numbers, e.g. "Regn från tretiden". */
612
+ summary?: string
613
+ }
614
+
615
+ /**
616
+ * One step of a forecast.
617
+ *
618
+ * Deliberately the same shape whether the steps are hours or days — the only
619
+ * difference is what `label` says and whether there are two temperatures. A
620
+ * separate hourly component would have been the same component twice.
621
+ */
622
+ export interface WeatherForecastStep {
623
+ /** Short: `14`, `Tors`, `Nu`. */
624
+ label: string
625
+ condition: WeatherCondition
626
+ conditionLabel?: string
627
+ /** The warmer of the two, or the only one for a single-temperature step. */
628
+ high: number
629
+ /** The colder one. Absent when the step has a single temperature. */
630
+ low?: number
631
+ night?: boolean
632
+ /** Chance of precipitation as a percentage, 0–100. */
633
+ precipitation?: number
634
+ /**
635
+ * The step the reader is at — this hour, today. Drawn larger than its
636
+ * neighbours, so the eye lands on it before reading the row.
637
+ *
638
+ * Same word as `TimelineEntry.current`, and the same meaning: mark one, and
639
+ * only one. Marking several is marking none.
640
+ */
641
+ current?: boolean
642
+ }
643
+
644
+ /** The extension API properties for the WeatherForecast component. */
645
+ export interface WeatherForecastProps extends ExtensionComponentData {
646
+ component: 'WeatherForecast'
647
+ /** Heading above the row, e.g. "Kommande timmar". */
648
+ title?: string
649
+ /** Icon shown to the left of the title. */
650
+ icon?: HugeIconName
651
+ /** Written after every temperature. Defaults to `°`. */
652
+ unit?: string
653
+ steps: WeatherForecastStep[]
654
+ }
655
+
656
+ /** How a chart draws its series. */
657
+ export type ChartKind = 'line' | 'bar' | 'area'
658
+
659
+ /**
660
+ * One series of a chart.
661
+ *
662
+ * `points` lines up with the chart's `labels` by position. A `null` is a gap in
663
+ * the data and is drawn as one — a missing reading joined up as if it were a
664
+ * value is a lie the chart would tell on the caller's behalf.
665
+ */
666
+ export interface ChartSeries {
667
+ /** Named in the legend. Required in practice once there is more than one series. */
668
+ name?: string
669
+ points: Array<number | null>
670
+ }
671
+
672
+ /**
673
+ * The extension API properties for the Chart component.
674
+ *
675
+ * There is no colour option and no second axis, on purpose. Colours come from
676
+ * the host's categorical palette in slot order, so the same series keeps the
677
+ * same colour everywhere and the whole set stays legible to colour-blind
678
+ * readers. Two measurements of different magnitude are two charts.
679
+ */
680
+ export interface ChartProps extends ExtensionComponentData {
681
+ component: 'Chart'
682
+ /** Defaults to `line`. */
683
+ chart?: ChartKind
684
+ /** At most eight. Past that the ninth would need a colour the palette does not have. */
685
+ series: ChartSeries[]
686
+ /** One per point, along the horizontal axis. */
687
+ labels?: string[]
688
+ /** Heading above the plot. With a single series this is what names it, so no legend is drawn. */
689
+ title?: string
690
+ /** Icon shown to the left of the title. */
691
+ icon?: HugeIconName
692
+ /** Written after every value, e.g. `°`, ` mm`, ` kr`. */
693
+ unit?: string
694
+ /**
695
+ * Whether the value axis must include zero. Defaults to true for `bar` and
696
+ * `area`, where the fill's height is the reading, and false for `line`, where
697
+ * forcing zero can flatten the shape the chart exists to show.
698
+ */
699
+ zeroBaseline?: boolean
700
+ /** Plot height in rem. Defaults to 9. */
701
+ height?: number
702
+ }
703
+
704
+ /** Which way a stat tile's trend points. */
705
+ export type StatTrend = 'up' | 'down' | 'flat'
706
+
707
+ /**
708
+ * The extension API properties for the StatTile component.
709
+ *
710
+ * For the single number that does not need a plot. A chart of one value is a
711
+ * chart that spends a lot of space saying very little.
712
+ */
713
+ export interface StatTileProps extends ExtensionComponentData {
714
+ component: 'StatTile'
715
+ /** What the number is, e.g. "Förbrukning i går". */
716
+ label: string
717
+ value: string | number
718
+ /** Written after the value in smaller type, e.g. `kWh`. */
719
+ unit?: string
720
+ /** One quiet line under the number. */
721
+ caption?: string
722
+ icon?: HugeIconName
723
+ /** `flat` draws no arrow. */
724
+ trend?: StatTrend
725
+ /** The change in words, e.g. "+12 % mot förra veckan". Shown beside the arrow. */
726
+ trendLabel?: string
727
+ /**
728
+ * Whether up is the good direction. Defaults to true. Colours the trend;
729
+ * the arrow and the label carry the same meaning without it.
730
+ */
731
+ trendIsGood?: boolean
732
+ }
733
+
734
+ /** One row of a KeyValueList. */
735
+ export interface KeyValueRow {
736
+ label: string
737
+ value: string
738
+ icon?: HugeIconName
739
+ }
740
+
741
+ /**
742
+ * The extension API properties for the KeyValueList component.
743
+ *
744
+ * Facts in two columns, aligned. What a stack of `HorizontalStack`s was always
745
+ * being used for, without each caller having to get the alignment right.
746
+ */
747
+ export interface KeyValueListProps extends ExtensionComponentData {
748
+ component: 'KeyValueList'
749
+ rows: KeyValueRow[]
750
+ }
751
+
752
+ /** How much a timeline entry stands out. */
753
+ export type TimelineVariant = 'default' | 'accent' | 'success' | 'warning' | 'danger'
754
+
755
+ /**
756
+ * One entry on a timeline.
757
+ *
758
+ * `time` is a string, not a timestamp, because the component draws order rather
759
+ * than duration: entries sit evenly on the rail whether they are minutes or
760
+ * decades apart. That is what makes the same component work for today's
761
+ * calendar and for a project's milestones, and it means the caller writes the
762
+ * time the way it should be read — `09:00`, `Tors 14 mar`, `2019`.
763
+ */
764
+ export interface TimelineEntry {
765
+ /** When it happens, worded as it should be read. */
766
+ time: string
767
+ title: string
768
+ /** A line under the title. */
769
+ description?: string
770
+ icon?: HugeIconName
771
+ /** How long it lasts, in the caller's own words: `1 h`, `3 dagar`. */
772
+ duration?: string
773
+ /** Trailing badge, e.g. a place or a category. */
774
+ badge?: string
775
+ /** Marks the entry the reader is at. Drawn with a filled marker and a heavier rule. */
776
+ current?: boolean
777
+ /** Already behind us. Drawn recessive, so what is left to come reads first. */
778
+ past?: boolean
779
+ variant?: TimelineVariant
780
+ }
781
+
782
+ /**
783
+ * The extension API properties for the Timeline component.
784
+ *
785
+ * A vertical rail with one marker per entry. For a day's events, or for the
786
+ * order things happened in over a longer stretch.
787
+ */
788
+ export interface TimelineProps extends ExtensionComponentData {
789
+ component: 'Timeline'
790
+ /** Heading above the rail, e.g. "I dag". */
791
+ title?: string
792
+ /** Icon shown to the left of the title. */
793
+ icon?: HugeIconName
794
+ entries: TimelineEntry[]
795
+ }
796
+
797
+ /** How much a note stands out. */
798
+ export type NoteVariant = 'default' | 'accent' | 'success' | 'warning' | 'danger'
799
+
800
+ /**
801
+ * The extension API properties for the Note component.
802
+ *
803
+ * A titled block of prose: a heading, markdown, and a quiet line at the bottom.
804
+ * For the answer that *is* text but wants a shape — one of five news summaries,
805
+ * one of three things worth doing this weekend — where a single long reply would
806
+ * run them all together and the reader would lose which sentence belonged to
807
+ * which item.
808
+ *
809
+ * The body is markdown rather than components because prose is what it is for.
810
+ * A note that wants a chart in it is a `Frame` with a `Note` and a `Chart`
811
+ * inside; keeping those separate is what stops this from becoming a second,
812
+ * worse layout system.
813
+ */
814
+ export interface NoteProps extends ExtensionComponentData {
815
+ component: 'Note'
816
+ title?: string
817
+ /** Icon shown to the left of the title. */
818
+ icon?: HugeIconName
819
+ /** Trailing badge next to the title: a source, a category, a time. */
820
+ badge?: string
821
+ /** The body, as markdown. */
822
+ content: string
823
+ /** One quiet line at the bottom: where it came from, when, or a caveat. */
824
+ footer?: string
825
+ /** Colours the rail down the left edge. Defaults to none. */
826
+ variant?: NoteVariant
827
+ }
828
+
829
+ /** Whether an event is going ahead. */
830
+ export type CalendarEventStatus = 'confirmed' | 'tentative' | 'cancelled'
831
+
832
+ /**
833
+ * The extension API properties for the CalendarEvent component.
834
+ *
835
+ * One event, shown properly. It is the thing Stina most often has to say
836
+ * something *about* — a meeting starting shortly, a booking she is reminding you
837
+ * of — and a sentence carrying a time, a place and four names is a sentence
838
+ * nobody reads to the end.
839
+ *
840
+ * For a whole day, use `Timeline`. This is the single event that has the floor.
841
+ *
842
+ * `start` and `end` are timestamps rather than the worded strings `Timeline`
843
+ * takes, and that is the one place in the DSL where live computation is right:
844
+ * the card is *about* a moment, so the host works out "om tolv minuter" and
845
+ * "pågår nu" and keeps them true. A caller who wrote those as text would have
846
+ * written a card that lies the moment it is scrolled back to.
847
+ */
848
+ export interface CalendarEventProps extends ExtensionComponentData {
849
+ component: 'CalendarEvent'
850
+ title: string
851
+ /** ISO 8601, with an offset. The host formats it in the reader's own locale. */
852
+ start: string
853
+ /** ISO 8601, with an offset. Absent for an event with no stated end. */
854
+ end?: string
855
+ /** Draws a day rather than a clock time. */
856
+ allDay?: boolean
857
+ location?: string
858
+ /** Who called it. */
859
+ organizer?: string
860
+ /** Names as the calendar has them. Shown in full up to a few, then counted. */
861
+ attendees?: string[]
862
+ /** Which calendar it sits in. Shown as a badge. */
863
+ calendar?: string
864
+ /** Defaults to `confirmed`. */
865
+ status?: CalendarEventStatus
866
+ /**
867
+ * How it repeats, in words: `Varje tisdag`. The recurrence rule itself is not
868
+ * something this component parses — the caller already knows what it means,
869
+ * and a component guessing at an RRULE would get the exceptions wrong.
870
+ */
871
+ recurrence?: string
872
+ /** One short line of context. */
873
+ notes?: string
874
+ }
@@ -320,7 +320,21 @@ export interface UserProfile {
320
320
  * User API for profile access
321
321
  */
322
322
  export interface UserAPI {
323
- getProfile(): Promise<UserProfile>
323
+ /**
324
+ * Read a user's profile, including the timezone their clock times are written in.
325
+ *
326
+ * Pass the id of the user this work belongs to — `ctx.userId` in a background task,
327
+ * `context.userId` in a tool call, the fired job's `userId` in a scheduler callback.
328
+ * A host serving more than one user cannot guess which profile was meant, and answers
329
+ * with an empty profile when the id is left out.
330
+ *
331
+ * This matters most for `timezone`. An extension that turns a wall-clock time into an
332
+ * instant without it falls back to the machine's timezone, which on a server is UTC,
333
+ * and schedules the user's 11:30 for 11:30 UTC.
334
+ *
335
+ * @param userId The user whose profile to read. Optional only for single-user hosts.
336
+ */
337
+ getProfile(userId?: string): Promise<UserProfile>
324
338
  listIds(): Promise<string[]>
325
339
  }
326
340
 
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ExecutionContext } from './types.context.js'
8
+ import type { ExtensionComponentData } from './types.components.js'
8
9
 
9
10
  /**
10
11
  * Tool implementation
@@ -39,6 +40,20 @@ export interface ToolResult {
39
40
  message?: string
40
41
  /** Error message if failed */
41
42
  error?: string
43
+ /**
44
+ * Something to show the user that is not text.
45
+ *
46
+ * A component from the chat card profile — a chart, a weather reading, a
47
+ * timeline. It is rendered in the conversation where the tool ran, and it is
48
+ * *not* part of what goes back to the model: the model already knows what it
49
+ * asked to be shown, and sending the drawing back would spend the tokens
50
+ * twice and invite it to summarise its own picture.
51
+ *
52
+ * Validate with `validateChatCard` before returning one. A card that fails the
53
+ * profile is dropped by the host rather than half-rendered, so a tool that
54
+ * skips the check finds out from the user, which is the wrong end.
55
+ */
56
+ display?: ExtensionComponentData
42
57
  }
43
58
 
44
59
  /**