Skip to main content

tern_sdk/ui/
data.rs

1//! Structured prop values: table columns and rows, tree items, list and
2//! picker entries, meter parts, tool and agent facts, checklist phases,
3//! `prefs` pages, menu entries, and the items of the newer kinds (options,
4//! crumbs, nav items, trace lines, stack frames, questions). Every field is
5//! public; optional ones are omitted from the wire when `None` or empty.
6
7use std::collections::BTreeMap;
8
9use serde::{Serialize, Serializer, ser::SerializeMap};
10use serde_json::Value;
11
12use super::{
13	icon::Icon,
14	types::{
15		AvatarVariant, ButtonVariant, ChoiceStyle, ColumnFormat, Disabled, ItemStatus, SortDir,
16		Status, Text, TextAlign, Tone, Truncate,
17	},
18};
19
20/// One `kv` pair.
21#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
22pub struct KvItem {
23	/// The key.
24	pub k: Text,
25	/// The value.
26	pub v: Text,
27}
28
29/// A `table` column.
30#[derive(Clone, Debug, Default, PartialEq, Serialize)]
31pub struct Column {
32	/// The key of this column's cell in each row.
33	pub id:       String,
34	/// Header text.
35	#[serde(skip_serializing_if = "Option::is_none")]
36	pub head:     Option<Text>,
37	/// Alignment of the header and cells.
38	#[serde(skip_serializing_if = "Option::is_none")]
39	pub align:    Option<TextAlign>,
40	/// Where a cell that doesn't fit is cut.
41	#[serde(skip_serializing_if = "Option::is_none")]
42	pub truncate: Option<Truncate>,
43	/// Lower hides first when the table is narrow.
44	#[serde(skip_serializing_if = "Option::is_none")]
45	pub priority: Option<f64>,
46	/// Share of the spare width.
47	#[serde(skip_serializing_if = "Option::is_none")]
48	pub grow:     Option<f64>,
49	/// Cells in the mono face (ids, hashes, numbers).
50	#[serde(skip_serializing_if = "Option::is_none")]
51	pub mono:     Option<bool>,
52	/// Its header takes clicks and sends `action` `sort`.
53	#[serde(skip_serializing_if = "Option::is_none")]
54	pub sortable: Option<bool>,
55}
56
57impl Column {
58	/// A column with header `head`.
59	pub fn new(id: impl Into<String>, head: impl Into<Text>) -> Self {
60		Self { id: id.into(), head: Some(head.into()), ..Self::default() }
61	}
62
63	/// Sets the alignment.
64	pub fn align(mut self, align: impl Into<TextAlign>) -> Self {
65		self.align = Some(align.into());
66		self
67	}
68
69	/// Sets the grow share.
70	pub const fn grow(mut self, grow: f64) -> Self {
71		self.grow = Some(grow);
72		self
73	}
74
75	/// Sets the hide priority.
76	pub const fn priority(mut self, priority: f64) -> Self {
77		self.priority = Some(priority);
78		self
79	}
80
81	/// Sets where cells are cut.
82	pub fn truncate(mut self, truncate: impl Into<Truncate>) -> Self {
83		self.truncate = Some(truncate.into());
84		self
85	}
86
87	/// Draws the cells in the mono face.
88	pub const fn mono(mut self, mono: bool) -> Self {
89		self.mono = Some(mono);
90		self
91	}
92
93	/// Makes the header sort the table on click.
94	pub const fn sortable(mut self, sortable: bool) -> Self {
95		self.sortable = Some(sortable);
96		self
97	}
98}
99
100/// A stacked part of a meter or meter cell.
101#[derive(Clone, Debug, Default, PartialEq, Serialize)]
102pub struct Part {
103	/// The part's share.
104	pub value: f64,
105	/// Its color token.
106	#[serde(skip_serializing_if = "Option::is_none")]
107	pub token: Option<String>,
108	/// Its tooltip label.
109	#[serde(skip_serializing_if = "Option::is_none")]
110	pub label: Option<String>,
111	/// Diagonal hatching.
112	#[serde(skip_serializing_if = "Option::is_none")]
113	pub hatch: Option<bool>,
114}
115
116/// Fill levels that tint a meter.
117#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
118pub struct Thresholds {
119	/// `warn` at or above this.
120	#[serde(skip_serializing_if = "Option::is_none")]
121	pub warn: Option<f64>,
122	/// `bad` at or above this.
123	#[serde(skip_serializing_if = "Option::is_none")]
124	pub bad:  Option<f64>,
125}
126
127/// A table cell drawn as a meter.
128#[derive(Clone, Debug, Default, PartialEq, Serialize)]
129pub struct MeterCell {
130	/// The fill, 0–1.
131	#[serde(skip_serializing_if = "Option::is_none")]
132	pub value:      Option<f64>,
133	/// Stacked fills.
134	#[serde(skip_serializing_if = "Vec::is_empty")]
135	pub parts:      Vec<Part>,
136	/// Tint levels.
137	#[serde(skip_serializing_if = "Option::is_none")]
138	pub thresholds: Option<Thresholds>,
139	/// The fill's tone.
140	#[serde(skip_serializing_if = "Option::is_none")]
141	pub tone:       Option<Tone>,
142	/// The tooltip.
143	#[serde(skip_serializing_if = "Option::is_none")]
144	pub title:      Option<String>,
145}
146
147/// A table cell: text with a status dot or icon before it.
148#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
149pub struct TextCell {
150	/// The text.
151	pub text:  Text,
152	/// An icon before the text, colored by `tone`.
153	#[serde(skip_serializing_if = "Option::is_none")]
154	pub icon:  Option<Icon>,
155	/// A status dot before the text, in this tone.
156	#[serde(skip_serializing_if = "Option::is_none")]
157	pub dot:   Option<Tone>,
158	/// The icon's color.
159	#[serde(skip_serializing_if = "Option::is_none")]
160	pub tone:  Option<Tone>,
161	/// The tooltip.
162	#[serde(skip_serializing_if = "Option::is_none")]
163	pub title: Option<String>,
164}
165
166impl TextCell {
167	/// A cell of `text`.
168	pub fn new(text: impl Into<Text>) -> Self {
169		Self { text: text.into(), ..Self::default() }
170	}
171
172	/// Sets the icon.
173	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
174		self.icon = Some(icon.into());
175		self
176	}
177
178	/// Sets the status dot's tone.
179	pub fn dot(mut self, tone: impl Into<Tone>) -> Self {
180		self.dot = Some(tone.into());
181		self
182	}
183
184	/// Sets the icon's tone.
185	pub fn tone(mut self, tone: impl Into<Tone>) -> Self {
186		self.tone = Some(tone.into());
187		self
188	}
189
190	/// Sets the tooltip.
191	pub fn title(mut self, title: impl Into<String>) -> Self {
192		self.title = Some(title.into());
193		self
194	}
195}
196
197/// A table cell drawn as a sm `button`. Its click sends an `action` event
198/// with `act` and the row as `item`, and doesn't select the row.
199#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
200pub struct ButtonCell {
201	/// The label.
202	pub text:     String,
203	/// The action name sent.
204	pub act:      String,
205	/// A leading icon.
206	#[serde(skip_serializing_if = "Option::is_none")]
207	pub icon:     Option<Icon>,
208	/// Keycaps after the label.
209	#[serde(skip_serializing_if = "Vec::is_empty")]
210	pub keys:     Vec<String>,
211	/// The chrome.
212	#[serde(skip_serializing_if = "Option::is_none")]
213	pub variant:  Option<ButtonVariant>,
214	/// Dimmed and silent; a reason is the tooltip.
215	#[serde(skip_serializing_if = "Option::is_none")]
216	pub disabled: Option<Disabled>,
217}
218
219impl ButtonCell {
220	/// A button labeled `text` sending `act`.
221	pub fn new(text: impl Into<String>, act: impl Into<String>) -> Self {
222		Self { text: text.into(), act: act.into(), ..Self::default() }
223	}
224
225	/// Sets the icon.
226	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
227		self.icon = Some(icon.into());
228		self
229	}
230
231	/// Sets the keycaps.
232	pub fn keys<K: Into<String>>(mut self, keys: impl IntoIterator<Item = K>) -> Self {
233		self.keys = keys.into_iter().map(Into::into).collect();
234		self
235	}
236
237	/// Sets the chrome.
238	pub fn variant(mut self, variant: impl Into<ButtonVariant>) -> Self {
239		self.variant = Some(variant.into());
240		self
241	}
242
243	/// Disables it (`true` or a reason).
244	pub fn disabled(mut self, disabled: impl Into<Disabled>) -> Self {
245		self.disabled = Some(disabled.into());
246		self
247	}
248}
249
250/// A table cell drawn as a sparkline.
251#[derive(Clone, Debug, Default, PartialEq, Serialize)]
252pub struct SparkCell {
253	/// The values, oldest first.
254	pub values: Vec<f64>,
255	/// The line's tone (default accent).
256	#[serde(skip_serializing_if = "Option::is_none")]
257	pub tone:   Option<Tone>,
258	/// The bottom of the scale (default the smallest value).
259	#[serde(skip_serializing_if = "Option::is_none")]
260	pub min:    Option<f64>,
261	/// The top of the scale (default the largest value).
262	#[serde(skip_serializing_if = "Option::is_none")]
263	pub max:    Option<f64>,
264	/// The tooltip.
265	#[serde(skip_serializing_if = "Option::is_none")]
266	pub title:  Option<String>,
267}
268
269impl SparkCell {
270	/// A sparkline of `values`.
271	pub fn new(values: impl Into<Vec<f64>>) -> Self {
272		Self { values: values.into(), ..Self::default() }
273	}
274
275	/// Sets the tone.
276	pub fn tone(mut self, tone: impl Into<Tone>) -> Self {
277		self.tone = Some(tone.into());
278		self
279	}
280
281	/// Sets the scale.
282	pub const fn range(mut self, min: f64, max: f64) -> Self {
283		self.min = Some(min);
284		self.max = Some(max);
285		self
286	}
287}
288
289/// A table cell: spans, a meter, decorated text, a badge, a button or a
290/// sparkline.
291#[derive(Clone, Debug, PartialEq)]
292pub enum Cell {
293	/// Text.
294	Text(Text),
295	/// A meter.
296	Meter(MeterCell),
297	/// Text with a dot or icon.
298	Rich(TextCell),
299	/// A badge.
300	Badge(BadgeSpec),
301	/// A sm button.
302	Button(ButtonCell),
303	/// A sparkline.
304	Spark(SparkCell),
305}
306
307impl Serialize for Cell {
308	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
309		fn one<S: Serializer>(s: S, key: &str, value: &impl Serialize) -> Result<S::Ok, S::Error> {
310			let mut map = s.serialize_map(Some(1))?;
311			map.serialize_entry(key, value)?;
312			map.end()
313		}
314		match self {
315			Self::Text(t) => t.serialize(s),
316			Self::Rich(c) => c.serialize(s),
317			Self::Meter(m) => one(s, "meter", m),
318			Self::Badge(b) => one(s, "badge", b),
319			Self::Button(b) => one(s, "button", b),
320			Self::Spark(c) => one(s, "spark", c),
321		}
322	}
323}
324
325impl<T: Into<Text>> From<T> for Cell {
326	fn from(t: T) -> Self {
327		Self::Text(t.into())
328	}
329}
330
331impl From<MeterCell> for Cell {
332	fn from(m: MeterCell) -> Self {
333		Self::Meter(m)
334	}
335}
336
337impl From<TextCell> for Cell {
338	fn from(c: TextCell) -> Self {
339		Self::Rich(c)
340	}
341}
342
343impl From<BadgeSpec> for Cell {
344	fn from(b: BadgeSpec) -> Self {
345		Self::Badge(b)
346	}
347}
348
349impl From<ButtonCell> for Cell {
350	fn from(b: ButtonCell) -> Self {
351		Self::Button(b)
352	}
353}
354
355impl From<SparkCell> for Cell {
356	fn from(c: SparkCell) -> Self {
357		Self::Spark(c)
358	}
359}
360
361/// A `table` row.
362#[derive(Clone, Debug, Default, PartialEq, Serialize)]
363pub struct TableRow {
364	/// Row identity, unique in the table.
365	pub id:       String,
366	/// One cell per column id.
367	pub cells:    BTreeMap<String, Cell>,
368	/// Dimmed, can't be selected or activated; a reason is the tooltip.
369	#[serde(skip_serializing_if = "Option::is_none")]
370	pub disabled: Option<Disabled>,
371	/// Right-click actions, each sent as an `action` with the row as `item`.
372	#[serde(skip_serializing_if = "Vec::is_empty")]
373	pub menu:     Vec<String>,
374	/// The row's tooltip.
375	#[serde(skip_serializing_if = "Option::is_none")]
376	pub title:    Option<String>,
377}
378
379impl TableRow {
380	/// An empty row.
381	pub fn new(id: impl Into<String>) -> Self {
382		Self { id: id.into(), ..Self::default() }
383	}
384
385	/// Disables the row (`true` or a reason).
386	pub fn disabled(mut self, disabled: impl Into<Disabled>) -> Self {
387		self.disabled = Some(disabled.into());
388		self
389	}
390
391	/// Adds a right-click action.
392	pub fn menu(mut self, act: impl Into<String>) -> Self {
393		self.menu.push(act.into());
394		self
395	}
396
397	/// Sets the tooltip.
398	pub fn title(mut self, title: impl Into<String>) -> Self {
399		self.title = Some(title.into());
400		self
401	}
402
403	/// Sets the cell of column `col`.
404	pub fn cell(mut self, col: impl Into<String>, cell: impl Into<Cell>) -> Self {
405		self.cells.insert(col.into(), cell.into());
406		self
407	}
408}
409
410/// A `tree` item.
411#[derive(Clone, Debug, Default, PartialEq, Serialize)]
412pub struct TreeItem {
413	/// Identity, unique in the tree.
414	pub id:       String,
415	/// The row's text.
416	pub label:    Text,
417	/// An icon before the label.
418	#[serde(skip_serializing_if = "Option::is_none")]
419	pub icon:     Option<Icon>,
420	/// Whether the children show.
421	#[serde(skip_serializing_if = "Option::is_none")]
422	pub open:     Option<bool>,
423	/// Nested items.
424	#[serde(skip_serializing_if = "Vec::is_empty")]
425	pub children: Vec<Self>,
426}
427
428impl TreeItem {
429	/// An item labeled `label`.
430	pub fn new(id: impl Into<String>, label: impl Into<Text>) -> Self {
431		Self { id: id.into(), label: label.into(), ..Self::default() }
432	}
433
434	/// Adds a child item.
435	pub fn child(mut self, item: Self) -> Self {
436		self.children.push(item);
437		self
438	}
439
440	/// Sets whether the children show.
441	pub const fn open(mut self, open: bool) -> Self {
442		self.open = Some(open);
443		self
444	}
445}
446
447/// A tab of a `tabs` strip.
448#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
449pub struct Tab {
450	/// The tab's id.
451	pub id:    String,
452	/// Its label.
453	pub label: Text,
454}
455
456/// A code line mark.
457#[derive(Clone, Debug, Default, PartialEq, Serialize)]
458pub struct CodeMark {
459	/// The line, in the gutter's numbering.
460	pub line:   u64,
461	/// The tone.
462	#[serde(skip_serializing_if = "Option::is_none")]
463	pub tone:   Option<Tone>,
464	/// A score 0–1 on the heat ramp: the overview's bar, the fold sparkline,
465	/// and the row's wash without `wash`; with no `tone`, no bar.
466	#[serde(skip_serializing_if = "Option::is_none")]
467	pub heat:   Option<f64>,
468	/// Character ranges `[from, to)` marked on the line.
469	#[serde(skip_serializing_if = "Vec::is_empty")]
470	pub ranges: Vec<[u64; 2]>,
471	/// Scores 0–1 washing the row on the heat ramp: one flat, several a
472	/// gradient across it, left to right; in place of `heat`'s wash.
473	#[serde(skip_serializing_if = "Vec::is_empty")]
474	pub wash:   Vec<f64>,
475}
476
477/// A run of lines a code block draws as one row until it is opened.
478#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
479pub struct CodeFold {
480	/// The first folded line, in the gutter's numbering.
481	pub from:      u64,
482	/// The last folded line, in the gutter's numbering.
483	pub to:        u64,
484	/// The folded row's text, in the code's face (default `… [lines
485	/// {from}-{to}]`, `… [line {from}]` for one line).
486	#[serde(skip_serializing_if = "Option::is_none")]
487	pub label:     Option<String>,
488	/// What the fold hides, after a sparkline of its lines' heat.
489	#[serde(skip_serializing_if = "Option::is_none")]
490	pub note:      Option<Text>,
491	/// `error` tints the row; any tone colors the note.
492	#[serde(skip_serializing_if = "Option::is_none")]
493	pub tone:      Option<Tone>,
494	/// Drawn open; a click on the folded row (or an open fold's head)
495	/// toggles it in place.
496	#[serde(skip_serializing_if = "Option::is_none")]
497	pub open:      Option<bool>,
498	/// A card shown while the pointer rests on the folded row: an id-less
499	/// node tree, as [`Node::to_json`](super::Node::to_json) gives.
500	#[serde(skip_serializing_if = "Option::is_none")]
501	pub hovercard: Option<Value>,
502}
503
504/// A diff hunk given directly.
505#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
506#[serde(rename_all = "camelCase")]
507pub struct Hunk {
508	/// The old side's first line.
509	#[serde(skip_serializing_if = "Option::is_none")]
510	pub old_start: Option<u64>,
511	/// The new side's first line.
512	#[serde(skip_serializing_if = "Option::is_none")]
513	pub new_start: Option<u64>,
514	/// Lines starting with `+`, `-` or a space.
515	pub lines:     Vec<String>,
516}
517
518/// A styled range of an editor's text (UTF-16 offsets).
519#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
520pub struct Decor {
521	/// Start.
522	pub from: usize,
523	/// End.
524	pub to:   usize,
525	/// Style tokens.
526	pub s:    String,
527	/// An effect.
528	#[serde(skip_serializing_if = "Option::is_none")]
529	pub fx:   Option<super::types::Fx>,
530}
531
532/// A shimmer's sweep colors, as span tokens.
533#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
534pub struct ShimmerPalette {
535	/// The low color.
536	#[serde(skip_serializing_if = "Option::is_none")]
537	pub low:  Option<String>,
538	/// The middle color.
539	#[serde(skip_serializing_if = "Option::is_none")]
540	pub mid:  Option<String>,
541	/// The high color.
542	#[serde(skip_serializing_if = "Option::is_none")]
543	pub high: Option<String>,
544}
545
546/// A tick on a meter.
547#[derive(Clone, Debug, Default, PartialEq, Serialize)]
548pub struct MeterMark {
549	/// Position, 0–1.
550	pub at:    f64,
551	/// Tick color.
552	#[serde(skip_serializing_if = "Option::is_none")]
553	pub tone:  Option<Tone>,
554	/// Tooltip.
555	#[serde(skip_serializing_if = "Option::is_none")]
556	pub title: Option<String>,
557	/// An icon on the track.
558	#[serde(skip_serializing_if = "Option::is_none")]
559	pub icon:  Option<Icon>,
560}
561
562/// One bar of a `bars` or `spark` chart, one point of a one-series `line`
563/// or `area` chart, or one segment of a `donut`.
564#[derive(Clone, Debug, Default, PartialEq, Serialize)]
565pub struct Point {
566	/// The value.
567	pub value: f64,
568	/// A label (`line`/`area`: the point's x label; `donut`: its legend row).
569	#[serde(skip_serializing_if = "Option::is_none")]
570	pub label: Option<String>,
571	/// A tooltip.
572	#[serde(skip_serializing_if = "Option::is_none")]
573	pub title: Option<String>,
574	/// This bar's fill instead of the chart's `token`.
575	#[serde(skip_serializing_if = "Option::is_none")]
576	pub tone:  Option<Tone>,
577	/// `donut`: this segment's color token.
578	#[serde(skip_serializing_if = "Option::is_none")]
579	pub token: Option<String>,
580}
581
582/// One line of a multi-series `line` or `area` chart.
583#[derive(Clone, Debug, Default, PartialEq, Serialize)]
584pub struct Series {
585	/// Names its legend chip and its part of the tooltip.
586	#[serde(skip_serializing_if = "Option::is_none")]
587	pub label:  Option<String>,
588	/// Its color token (default the next chart color).
589	#[serde(skip_serializing_if = "Option::is_none")]
590	pub token:  Option<String>,
591	/// One value per x position; `None` is a gap where the line breaks.
592	pub values: Vec<Option<f64>>,
593}
594
595impl Series {
596	/// A series of `values`.
597	pub fn new(values: impl Into<Vec<Option<f64>>>) -> Self {
598		Self { values: values.into(), ..Self::default() }
599	}
600
601	/// Sets the legend label.
602	pub fn label(mut self, label: impl Into<String>) -> Self {
603		self.label = Some(label.into());
604		self
605	}
606
607	/// Sets the color token.
608	pub fn token(mut self, token: impl Into<String>) -> Self {
609		self.token = Some(token.into());
610		self
611	}
612}
613
614/// A dashed reference line (a target, a limit) across a `bars`, `spark`,
615/// `line` or `area` chart. Outside the domain it is clamped to its edge.
616#[derive(Clone, Debug, Default, PartialEq, Serialize)]
617pub struct ChartMark {
618	/// Where the line sits, in value units.
619	pub value: f64,
620	/// The line's color (default muted foreground).
621	#[serde(skip_serializing_if = "Option::is_none")]
622	pub tone:  Option<Tone>,
623	/// The line's tooltip.
624	#[serde(skip_serializing_if = "Option::is_none")]
625	pub title: Option<String>,
626}
627
628/// A heatmap column label.
629#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
630pub struct ChartCol {
631	/// The 0-based column.
632	pub at:    u32,
633	/// The label.
634	pub label: String,
635}
636
637/// Where an overlay floats.
638#[derive(Clone, Debug, PartialEq, Eq)]
639pub enum Anchor {
640	/// Centered.
641	Center,
642	/// At the top.
643	Top,
644	/// At the bottom.
645	Bottom,
646	/// Beside node `id`, below it unless `above`.
647	Node {
648		/// The node.
649		id:    String,
650		/// Above instead of below.
651		above: bool,
652	},
653	/// Below the caret of field `id`.
654	Caret(String),
655}
656
657impl Serialize for Anchor {
658	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
659		use serde::ser::SerializeMap as _;
660		match self {
661			Self::Center => s.serialize_str("center"),
662			Self::Top => s.serialize_str("top"),
663			Self::Bottom => s.serialize_str("bottom"),
664			Self::Node { id, above } => {
665				let mut m = s.serialize_map(Some(2))?;
666				m.serialize_entry("node", id)?;
667				m.serialize_entry("side", if *above { "above" } else { "below" })?;
668				m.end()
669			},
670			Self::Caret(id) => {
671				let mut m = s.serialize_map(Some(1))?;
672				m.serialize_entry("caret", id)?;
673				m.end()
674			},
675		}
676	}
677}
678
679/// A badge in a tool's or agent's head, or after a picker row's label.
680#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
681pub struct BadgeSpec {
682	/// The text.
683	pub text:  String,
684	/// The tone.
685	#[serde(skip_serializing_if = "Option::is_none")]
686	pub tone:  Option<Tone>,
687	/// A tooltip.
688	#[serde(skip_serializing_if = "Option::is_none")]
689	pub title: Option<String>,
690}
691
692impl BadgeSpec {
693	/// A badge reading `text`.
694	pub fn new(text: impl Into<String>) -> Self {
695		Self { text: text.into(), ..Self::default() }
696	}
697
698	/// Sets the tone.
699	pub fn tone(mut self, tone: impl Into<Tone>) -> Self {
700		self.tone = Some(tone.into());
701		self
702	}
703
704	/// Sets the tooltip.
705	pub fn title(mut self, title: impl Into<String>) -> Self {
706		self.title = Some(title.into());
707		self
708	}
709}
710
711/// One tile of a `stats` well.
712#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
713pub struct StatItem {
714	/// What is counted.
715	pub label: Text,
716	/// The figure.
717	pub value: Text,
718	/// A note under the figure.
719	#[serde(skip_serializing_if = "Option::is_none")]
720	pub sub:   Option<Text>,
721	/// Colors the figure.
722	#[serde(skip_serializing_if = "Option::is_none")]
723	pub tone:  Option<Tone>,
724	/// The tooltip.
725	#[serde(skip_serializing_if = "Option::is_none")]
726	pub title: Option<String>,
727}
728
729impl StatItem {
730	/// A tile showing `value` under `label`.
731	pub fn new(label: impl Into<Text>, value: impl Into<Text>) -> Self {
732		Self { label: label.into(), value: value.into(), ..Self::default() }
733	}
734
735	/// Sets the note under the figure.
736	#[expect(clippy::should_implement_trait, reason = "named after the wire prop `sub`")]
737	pub fn sub(mut self, sub: impl Into<Text>) -> Self {
738		self.sub = Some(sub.into());
739		self
740	}
741
742	/// Sets the figure's tone.
743	pub fn tone(mut self, tone: impl Into<Tone>) -> Self {
744		self.tone = Some(tone.into());
745		self
746	}
747
748	/// Sets the tooltip.
749	pub fn title(mut self, title: impl Into<String>) -> Self {
750		self.title = Some(title.into());
751		self
752	}
753}
754
755/// A hover button on a tool.
756#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
757pub struct ToolButton {
758	/// The action id.
759	pub id:    String,
760	/// The label.
761	#[serde(skip_serializing_if = "Option::is_none")]
762	pub label: Option<String>,
763	/// Keycaps.
764	#[serde(skip_serializing_if = "Vec::is_empty")]
765	pub keys:  Vec<String>,
766}
767
768/// The clamp of a collapsed tool body.
769#[derive(Clone, Copy, Debug, PartialEq, Eq)]
770pub enum ToolPreview {
771	/// The first lines.
772	Lines(u32),
773	/// The last lines.
774	Tail(u32),
775}
776
777impl Serialize for ToolPreview {
778	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
779		use serde::ser::SerializeMap as _;
780		let mut m = s.serialize_map(Some(1))?;
781		match *self {
782			Self::Lines(n) => m.serialize_entry("lines", &n)?,
783			Self::Tail(n) => m.serialize_entry("tail", &n)?,
784		}
785		m.end()
786	}
787}
788
789/// Lines a collapsed card shows.
790#[derive(Clone, Copy, Debug, PartialEq, Eq)]
791pub enum Preview {
792	/// Ten lines.
793	Auto,
794	/// This many lines.
795	Lines(u32),
796}
797
798impl Serialize for Preview {
799	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
800		use serde::ser::SerializeMap as _;
801		match *self {
802			Self::Auto => s.serialize_str("auto"),
803			Self::Lines(n) => {
804				let mut m = s.serialize_map(Some(1))?;
805				m.serialize_entry("lines", &n)?;
806				m.end()
807			},
808		}
809	}
810}
811
812/// An agent's stats.
813#[derive(Clone, Debug, Default, PartialEq, Serialize)]
814#[serde(rename_all = "camelCase")]
815pub struct AgentStats {
816	/// Tool calls.
817	#[serde(skip_serializing_if = "Option::is_none")]
818	pub tools:         Option<u64>,
819	/// Requests.
820	#[serde(skip_serializing_if = "Option::is_none")]
821	pub requests:      Option<u64>,
822	/// Fraction done, 0–1.
823	#[serde(skip_serializing_if = "Option::is_none")]
824	pub done:          Option<f64>,
825	/// Context used, 0–1.
826	#[serde(skip_serializing_if = "Option::is_none")]
827	pub context:       Option<f64>,
828	/// The context meter's label.
829	#[serde(skip_serializing_if = "Option::is_none")]
830	pub context_label: Option<String>,
831	/// Tokens used.
832	#[serde(skip_serializing_if = "Option::is_none")]
833	pub tokens:        Option<u64>,
834	/// Cost in dollars.
835	#[serde(skip_serializing_if = "Option::is_none")]
836	pub cost:          Option<f64>,
837	/// Elapsed ms when sent.
838	#[serde(skip_serializing_if = "Option::is_none")]
839	pub age:           Option<i64>,
840	/// Final duration in ms.
841	#[serde(skip_serializing_if = "Option::is_none")]
842	pub took:          Option<u64>,
843}
844
845/// The tool an agent runs now.
846#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
847pub struct AgentTool {
848	/// The tool's name.
849	pub name:   String,
850	/// What it does.
851	#[serde(skip_serializing_if = "Option::is_none")]
852	pub intent: Option<String>,
853	/// Elapsed ms when sent.
854	#[serde(skip_serializing_if = "Option::is_none")]
855	pub age:    Option<i64>,
856}
857
858/// An agent's retry countdown.
859#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
860pub struct Retry {
861	/// The attempt.
862	#[serde(skip_serializing_if = "Option::is_none")]
863	pub attempt: Option<u32>,
864	/// Attempts allowed.
865	#[serde(skip_serializing_if = "Option::is_none")]
866	pub max:     Option<u32>,
867	/// The delay in ms.
868	#[serde(skip_serializing_if = "Option::is_none")]
869	pub delay:   Option<u64>,
870	/// Elapsed ms when sent.
871	#[serde(skip_serializing_if = "Option::is_none")]
872	pub age:     Option<i64>,
873	/// The error retried.
874	#[serde(skip_serializing_if = "Option::is_none")]
875	pub error:   Option<String>,
876}
877
878/// A checklist item.
879#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
880pub struct ChecklistItem {
881	/// Item id.
882	pub id:     String,
883	/// The text.
884	pub text:   Text,
885	/// The state.
886	#[serde(skip_serializing_if = "Option::is_none")]
887	pub status: Option<ItemStatus>,
888	/// A line under the item.
889	#[serde(skip_serializing_if = "Option::is_none")]
890	pub note:   Option<Text>,
891}
892
893/// A checklist phase.
894#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
895pub struct Phase {
896	/// Phase id.
897	pub id:        String,
898	/// The title.
899	#[serde(skip_serializing_if = "Option::is_none")]
900	pub title:     Option<Text>,
901	/// The items.
902	pub items:     Vec<ChecklistItem>,
903	/// The initial fold.
904	#[serde(skip_serializing_if = "Option::is_none")]
905	pub collapsed: Option<bool>,
906}
907
908/// A picker catalog item.
909#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
910pub struct PickerItem {
911	/// Item id.
912	pub id:       String,
913	/// Row text.
914	pub label:    Text,
915	/// Text after the label.
916	#[serde(skip_serializing_if = "Option::is_none")]
917	pub detail:   Option<Text>,
918	/// Lead icon.
919	#[serde(skip_serializing_if = "Option::is_none")]
920	pub icon:     Option<Icon>,
921	/// Picks the lead icon from a role.
922	#[serde(skip_serializing_if = "Option::is_none")]
923	pub role:     Option<String>,
924	/// An initials avatar `{text, seed?}`.
925	#[serde(skip_serializing_if = "Option::is_none")]
926	pub mark:     Option<Value>,
927	/// A status dot.
928	#[serde(skip_serializing_if = "Option::is_none")]
929	pub dot:      Option<Tone>,
930	/// Monospace label.
931	#[serde(skip_serializing_if = "Option::is_none")]
932	pub mono:     Option<bool>,
933	/// Column values by column id.
934	#[serde(skip_serializing_if = "BTreeMap::is_empty")]
935	pub facts:    BTreeMap<String, Value>,
936	/// Badges after the label.
937	#[serde(skip_serializing_if = "Vec::is_empty")]
938	pub badges:   Vec<BadgeSpec>,
939	/// Role chips `{text, on?, auto?, dot?}`.
940	#[serde(skip_serializing_if = "Vec::is_empty")]
941	pub chips:    Vec<Value>,
942	/// The row's tone.
943	#[serde(skip_serializing_if = "Option::is_none")]
944	pub tone:     Option<Tone>,
945	/// `true` or a reason: dims the row.
946	#[serde(skip_serializing_if = "Option::is_none")]
947	pub disabled: Option<Value>,
948	/// Match ranges `[from, to)` in the label (UTF-16).
949	#[serde(skip_serializing_if = "Vec::is_empty")]
950	pub hits:     Vec<[usize; 2]>,
951	/// `timeline` node style.
952	#[serde(skip_serializing_if = "Option::is_none")]
953	pub node:     Option<String>,
954	/// `tree` depth.
955	#[serde(skip_serializing_if = "Option::is_none")]
956	pub depth:    Option<u32>,
957	/// `tree` chevron state.
958	#[serde(skip_serializing_if = "Option::is_none")]
959	pub open:     Option<bool>,
960	/// Row tooltip.
961	#[serde(skip_serializing_if = "Option::is_none")]
962	pub title:    Option<String>,
963}
964
965impl PickerItem {
966	/// An item labeled `label`.
967	pub fn new(id: impl Into<String>, label: impl Into<Text>) -> Self {
968		Self { id: id.into(), label: label.into(), ..Self::default() }
969	}
970}
971
972/// A picker `order` entry: an item id or a group header.
973#[derive(Clone, Debug, PartialEq, Eq)]
974pub enum OrderEntry {
975	/// An item id.
976	Item(String),
977	/// A group header.
978	Group {
979		/// The group's key.
980		group: String,
981		/// Its label.
982		label: String,
983		/// A count.
984		count: Option<u64>,
985	},
986}
987
988impl Serialize for OrderEntry {
989	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
990		use serde::ser::SerializeMap as _;
991		match self {
992			Self::Item(id) => s.serialize_str(id),
993			Self::Group { group, label, count } => {
994				let mut m = s.serialize_map(None)?;
995				m.serialize_entry("group", group)?;
996				m.serialize_entry("label", label)?;
997				if let Some(count) = count {
998					m.serialize_entry("count", count)?;
999				}
1000				m.end()
1001			},
1002		}
1003	}
1004}
1005
1006/// A picker fact column.
1007#[derive(Clone, Debug, Default, PartialEq, Serialize)]
1008pub struct PickerColumn {
1009	/// The `facts` key.
1010	pub id:       String,
1011	/// Head label.
1012	#[serde(skip_serializing_if = "Option::is_none")]
1013	pub head:     Option<String>,
1014	/// Format.
1015	#[serde(skip_serializing_if = "Option::is_none")]
1016	pub format:   Option<ColumnFormat>,
1017	/// Lower hides first.
1018	#[serde(skip_serializing_if = "Option::is_none")]
1019	pub priority: Option<f64>,
1020	/// Minimum width in characters.
1021	#[serde(skip_serializing_if = "Option::is_none")]
1022	pub min:      Option<u32>,
1023}
1024
1025/// A picker confirm strip.
1026#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1027pub struct Confirm {
1028	/// The question.
1029	pub text:  String,
1030	/// The action sent (default `confirm`).
1031	#[serde(skip_serializing_if = "Option::is_none")]
1032	pub act:   Option<String>,
1033	/// The button label (default `Delete`).
1034	#[serde(skip_serializing_if = "Option::is_none")]
1035	pub label: Option<String>,
1036}
1037
1038/// A picker scope.
1039#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1040pub struct Scope {
1041	/// Scope id.
1042	pub id:       String,
1043	/// Label.
1044	pub label:    String,
1045	/// Group header.
1046	#[serde(skip_serializing_if = "Option::is_none")]
1047	pub group:    Option<String>,
1048	/// Initials avatar `{text, seed?}`.
1049	#[serde(skip_serializing_if = "Option::is_none")]
1050	pub mark:     Option<Value>,
1051	/// Icon.
1052	#[serde(skip_serializing_if = "Option::is_none")]
1053	pub icon:     Option<Icon>,
1054	/// Status dot.
1055	#[serde(skip_serializing_if = "Option::is_none")]
1056	pub dot:      Option<Tone>,
1057	/// Count.
1058	#[serde(skip_serializing_if = "Option::is_none")]
1059	pub count:    Option<u64>,
1060	/// `true` or a reason.
1061	#[serde(skip_serializing_if = "Option::is_none")]
1062	pub disabled: Option<Value>,
1063}
1064
1065/// A picker tab.
1066#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1067pub struct PickerTab {
1068	/// Tab id.
1069	pub id:    String,
1070	/// Label.
1071	pub label: String,
1072	/// Count.
1073	#[serde(skip_serializing_if = "Option::is_none")]
1074	pub count: Option<u64>,
1075}
1076
1077/// A switch chip of a picker strip.
1078#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1079pub struct StripItem {
1080	/// Chip id.
1081	pub id:    String,
1082	/// Label.
1083	pub label: String,
1084	/// Checked.
1085	#[serde(skip_serializing_if = "Option::is_none")]
1086	pub on:    Option<bool>,
1087	/// Dot palette name.
1088	#[serde(skip_serializing_if = "Option::is_none")]
1089	pub dot:   Option<String>,
1090}
1091
1092/// A picker's chip strip.
1093#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1094pub struct Strip {
1095	/// Label.
1096	#[serde(skip_serializing_if = "Option::is_none")]
1097	pub label:    Option<String>,
1098	/// Chips.
1099	pub items:    Vec<StripItem>,
1100	/// Outlined chip.
1101	#[serde(skip_serializing_if = "Option::is_none")]
1102	pub selected: Option<String>,
1103}
1104
1105/// A picker action bar button.
1106#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1107pub struct PickerAction {
1108	/// Action id.
1109	pub id:       String,
1110	/// Label (default the id).
1111	#[serde(skip_serializing_if = "Option::is_none")]
1112	pub label:    Option<String>,
1113	/// Keycaps.
1114	#[serde(skip_serializing_if = "Vec::is_empty")]
1115	pub keys:     Vec<String>,
1116	/// The primary action.
1117	#[serde(skip_serializing_if = "Option::is_none")]
1118	pub primary:  Option<bool>,
1119	/// At the right end.
1120	#[serde(skip_serializing_if = "Option::is_none")]
1121	pub end:      Option<bool>,
1122	/// Destructive.
1123	#[serde(skip_serializing_if = "Option::is_none")]
1124	pub danger:   Option<bool>,
1125	/// Toggled on.
1126	#[serde(skip_serializing_if = "Option::is_none")]
1127	pub on:       Option<bool>,
1128	/// `true` or a reason.
1129	#[serde(skip_serializing_if = "Option::is_none")]
1130	pub disabled: Option<Value>,
1131}
1132
1133/// A `prefs` nav page.
1134#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1135pub struct PrefsPage {
1136	/// Page id.
1137	pub id:       String,
1138	/// Label.
1139	#[serde(skip_serializing_if = "Option::is_none")]
1140	pub label:    Option<String>,
1141	/// Group heading.
1142	#[serde(skip_serializing_if = "Option::is_none")]
1143	pub group:    Option<String>,
1144	/// Icon.
1145	#[serde(skip_serializing_if = "Option::is_none")]
1146	pub icon:     Option<Icon>,
1147	/// Why it is disabled.
1148	#[serde(skip_serializing_if = "Option::is_none")]
1149	pub disabled: Option<String>,
1150	/// Rows changed from default.
1151	#[serde(skip_serializing_if = "Option::is_none")]
1152	pub changed:  Option<u32>,
1153}
1154
1155/// A `prefs` section.
1156#[derive(Clone, Debug, Default, PartialEq, Serialize)]
1157pub struct PrefsSection {
1158	/// Section id.
1159	#[serde(skip_serializing_if = "Option::is_none")]
1160	pub id:    Option<String>,
1161	/// Title.
1162	#[serde(skip_serializing_if = "Option::is_none")]
1163	pub title: Option<String>,
1164	/// The page it belongs to (search results).
1165	#[serde(skip_serializing_if = "Option::is_none")]
1166	pub page:  Option<String>,
1167	/// The rows.
1168	pub rows:  Vec<PrefsRow>,
1169}
1170
1171/// A `prefs` row.
1172#[derive(Clone, Debug, Default, PartialEq, Serialize)]
1173#[serde(rename_all = "camelCase")]
1174pub struct PrefsRow {
1175	/// Row id.
1176	pub id:            String,
1177	/// Label.
1178	#[serde(skip_serializing_if = "Option::is_none")]
1179	pub label:         Option<String>,
1180	/// A line under the label.
1181	#[serde(skip_serializing_if = "Option::is_none")]
1182	pub hint:          Option<String>,
1183	/// A warning.
1184	#[serde(skip_serializing_if = "Option::is_none")]
1185	pub warning:       Option<String>,
1186	/// Why it is disabled.
1187	#[serde(skip_serializing_if = "Option::is_none")]
1188	pub disabled:      Option<String>,
1189	/// Changed from default.
1190	#[serde(skip_serializing_if = "Option::is_none")]
1191	pub changed:       Option<bool>,
1192	/// The default, named in the changed dot's tooltip.
1193	#[serde(skip_serializing_if = "Option::is_none")]
1194	pub default_label: Option<String>,
1195	/// The control.
1196	#[serde(skip_serializing_if = "Option::is_none")]
1197	pub control:       Option<Control>,
1198}
1199
1200/// An option of a `prefs` choice or multi control.
1201#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1202pub struct ChoiceOption {
1203	/// The value.
1204	pub value:  String,
1205	/// The label.
1206	#[serde(skip_serializing_if = "Option::is_none")]
1207	pub label:  Option<String>,
1208	/// A tooltip or second line.
1209	#[serde(skip_serializing_if = "Option::is_none")]
1210	pub detail: Option<String>,
1211}
1212
1213/// A `prefs` row's control, by `k`.
1214#[derive(Clone, Debug, PartialEq, Serialize)]
1215#[serde(tag = "k", rename_all = "lowercase")]
1216pub enum Control {
1217	/// A toggle.
1218	Switch {
1219		/// On.
1220		on: bool,
1221	},
1222	/// One of several options.
1223	Choice {
1224		/// The current value.
1225		value:   String,
1226		/// The options.
1227		options: Vec<ChoiceOption>,
1228		/// How it draws.
1229		#[serde(skip_serializing_if = "Option::is_none")]
1230		style:   Option<ChoiceStyle>,
1231		/// Monospace labels.
1232		#[serde(skip_serializing_if = "Option::is_none")]
1233		mono:    Option<bool>,
1234	},
1235	/// A stepper.
1236	Number {
1237		/// The value.
1238		value:  f64,
1239		/// Minimum.
1240		#[serde(skip_serializing_if = "Option::is_none")]
1241		min:    Option<f64>,
1242		/// Maximum.
1243		#[serde(skip_serializing_if = "Option::is_none")]
1244		max:    Option<f64>,
1245		/// Step (default 1).
1246		#[serde(skip_serializing_if = "Option::is_none")]
1247		step:   Option<f64>,
1248		/// Unit.
1249		#[serde(skip_serializing_if = "Option::is_none")]
1250		unit:   Option<String>,
1251		/// Labels by value.
1252		#[serde(skip_serializing_if = "BTreeMap::is_empty")]
1253		labels: BTreeMap<String, String>,
1254	},
1255	/// A text field look.
1256	Text {
1257		/// The value.
1258		value:       String,
1259		/// Placeholder.
1260		#[serde(skip_serializing_if = "Option::is_none")]
1261		placeholder: Option<String>,
1262		/// Dots instead of text.
1263		#[serde(skip_serializing_if = "Option::is_none")]
1264		secret:      Option<bool>,
1265		/// Monospace.
1266		#[serde(skip_serializing_if = "Option::is_none")]
1267		mono:        Option<bool>,
1268	},
1269	/// Keycap chords.
1270	Keys {
1271		/// Chords, each a list of key names.
1272		keys: Vec<Vec<String>>,
1273	},
1274	/// Toggle chips or an ordered list.
1275	Multi {
1276		/// Enabled values.
1277		values:  Vec<String>,
1278		/// The options.
1279		options: Vec<ChoiceOption>,
1280		/// A reorderable list.
1281		#[serde(skip_serializing_if = "Option::is_none")]
1282		ordered: Option<bool>,
1283	},
1284	/// A button.
1285	Action {
1286		/// The action (default `edit`).
1287		#[serde(skip_serializing_if = "Option::is_none")]
1288		act:   Option<String>,
1289		/// The label.
1290		#[serde(skip_serializing_if = "Option::is_none")]
1291		label: Option<String>,
1292	},
1293}
1294
1295/// The `prefs` row being edited with the keyboard.
1296#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1297pub struct Editing {
1298	/// The row.
1299	pub row:    String,
1300	/// The highlighted option.
1301	#[serde(skip_serializing_if = "Option::is_none")]
1302	pub option: Option<String>,
1303	/// A text row's draft.
1304	#[serde(skip_serializing_if = "Option::is_none")]
1305	pub draft:  Option<String>,
1306	/// The caret in the draft (UTF-16).
1307	#[serde(skip_serializing_if = "Option::is_none")]
1308	pub cursor: Option<usize>,
1309}
1310
1311/// An entry of a `menu`, of a `menubar` menu or of `actions.menu`.
1312#[derive(Clone, Debug, PartialEq, Eq)]
1313pub enum MenuEntry {
1314	/// An action labeled from its id (`open-file` → "Open file").
1315	Act(String),
1316	/// An item: label, icon, keys, a check or radio, a submenu.
1317	Item(MenuItem),
1318	/// A dim section title.
1319	Header(String),
1320	/// A separator line.
1321	Separator,
1322}
1323
1324impl MenuEntry {
1325	/// A section title.
1326	pub fn header(title: impl Into<String>) -> Self {
1327		Self::Header(title.into())
1328	}
1329}
1330
1331impl Serialize for MenuEntry {
1332	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1333		match self {
1334			Self::Act(act) => s.serialize_str(act),
1335			Self::Item(item) => item.serialize(s),
1336			Self::Header(title) => {
1337				let mut map = s.serialize_map(Some(1))?;
1338				map.serialize_entry("header", title)?;
1339				map.end()
1340			},
1341			Self::Separator => s.serialize_str("-"),
1342		}
1343	}
1344}
1345
1346impl From<&str> for MenuEntry {
1347	fn from(act: &str) -> Self {
1348		Self::Act(act.to_owned())
1349	}
1350}
1351
1352impl From<String> for MenuEntry {
1353	fn from(act: String) -> Self {
1354		Self::Act(act)
1355	}
1356}
1357
1358impl From<MenuItem> for MenuEntry {
1359	fn from(item: MenuItem) -> Self {
1360		Self::Item(item)
1361	}
1362}
1363
1364/// A menu row. Choosing it sends `action` with `id` as `act` (and the new
1365/// `checked` of a check or radio row).
1366#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1367pub struct MenuItem {
1368	/// What the `action` event sends as `act`.
1369	pub id:       String,
1370	/// The row's text (default from `id`).
1371	#[serde(skip_serializing_if = "Option::is_none")]
1372	pub label:    Option<String>,
1373	/// A leading icon.
1374	#[serde(skip_serializing_if = "Option::is_none")]
1375	pub icon:     Option<Icon>,
1376	/// Shortcut keys drawn at the right (display only).
1377	#[serde(skip_serializing_if = "Vec::is_empty")]
1378	pub keys:     Vec<String>,
1379	/// A second line under the label (a rich row).
1380	#[serde(skip_serializing_if = "Option::is_none")]
1381	pub detail:   Option<String>,
1382	/// Makes it a check row, checked while `true`.
1383	#[serde(skip_serializing_if = "Option::is_none")]
1384	pub checked:  Option<bool>,
1385	/// Makes it a radio row of this group.
1386	#[serde(skip_serializing_if = "Option::is_none")]
1387	pub radio:    Option<String>,
1388	/// Dimmed, not choosable; a reason is the tooltip.
1389	#[serde(skip_serializing_if = "Option::is_none")]
1390	pub disabled: Option<Disabled>,
1391	/// A destructive action, drawn in `--bad`.
1392	#[serde(skip_serializing_if = "Option::is_none")]
1393	pub danger:   Option<bool>,
1394	/// A submenu (the row itself sends nothing).
1395	#[serde(skip_serializing_if = "Vec::is_empty")]
1396	pub items:    Vec<MenuEntry>,
1397}
1398
1399impl MenuItem {
1400	/// A row sending `id`, labeled `label`.
1401	pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
1402		Self { id: id.into(), label: Some(label.into()), ..Self::default() }
1403	}
1404
1405	/// Sets the icon.
1406	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
1407		self.icon = Some(icon.into());
1408		self
1409	}
1410
1411	/// Sets the shortcut keys.
1412	pub fn keys<K: Into<String>>(mut self, keys: impl IntoIterator<Item = K>) -> Self {
1413		self.keys = keys.into_iter().map(Into::into).collect();
1414		self
1415	}
1416
1417	/// Sets the second line.
1418	pub fn detail(mut self, detail: impl Into<String>) -> Self {
1419		self.detail = Some(detail.into());
1420		self
1421	}
1422
1423	/// Makes it a check row.
1424	pub const fn checked(mut self, checked: bool) -> Self {
1425		self.checked = Some(checked);
1426		self
1427	}
1428
1429	/// Makes it a radio row of `group`.
1430	pub fn radio(mut self, group: impl Into<String>) -> Self {
1431		self.radio = Some(group.into());
1432		self
1433	}
1434
1435	/// Disables it (`true` or a reason).
1436	pub fn disabled(mut self, disabled: impl Into<Disabled>) -> Self {
1437		self.disabled = Some(disabled.into());
1438		self
1439	}
1440
1441	/// Draws it as destructive.
1442	pub const fn danger(mut self, danger: bool) -> Self {
1443		self.danger = Some(danger);
1444		self
1445	}
1446
1447	/// Adds an entry to its submenu.
1448	pub fn item(mut self, entry: impl Into<MenuEntry>) -> Self {
1449		self.items.push(entry.into());
1450		self
1451	}
1452}
1453
1454/// A menu of a `menubar`.
1455#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1456pub struct MenubarMenu {
1457	/// Names the menu in events and in `open`.
1458	pub id:    String,
1459	/// Its title (default the `id`).
1460	#[serde(skip_serializing_if = "Option::is_none")]
1461	pub label: Option<String>,
1462	/// Its entries.
1463	pub items: Vec<MenuEntry>,
1464}
1465
1466impl MenubarMenu {
1467	/// A menu titled `label`.
1468	pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
1469		Self { id: id.into(), label: Some(label.into()), ..Self::default() }
1470	}
1471
1472	/// Adds an entry.
1473	pub fn item(mut self, entry: impl Into<MenuEntry>) -> Self {
1474		self.items.push(entry.into());
1475		self
1476	}
1477}
1478
1479/// A button that sends `act`: in a table's selection bar (`bar`, sent with
1480/// the picked ids as `value`), or attached to an `input` or `editor`
1481/// (`buttons`; `"copy"` copies the text).
1482#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1483pub struct ActButton {
1484	/// The action a click sends.
1485	pub act:      String,
1486	/// The label (without one the button is icon-only).
1487	#[serde(skip_serializing_if = "Option::is_none")]
1488	pub text:     Option<String>,
1489	/// A leading icon.
1490	#[serde(skip_serializing_if = "Option::is_none")]
1491	pub icon:     Option<Icon>,
1492	/// The chrome.
1493	#[serde(skip_serializing_if = "Option::is_none")]
1494	pub variant:  Option<ButtonVariant>,
1495	/// Keycaps after the label.
1496	#[serde(skip_serializing_if = "Vec::is_empty")]
1497	pub keys:     Vec<String>,
1498	/// The tooltip (and an icon-only button's accessible name).
1499	#[serde(skip_serializing_if = "Option::is_none")]
1500	pub title:    Option<String>,
1501	/// Faded and inert; a reason is the tooltip.
1502	#[serde(skip_serializing_if = "Option::is_none")]
1503	pub disabled: Option<Disabled>,
1504	/// A spinner in the icon's place; inert.
1505	#[serde(skip_serializing_if = "Option::is_none")]
1506	pub busy:     Option<bool>,
1507}
1508
1509impl ActButton {
1510	/// A button labeled `text` sending `act`.
1511	pub fn new(act: impl Into<String>, text: impl Into<String>) -> Self {
1512		Self { act: act.into(), text: Some(text.into()), ..Self::default() }
1513	}
1514
1515	/// An icon-only button sending `act`.
1516	pub fn icon_only(act: impl Into<String>, icon: impl Into<Icon>) -> Self {
1517		Self { act: act.into(), icon: Some(icon.into()), ..Self::default() }
1518	}
1519
1520	/// Sets the icon.
1521	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
1522		self.icon = Some(icon.into());
1523		self
1524	}
1525
1526	/// Sets the chrome.
1527	pub fn variant(mut self, variant: impl Into<ButtonVariant>) -> Self {
1528		self.variant = Some(variant.into());
1529		self
1530	}
1531
1532	/// Sets the keycaps.
1533	pub fn keys<K: Into<String>>(mut self, keys: impl IntoIterator<Item = K>) -> Self {
1534		self.keys = keys.into_iter().map(Into::into).collect();
1535		self
1536	}
1537
1538	/// Sets the tooltip.
1539	pub fn title(mut self, title: impl Into<String>) -> Self {
1540		self.title = Some(title.into());
1541		self
1542	}
1543
1544	/// Disables it (`true` or a reason).
1545	pub fn disabled(mut self, disabled: impl Into<Disabled>) -> Self {
1546		self.disabled = Some(disabled.into());
1547		self
1548	}
1549
1550	/// Shows a spinner; inert.
1551	pub const fn busy(mut self, busy: bool) -> Self {
1552		self.busy = Some(busy);
1553		self
1554	}
1555}
1556
1557/// An icon button of a `message` or an `attachment` (`actions`).
1558#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1559pub struct IconAction {
1560	/// The action a click runs.
1561	pub act:   String,
1562	/// The icon (default the act's own).
1563	#[serde(skip_serializing_if = "Option::is_none")]
1564	pub icon:  Option<Icon>,
1565	/// The tooltip and accessible name (default the act, capitalized).
1566	#[serde(skip_serializing_if = "Option::is_none")]
1567	pub title: Option<String>,
1568}
1569
1570impl IconAction {
1571	/// A button running `act`.
1572	pub fn new(act: impl Into<String>) -> Self {
1573		Self { act: act.into(), ..Self::default() }
1574	}
1575
1576	/// Sets the icon.
1577	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
1578		self.icon = Some(icon.into());
1579		self
1580	}
1581
1582	/// Sets the tooltip.
1583	pub fn title(mut self, title: impl Into<String>) -> Self {
1584		self.title = Some(title.into());
1585		self
1586	}
1587}
1588
1589/// The column a `table`'s rows are sorted by.
1590#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1591pub struct Sort {
1592	/// The column's id.
1593	pub col: String,
1594	/// The direction.
1595	pub dir: SortDir,
1596}
1597
1598impl Sort {
1599	/// Sorted by `col`, ascending.
1600	pub fn asc(col: impl Into<String>) -> Self {
1601		Self { col: col.into(), dir: SortDir::Asc }
1602	}
1603
1604	/// Sorted by `col`, descending.
1605	pub fn desc(col: impl Into<String>) -> Self {
1606		Self { col: col.into(), dir: SortDir::Desc }
1607	}
1608}
1609
1610/// A scope of a tool's approval (how long the yes lasts).
1611#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1612pub struct AskScope {
1613	/// Sent as the approval actions' `value`.
1614	pub id:    String,
1615	/// The radio's label (default the `id`).
1616	#[serde(skip_serializing_if = "Option::is_none")]
1617	pub label: Option<String>,
1618}
1619
1620impl AskScope {
1621	/// A scope labeled `label`.
1622	pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
1623		Self { id: id.into(), label: Some(label.into()) }
1624	}
1625}
1626
1627/// The Always button of a tool's approval card.
1628#[derive(Clone, Debug, PartialEq, Eq)]
1629pub enum AskAlways {
1630	/// Shown with this label.
1631	Label(String),
1632	/// Hidden.
1633	Hidden,
1634}
1635
1636impl Serialize for AskAlways {
1637	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1638		match self {
1639			Self::Label(label) => s.serialize_str(label),
1640			Self::Hidden => s.serialize_bool(false),
1641		}
1642	}
1643}
1644
1645impl From<&str> for AskAlways {
1646	fn from(label: &str) -> Self {
1647		Self::Label(label.to_owned())
1648	}
1649}
1650
1651impl From<String> for AskAlways {
1652	fn from(label: String) -> Self {
1653		Self::Label(label)
1654	}
1655}
1656
1657/// The contents of a tool's approval panel (`ask`), drawn while its status
1658/// is `ask`.
1659#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1660pub struct Ask {
1661	/// The question.
1662	#[serde(skip_serializing_if = "Option::is_none")]
1663	pub text:   Option<String>,
1664	/// The Allow button's label.
1665	#[serde(skip_serializing_if = "Option::is_none")]
1666	pub allow:  Option<String>,
1667	/// The Deny button's label.
1668	#[serde(skip_serializing_if = "Option::is_none")]
1669	pub deny:   Option<String>,
1670	/// The Always button (`card` only).
1671	#[serde(skip_serializing_if = "Option::is_none")]
1672	pub always: Option<AskAlways>,
1673	/// The radios of the `scopes` look (default once, session, always).
1674	#[serde(skip_serializing_if = "Vec::is_empty")]
1675	pub scopes: Vec<AskScope>,
1676	/// The scope selected, sent as the actions' `value`.
1677	#[serde(skip_serializing_if = "Option::is_none")]
1678	pub scope:  Option<String>,
1679}
1680
1681impl Ask {
1682	/// A panel asking `text`.
1683	pub fn new(text: impl Into<String>) -> Self {
1684		Self { text: Some(text.into()), ..Self::default() }
1685	}
1686
1687	/// Sets the Allow button's label.
1688	pub fn allow(mut self, label: impl Into<String>) -> Self {
1689		self.allow = Some(label.into());
1690		self
1691	}
1692
1693	/// Sets the Deny button's label.
1694	pub fn deny(mut self, label: impl Into<String>) -> Self {
1695		self.deny = Some(label.into());
1696		self
1697	}
1698
1699	/// Sets the Always button's label, or hides it.
1700	pub fn always(mut self, always: impl Into<AskAlways>) -> Self {
1701		self.always = Some(always.into());
1702		self
1703	}
1704
1705	/// Adds a scope radio.
1706	pub fn scope_option(mut self, scope: AskScope) -> Self {
1707		self.scopes.push(scope);
1708		self
1709	}
1710
1711	/// Selects scope `id`.
1712	pub fn scope(mut self, id: impl Into<String>) -> Self {
1713		self.scope = Some(id.into());
1714		self
1715	}
1716}
1717
1718/// An option of a `select`.
1719#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1720pub struct SelectOption {
1721	/// What `value`, `active` and `change` carry.
1722	pub value:    String,
1723	/// What the option (and the trigger, when current) shows (default the
1724	/// value).
1725	#[serde(skip_serializing_if = "Option::is_none")]
1726	pub label:    Option<String>,
1727	/// Muted mono text at the option's right.
1728	#[serde(skip_serializing_if = "Option::is_none")]
1729	pub detail:   Option<String>,
1730	/// A leading icon (`provider:<id>` for a provider's logo).
1731	#[serde(skip_serializing_if = "Option::is_none")]
1732	pub icon:     Option<Icon>,
1733	/// A section header over each run of options of the same group.
1734	#[serde(skip_serializing_if = "Option::is_none")]
1735	pub group:    Option<String>,
1736	/// Drawn faint and never picked.
1737	#[serde(skip_serializing_if = "Option::is_none")]
1738	pub disabled: Option<bool>,
1739}
1740
1741impl SelectOption {
1742	/// An option `value` labeled `label`.
1743	pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
1744		Self { value: value.into(), label: Some(label.into()), ..Self::default() }
1745	}
1746
1747	/// Sets the detail.
1748	pub fn detail(mut self, detail: impl Into<String>) -> Self {
1749		self.detail = Some(detail.into());
1750		self
1751	}
1752
1753	/// Sets the icon.
1754	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
1755		self.icon = Some(icon.into());
1756		self
1757	}
1758
1759	/// Sets the group.
1760	pub fn group(mut self, group: impl Into<String>) -> Self {
1761		self.group = Some(group.into());
1762		self
1763	}
1764
1765	/// Disables it.
1766	pub const fn disabled(mut self, disabled: bool) -> Self {
1767		self.disabled = Some(disabled);
1768		self
1769	}
1770}
1771
1772impl From<&str> for SelectOption {
1773	fn from(value: &str) -> Self {
1774		Self { value: value.to_owned(), ..Self::default() }
1775	}
1776}
1777
1778impl From<String> for SelectOption {
1779	fn from(value: String) -> Self {
1780		Self { value, ..Self::default() }
1781	}
1782}
1783
1784/// One avatar of an `avatars` stack.
1785#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1786pub struct AvatarItem {
1787	/// The name: its first two characters are the initials.
1788	pub text:    String,
1789	/// The shape.
1790	#[serde(skip_serializing_if = "Option::is_none")]
1791	pub variant: Option<AvatarVariant>,
1792	/// Picks the colors (default `text`).
1793	#[serde(skip_serializing_if = "Option::is_none")]
1794	pub seed:    Option<String>,
1795	/// An icon drawn instead of the initials.
1796	#[serde(skip_serializing_if = "Option::is_none")]
1797	pub icon:    Option<Icon>,
1798	/// A picture's blob id.
1799	#[serde(skip_serializing_if = "Option::is_none")]
1800	pub blob:    Option<String>,
1801	/// A status tone (not drawn in a stack).
1802	#[serde(skip_serializing_if = "Option::is_none")]
1803	pub status:  Option<Tone>,
1804	/// The tooltip and accessible name (default `text`).
1805	#[serde(skip_serializing_if = "Option::is_none")]
1806	pub title:   Option<String>,
1807}
1808
1809impl AvatarItem {
1810	/// An avatar of `text`.
1811	pub fn new(text: impl Into<String>) -> Self {
1812		Self { text: text.into(), ..Self::default() }
1813	}
1814
1815	/// Sets the shape.
1816	pub fn variant(mut self, variant: impl Into<AvatarVariant>) -> Self {
1817		self.variant = Some(variant.into());
1818		self
1819	}
1820
1821	/// Sets the color seed.
1822	pub fn seed(mut self, seed: impl Into<String>) -> Self {
1823		self.seed = Some(seed.into());
1824		self
1825	}
1826
1827	/// Sets the icon.
1828	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
1829		self.icon = Some(icon.into());
1830		self
1831	}
1832
1833	/// Sets the picture's blob.
1834	pub fn blob(mut self, blob: impl Into<String>) -> Self {
1835		self.blob = Some(blob.into());
1836		self
1837	}
1838
1839	/// Sets the tooltip.
1840	pub fn title(mut self, title: impl Into<String>) -> Self {
1841		self.title = Some(title.into());
1842		self
1843	}
1844}
1845
1846/// A crumb of a `breadcrumb`.
1847#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1848pub struct Crumb {
1849	/// Identity: the `select` event's `item`.
1850	pub id:    String,
1851	/// The text (default the `id`).
1852	#[serde(skip_serializing_if = "Option::is_none")]
1853	pub label: Option<String>,
1854	/// An icon before the label.
1855	#[serde(skip_serializing_if = "Option::is_none")]
1856	pub icon:  Option<Icon>,
1857	/// A URL or path ⌘-click opens.
1858	#[serde(skip_serializing_if = "Option::is_none")]
1859	pub href:  Option<String>,
1860}
1861
1862impl Crumb {
1863	/// A crumb labeled `label`.
1864	pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
1865		Self { id: id.into(), label: Some(label.into()), ..Self::default() }
1866	}
1867
1868	/// Sets the icon.
1869	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
1870		self.icon = Some(icon.into());
1871		self
1872	}
1873
1874	/// Sets the link.
1875	pub fn href(mut self, href: impl Into<String>) -> Self {
1876		self.href = Some(href.into());
1877		self
1878	}
1879}
1880
1881/// A day of a `calendar` carrying a dot.
1882#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1883pub struct DayMark {
1884	/// The day, `YYYY-MM-DD`.
1885	pub date: String,
1886	/// The dot's tone (default the accent).
1887	#[serde(skip_serializing_if = "Option::is_none")]
1888	pub tone: Option<Tone>,
1889}
1890
1891impl DayMark {
1892	/// A dot on `date` in `tone`.
1893	pub fn new(date: impl Into<String>, tone: impl Into<Tone>) -> Self {
1894		Self { date: date.into(), tone: Some(tone.into()) }
1895	}
1896}
1897
1898impl From<&str> for DayMark {
1899	fn from(date: &str) -> Self {
1900		Self { date: date.to_owned(), tone: None }
1901	}
1902}
1903
1904impl From<String> for DayMark {
1905	fn from(date: String) -> Self {
1906		Self { date, tone: None }
1907	}
1908}
1909
1910/// A preset of a `week` calendar.
1911#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1912pub struct Preset {
1913	/// The `select` event's `item` and the `preset` prop's value.
1914	pub id:    String,
1915	/// The text (default the `id`).
1916	#[serde(skip_serializing_if = "Option::is_none")]
1917	pub label: Option<String>,
1918}
1919
1920impl Preset {
1921	/// A preset labeled `label`.
1922	pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
1923		Self { id: id.into(), label: Some(label.into()) }
1924	}
1925}
1926
1927/// An item of a `nav`.
1928#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1929pub struct NavItem {
1930	/// The `select` event's `item`, and `active`'s value.
1931	pub id:    String,
1932	/// The text (default the `id`).
1933	#[serde(skip_serializing_if = "Option::is_none")]
1934	pub label: Option<String>,
1935	/// An icon before the label.
1936	#[serde(skip_serializing_if = "Option::is_none")]
1937	pub icon:  Option<Icon>,
1938	/// A small mono pill after the label.
1939	#[serde(skip_serializing_if = "Option::is_none")]
1940	pub count: Option<String>,
1941	/// Sub-items: the item opens a menu or panel instead of being picked.
1942	#[serde(skip_serializing_if = "Vec::is_empty")]
1943	pub items: Vec<NavSubItem>,
1944}
1945
1946impl NavItem {
1947	/// An item labeled `label`.
1948	pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
1949		Self { id: id.into(), label: Some(label.into()), ..Self::default() }
1950	}
1951
1952	/// Sets the icon.
1953	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
1954		self.icon = Some(icon.into());
1955		self
1956	}
1957
1958	/// Sets the count pill.
1959	pub fn count(mut self, count: impl Into<String>) -> Self {
1960		self.count = Some(count.into());
1961		self
1962	}
1963
1964	/// Adds a sub-item.
1965	pub fn item(mut self, item: NavSubItem) -> Self {
1966		self.items.push(item);
1967		self
1968	}
1969}
1970
1971/// A sub-item of a `nav` item.
1972#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1973pub struct NavSubItem {
1974	/// The `select` event's `item`.
1975	pub id:     String,
1976	/// The text (default the `id`).
1977	#[serde(skip_serializing_if = "Option::is_none")]
1978	pub label:  Option<String>,
1979	/// A muted note (`pills`) or description line (`links`).
1980	#[serde(skip_serializing_if = "Option::is_none")]
1981	pub detail: Option<String>,
1982	/// Leads the menu row, or fills the panel entry's tile.
1983	#[serde(skip_serializing_if = "Option::is_none")]
1984	pub icon:   Option<Icon>,
1985}
1986
1987impl NavSubItem {
1988	/// A sub-item labeled `label`.
1989	pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
1990		Self { id: id.into(), label: Some(label.into()), ..Self::default() }
1991	}
1992
1993	/// Sets the detail.
1994	pub fn detail(mut self, detail: impl Into<String>) -> Self {
1995		self.detail = Some(detail.into());
1996		self
1997	}
1998
1999	/// Sets the icon.
2000	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
2001		self.icon = Some(icon.into());
2002		self
2003	}
2004}
2005
2006/// A reaction chip of a `message`.
2007#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2008pub struct Reaction {
2009	/// An emoji or glyph: the reaction's identity.
2010	pub text:  String,
2011	/// How many reacted, with the local user (hidden at 0).
2012	#[serde(skip_serializing_if = "Option::is_none")]
2013	pub count: Option<u32>,
2014	/// The local user reacted.
2015	#[serde(skip_serializing_if = "Option::is_none")]
2016	pub on:    Option<bool>,
2017}
2018
2019impl Reaction {
2020	/// `text` reacted `count` times.
2021	pub fn new(text: impl Into<String>, count: u32) -> Self {
2022		Self { text: text.into(), count: Some(count), on: None }
2023	}
2024
2025	/// Marks the local user's own reaction.
2026	pub const fn on(mut self, on: bool) -> Self {
2027		self.on = Some(on);
2028		self
2029	}
2030}
2031
2032/// A source chip of a `trace` or one of its lines.
2033#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2034pub struct Source {
2035	/// The tooltip.
2036	#[serde(skip_serializing_if = "Option::is_none")]
2037	pub title: Option<String>,
2038	/// Opened by a click.
2039	#[serde(skip_serializing_if = "Option::is_none")]
2040	pub href:  Option<String>,
2041	/// The name shown (default the `href`'s host, `www.` dropped).
2042	#[serde(skip_serializing_if = "Option::is_none")]
2043	pub site:  Option<String>,
2044}
2045
2046impl Source {
2047	/// A source at `href`.
2048	pub fn new(href: impl Into<String>) -> Self {
2049		Self { href: Some(href.into()), ..Self::default() }
2050	}
2051
2052	/// Sets the tooltip.
2053	pub fn title(mut self, title: impl Into<String>) -> Self {
2054		self.title = Some(title.into());
2055		self
2056	}
2057
2058	/// Sets the site name.
2059	pub fn site(mut self, site: impl Into<String>) -> Self {
2060		self.site = Some(site.into());
2061		self
2062	}
2063}
2064
2065/// A step of a `trace`. A child keyed by its `id` is its content.
2066#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2067pub struct TraceLine {
2068	/// Identity (default its index): keys its content child and its open
2069	/// state, and is the `toggle` event's `item`.
2070	#[serde(skip_serializing_if = "Option::is_none")]
2071	pub id:      Option<String>,
2072	/// Its glyph, tinted by status.
2073	#[serde(skip_serializing_if = "Option::is_none")]
2074	pub icon:    Option<Icon>,
2075	/// The step in words.
2076	#[serde(skip_serializing_if = "Option::is_none")]
2077	pub title:   Option<Text>,
2078	/// What it acted on, in mono.
2079	#[serde(skip_serializing_if = "Option::is_none")]
2080	pub target:  Option<Text>,
2081	/// A dim line under the row.
2082	#[serde(skip_serializing_if = "Option::is_none")]
2083	pub detail:  Option<Text>,
2084	/// `pending`, `running`, `done` or `error`.
2085	#[serde(skip_serializing_if = "Option::is_none")]
2086	pub status:  Option<Status>,
2087	/// Elapsed ms when sent.
2088	#[serde(skip_serializing_if = "Option::is_none")]
2089	pub age:     Option<u64>,
2090	/// Its time in ms once settled.
2091	#[serde(skip_serializing_if = "Option::is_none")]
2092	pub took:    Option<u64>,
2093	/// Whether its content shows.
2094	#[serde(skip_serializing_if = "Option::is_none")]
2095	pub open:    Option<bool>,
2096	/// Chips under the line.
2097	#[serde(skip_serializing_if = "Vec::is_empty")]
2098	pub sources: Vec<Source>,
2099}
2100
2101impl TraceLine {
2102	/// A step `id` reading `title`.
2103	pub fn new(id: impl Into<String>, title: impl Into<Text>) -> Self {
2104		Self { id: Some(id.into()), title: Some(title.into()), ..Self::default() }
2105	}
2106
2107	/// Sets the glyph.
2108	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
2109		self.icon = Some(icon.into());
2110		self
2111	}
2112
2113	/// Sets what it acted on.
2114	pub fn target(mut self, target: impl Into<Text>) -> Self {
2115		self.target = Some(target.into());
2116		self
2117	}
2118
2119	/// Sets the dim line under it.
2120	pub fn detail(mut self, detail: impl Into<Text>) -> Self {
2121		self.detail = Some(detail.into());
2122		self
2123	}
2124
2125	/// Sets the status.
2126	pub fn status(mut self, status: impl Into<Status>) -> Self {
2127		self.status = Some(status.into());
2128		self
2129	}
2130
2131	/// Sets the elapsed ms.
2132	pub const fn age(mut self, age: u64) -> Self {
2133		self.age = Some(age);
2134		self
2135	}
2136
2137	/// Sets the final time in ms.
2138	pub const fn took(mut self, took: u64) -> Self {
2139		self.took = Some(took);
2140		self
2141	}
2142
2143	/// Sets whether its content shows.
2144	pub const fn open(mut self, open: bool) -> Self {
2145		self.open = Some(open);
2146		self
2147	}
2148
2149	/// Adds a source chip.
2150	pub fn source(mut self, source: Source) -> Self {
2151		self.sources.push(source);
2152		self
2153	}
2154}
2155
2156/// The error of a `stacktrace`.
2157#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2158pub struct StackError {
2159	/// The error's type (`type` on the wire).
2160	#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
2161	pub kind:    Option<String>,
2162	/// The message.
2163	#[serde(skip_serializing_if = "Option::is_none")]
2164	pub message: Option<String>,
2165}
2166
2167impl StackError {
2168	/// An error of type `kind` saying `message`.
2169	pub fn new(kind: impl Into<String>, message: impl Into<String>) -> Self {
2170		Self { kind: Some(kind.into()), message: Some(message.into()) }
2171	}
2172}
2173
2174/// A frame of a `stacktrace`.
2175#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2176pub struct Frame {
2177	/// The function (`fn` on the wire; `<anonymous>` when empty).
2178	#[serde(rename = "fn")]
2179	pub func: String,
2180	/// The file path; a relative one opens against the pane's directory.
2181	pub file: String,
2182	/// The 1-based line.
2183	#[serde(skip_serializing_if = "Option::is_none")]
2184	pub line: Option<u32>,
2185	/// The 1-based column.
2186	#[serde(skip_serializing_if = "Option::is_none")]
2187	pub col:  Option<u32>,
2188	/// Your code (default) or a library's (`false`).
2189	#[serde(skip_serializing_if = "Option::is_none")]
2190	pub app:  Option<bool>,
2191	/// A source excerpt for `split`.
2192	#[serde(skip_serializing_if = "Option::is_none")]
2193	pub code: Option<FrameCode>,
2194}
2195
2196impl Frame {
2197	/// A frame of `func` in `file`.
2198	pub fn new(func: impl Into<String>, file: impl Into<String>) -> Self {
2199		Self { func: func.into(), file: file.into(), ..Self::default() }
2200	}
2201
2202	/// Sets the line and column.
2203	pub const fn at(mut self, line: u32, col: u32) -> Self {
2204		self.line = Some(line);
2205		self.col = Some(col);
2206		self
2207	}
2208
2209	/// Sets the line.
2210	pub const fn line(mut self, line: u32) -> Self {
2211		self.line = Some(line);
2212		self
2213	}
2214
2215	/// Marks it as your code or a library's.
2216	pub const fn app(mut self, app: bool) -> Self {
2217		self.app = Some(app);
2218		self
2219	}
2220
2221	/// Sets the source excerpt.
2222	pub fn code(mut self, code: FrameCode) -> Self {
2223		self.code = Some(code);
2224		self
2225	}
2226}
2227
2228/// A source excerpt of a `stacktrace` frame.
2229#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2230pub struct FrameCode {
2231	/// The first line's number (default 1).
2232	#[serde(skip_serializing_if = "Option::is_none")]
2233	pub start: Option<u32>,
2234	/// The lines.
2235	pub lines: Vec<String>,
2236	/// The line to mark (default the frame's `line`).
2237	#[serde(skip_serializing_if = "Option::is_none")]
2238	pub line:  Option<u32>,
2239}
2240
2241impl FrameCode {
2242	/// `lines` from line `start`.
2243	pub fn new<L: Into<String>>(start: u32, lines: impl IntoIterator<Item = L>) -> Self {
2244		Self { start: Some(start), lines: lines.into_iter().map(Into::into).collect(), line: None }
2245	}
2246
2247	/// Sets the line to mark.
2248	pub const fn line(mut self, line: u32) -> Self {
2249		self.line = Some(line);
2250		self
2251	}
2252}
2253
2254/// A prompt of a `suggestions` node.
2255#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2256pub struct Suggestion {
2257	/// What `activate` names (default the `text`).
2258	#[serde(skip_serializing_if = "Option::is_none")]
2259	pub id:     Option<String>,
2260	/// The prompt as shown.
2261	pub text:   String,
2262	/// `rows`: muted after the text; `cards`: the line under it; `chips`:
2263	/// the tooltip.
2264	#[serde(skip_serializing_if = "Option::is_none")]
2265	pub detail: Option<String>,
2266	/// The leading icon.
2267	#[serde(skip_serializing_if = "Option::is_none")]
2268	pub icon:   Option<Icon>,
2269	/// `rows`: the keycaps at the right (default the item's number; empty
2270	/// for none).
2271	#[serde(skip_serializing_if = "Option::is_none")]
2272	pub keys:   Option<Vec<String>>,
2273}
2274
2275impl Suggestion {
2276	/// A prompt reading `text`.
2277	pub fn new(text: impl Into<String>) -> Self {
2278		Self { text: text.into(), ..Self::default() }
2279	}
2280
2281	/// Sets the id `activate` names.
2282	pub fn id(mut self, id: impl Into<String>) -> Self {
2283		self.id = Some(id.into());
2284		self
2285	}
2286
2287	/// Sets the detail.
2288	pub fn detail(mut self, detail: impl Into<String>) -> Self {
2289		self.detail = Some(detail.into());
2290		self
2291	}
2292
2293	/// Sets the icon.
2294	pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
2295		self.icon = Some(icon.into());
2296		self
2297	}
2298
2299	/// Sets the keycaps (`rows`).
2300	pub fn keys<K: Into<String>>(mut self, keys: impl IntoIterator<Item = K>) -> Self {
2301		self.keys = Some(keys.into_iter().map(Into::into).collect());
2302		self
2303	}
2304}
2305
2306/// A question of a `questions` node.
2307#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2308pub struct Question {
2309	/// Its events' `item` and its key in `answers`.
2310	pub id:          String,
2311	/// The question.
2312	#[serde(skip_serializing_if = "Option::is_none")]
2313	pub prompt:      Option<String>,
2314	/// A line under the prompt.
2315	#[serde(skip_serializing_if = "Option::is_none")]
2316	pub description: Option<String>,
2317	/// The choices.
2318	#[serde(skip_serializing_if = "Vec::is_empty")]
2319	pub choices:     Vec<QuestionChoice>,
2320	/// Several picks: the answer is a list of values.
2321	#[serde(skip_serializing_if = "Option::is_none")]
2322	pub multi:       Option<bool>,
2323	/// Takes a free-text answer from the `input` child keyed `<id>.other`.
2324	#[serde(skip_serializing_if = "Option::is_none")]
2325	pub other:       Option<OtherAnswer>,
2326	/// No Skip; Next and Submit wait for its answer.
2327	#[serde(skip_serializing_if = "Option::is_none")]
2328	pub required:    Option<bool>,
2329}
2330
2331impl Question {
2332	/// Question `id` asking `prompt`.
2333	pub fn new(id: impl Into<String>, prompt: impl Into<String>) -> Self {
2334		Self { id: id.into(), prompt: Some(prompt.into()), ..Self::default() }
2335	}
2336
2337	/// Sets the line under the prompt.
2338	pub fn description(mut self, description: impl Into<String>) -> Self {
2339		self.description = Some(description.into());
2340		self
2341	}
2342
2343	/// Adds a choice.
2344	pub fn choice(mut self, choice: impl Into<QuestionChoice>) -> Self {
2345		self.choices.push(choice.into());
2346		self
2347	}
2348
2349	/// Takes several picks.
2350	pub const fn multi(mut self, multi: bool) -> Self {
2351		self.multi = Some(multi);
2352		self
2353	}
2354
2355	/// Takes a free-text answer (`true`, or its placeholder).
2356	pub fn other(mut self, other: impl Into<OtherAnswer>) -> Self {
2357		self.other = Some(other.into());
2358		self
2359	}
2360
2361	/// Requires an answer.
2362	pub const fn required(mut self, required: bool) -> Self {
2363		self.required = Some(required);
2364		self
2365	}
2366}
2367
2368/// A choice of a question.
2369#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
2370pub struct QuestionChoice {
2371	/// The answer it gives.
2372	pub value:  String,
2373	/// The text (default the `value`).
2374	#[serde(skip_serializing_if = "Option::is_none")]
2375	pub label:  Option<String>,
2376	/// A second line on a choice card (`steps` only).
2377	#[serde(skip_serializing_if = "Option::is_none")]
2378	pub detail: Option<String>,
2379}
2380
2381impl QuestionChoice {
2382	/// A choice `value` labeled `label`.
2383	pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
2384		Self { value: value.into(), label: Some(label.into()), detail: None }
2385	}
2386
2387	/// Sets the second line.
2388	pub fn detail(mut self, detail: impl Into<String>) -> Self {
2389		self.detail = Some(detail.into());
2390		self
2391	}
2392}
2393
2394impl From<&str> for QuestionChoice {
2395	fn from(value: &str) -> Self {
2396		Self { value: value.to_owned(), ..Self::default() }
2397	}
2398}
2399
2400impl From<String> for QuestionChoice {
2401	fn from(value: String) -> Self {
2402		Self { value, ..Self::default() }
2403	}
2404}
2405
2406/// A question's free-text answer: on, or on with a placeholder.
2407#[derive(Clone, Debug, PartialEq, Eq)]
2408pub enum OtherAnswer {
2409	/// On or off.
2410	Flag(bool),
2411	/// On, with this placeholder.
2412	Placeholder(String),
2413}
2414
2415impl Serialize for OtherAnswer {
2416	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
2417		match self {
2418			Self::Flag(on) => s.serialize_bool(*on),
2419			Self::Placeholder(placeholder) => {
2420				let mut map = s.serialize_map(Some(1))?;
2421				map.serialize_entry("placeholder", placeholder)?;
2422				map.end()
2423			},
2424		}
2425	}
2426}
2427
2428impl From<bool> for OtherAnswer {
2429	fn from(on: bool) -> Self {
2430		Self::Flag(on)
2431	}
2432}
2433
2434impl From<&str> for OtherAnswer {
2435	fn from(placeholder: &str) -> Self {
2436		Self::Placeholder(placeholder.to_owned())
2437	}
2438}
2439
2440impl From<String> for OtherAnswer {
2441	fn from(placeholder: String) -> Self {
2442		Self::Placeholder(placeholder)
2443	}
2444}
2445
2446/// An answer of a `questions` node: a choice's value or text, or the values
2447/// of a `multi` question.
2448#[derive(Clone, Debug, PartialEq, Eq)]
2449pub enum QuestionAnswer {
2450	/// One value, or text.
2451	One(String),
2452	/// Several values.
2453	Many(Vec<String>),
2454}
2455
2456impl Serialize for QuestionAnswer {
2457	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
2458		match self {
2459			Self::One(value) => s.serialize_str(value),
2460			Self::Many(values) => values.serialize(s),
2461		}
2462	}
2463}
2464
2465impl From<&str> for QuestionAnswer {
2466	fn from(value: &str) -> Self {
2467		Self::One(value.to_owned())
2468	}
2469}
2470
2471impl From<String> for QuestionAnswer {
2472	fn from(value: String) -> Self {
2473		Self::One(value)
2474	}
2475}
2476
2477impl From<Vec<String>> for QuestionAnswer {
2478	fn from(values: Vec<String>) -> Self {
2479		Self::Many(values)
2480	}
2481}