- 1
//! Declarative starter definitions. These are content-only seeds: the runtime - 2
//! still validates, compiles, activates, and falls back through its generic - 3
//! pipeline. Adding a seed never adds a renderer branch. - 4
- 5
use super::{ - 6
AccessibilitySpec, Binding, EmptyValue, FallbackSpec, LibraryScope, PresentationOrigin, - 7
PresentationSpec, Primitive, SpecNode, SpecValue, StoredPresentation, digest, - 8
}; - 9
use std::collections::BTreeMap; - 10
- 11
const EVERYDAY: &[(&str, &str)] = &[ - 12
("overview", "overview"), - 13
("checklist", "checklist"), - 14
("schedule", "schedule"), - 15
("comparison", "comparison"), - 16
("decision", "decision"), - 17
("budget", "budget"), - 18
("collection", "collection"), - 19
("detail", "detail"), - 20
("steps", "steps"), - 21
("summary", "summary"), - 22
("notes", "notes"), - 23
("agenda", "agenda"), - 24
("follow-up", "follow_up"), - 25
("reminder", "reminder"), - 26
("comparison-table", "comparison_table"), - 27
("pros-cons", "pros_cons"), - 28
("scorecard", "scorecard"), - 29
("milestones", "milestones"), - 30
("progress", "progress"), - 31
("status", "status"), - 32
("inventory", "inventory"), - 33
("shopping-list", "shopping_list"), - 34
("meal-plan", "meal_plan"), - 35
("itinerary", "itinerary"), - 36
("lesson", "lesson"), - 37
("reading-list", "reading_list"), - 38
("habit-plan", "habit_plan"), - 39
("project-plan", "project_plan"), - 40
("meeting-notes", "meeting_notes"), - 41
("contact-log", "contact_log"), - 42
("finance-summary", "finance_summary"), - 43
("invoice-summary", "invoice_summary"), - 44
("recipe-summary", "recipe_summary"), - 45
("travel-options", "travel_options"), - 46
("home-project", "home_project"), - 47
("care-plan", "care_plan"), - 48
("event-plan", "event_plan"), - 49
("media-list", "media_list"), - 50
("research-brief", "research_brief"), - 51
("faq", "faq"), - 52
("timeline", "timeline"), - 53
("metric", "metric"), - 54
("recipe", "recipe"), - 55
("news", "news"), - 56
("ui-preview", "ui.preview"), - 57
]; - 58
- 59
const CODING: &[(&str, &str)] = &[ - 60
("diff-review", "coding.diff"), - 61
("test-results", "test.report"), - 62
("terminal-session", "terminal.view"), - 63
("deployment-receipt", "coding.deployment"), - 64
("incident-timeline", "coding.incident"), - 65
("benchmark", "coding.benchmark"), - 66
("architecture-review", "coding.architecture"), - 67
("dependency-review", "coding.dependencies"), - 68
("release-notes", "coding.release"), - 69
("code-search", "coding.search"), - 70
]; - 71
- 72
const UNIVERSAL: &[(&str, &str)] = &[ - 73
("map", "map"), - 74
("route-map", "route_map"), - 75
("calendar", "calendar"), - 76
("availability", "availability"), - 77
("board", "board"), - 78
("entity", "entity"), - 79
("search-results", "search_results"), - 80
("evidence", "evidence"), - 81
("decision-analysis", "decision_analysis"), - 82
("document", "document"), - 83
("table", "table"), - 84
("dataframe", "dataframe"), - 85
("graph", "graph"), - 86
("form", "form"), - 87
("action", "action"), - 88
("transaction", "transaction"), - 89
("alert", "alert"), - 90
("conversation", "conversation"), - 91
("progress-dashboard", "progress_dashboard"), - 92
("simulation", "simulation"), - 93
]; - 94
- 95
// These are the payload families of the built-in emit tools. Every seed must - 96
// bind the shape the tool actually validates; a title-only seed is not a card. - 97
const TABLE_TYPES: &[&str] = &[ - 98
"coding.benchmark", - 99
"coding.dependencies", - 100
"data.grid", - 101
"table", - 102
"dataframe", - 103
"comparison", - 104
"comparison_table", - 105
"pros_cons", - 106
"inventory", - 107
"scorecard", - 108
"budget", - 109
"finance_summary", - 110
"invoice_summary", - 111
"travel_options", - 112
"decision_matrix", - 113
"criteria_matrix", - 114
"tradeoff_analysis", - 115
]; - 116
const TIMELINE_TYPES: &[&str] = &[ - 117
"coding.deployment", - 118
"coding.incident", - 119
"coding.architecture", - 120
"coding.release", - 121
"plan.timeline", - 122
"timeline", - 123
"itinerary", - 124
"checklist", - 125
"schedule", - 126
"agenda", - 127
"milestones", - 128
"progress", - 129
"status", - 130
"steps", - 131
"overview", - 132
"summary", - 133
"detail", - 134
"notes", - 135
"follow_up", - 136
"reminder", - 137
"shopping_list", - 138
"lesson", - 139
"reading_list", - 140
"habit_plan", - 141
"project_plan", - 142
"meeting_notes", - 143
"contact_log", - 144
"home_project", - 145
"care_plan", - 146
"event_plan", - 147
"media_list", - 148
"collection", - 149
"faq", - 150
"decision", - 151
"decision_analysis", - 152
"meal_plan", - 153
]; - 154
const RECIPE_TYPES: &[&str] = &[ - 155
"recipe.card", - 156
"recipe", - 157
"recipe_summary", - 158
"lifestyle.recipe", - 159
"lifestyle.culinary_recipe", - 160
]; - 161
const RESEARCH_TYPES: &[&str] = &["research.synthesis", "research_brief", "news"]; - 162
const UNIVERSAL_TYPES: &[&str] = &[ - 163
"map", - 164
"route_map", - 165
"calendar", - 166
"availability", - 167
"board", - 168
"entity", - 169
"search_results", - 170
"coding.search", - 171
"evidence", - 172
"document", - 173
"graph", - 174
"form", - 175
"action", - 176
"transaction", - 177
"alert", - 178
"conversation", - 179
"progress_dashboard", - 180
"simulation", - 181
]; - 182
- 183
fn binding(path: &str) -> SpecValue { - 184
SpecValue::Binding(Binding { - 185
path: path.into(), - 186
required: false, - 187
empty: EmptyValue::EmptyText, - 188
}) - 189
} - 190
- 191
fn required_binding(path: &str) -> SpecValue { - 192
SpecValue::Binding(Binding { - 193
path: path.into(), - 194
required: true, - 195
empty: EmptyValue::Omit, - 196
}) - 197
} - 198
- 199
fn row(primitive: Primitive, props: &[(&str, &str)]) -> SpecNode { - 200
SpecNode { - 201
primitive, - 202
props: props - 203
.iter() - 204
.map(|(key, path)| ((*key).into(), binding(path))) - 205
.collect(), - 206
children: Vec::new(), - 207
each: None, - 208
item: None, - 209
} - 210
} - 211
- 212
fn list(primitive: Primitive, path: &str, item: SpecNode) -> SpecNode { - 213
SpecNode { - 214
primitive, - 215
props: BTreeMap::new(), - 216
children: Vec::new(), - 217
each: Some(Binding { - 218
path: path.into(), - 219
required: true, - 220
empty: EmptyValue::Omit, - 221
}), - 222
item: Some(Box::new(item)), - 223
} - 224
} - 225
- 226
#[allow(clippy::manual_unwrap_or_default)] - 227
fn seed(id: &str, accepts: &str) -> StoredPresentation { - 228
let mut root_primitive = match accepts { - 229
"itinerary" | "schedule" | "timeline" | "milestones" | "incident-timeline" => { - 230
Primitive::Timeline - 231
} - 232
"map" | "route_map" => Primitive::Map, - 233
"calendar" | "availability" => Primitive::Calendar, - 234
"board" => Primitive::Board, - 235
"entity" => Primitive::Entity, - 236
"evidence" => Primitive::Evidence, - 237
"graph" => Primitive::Graph, - 238
"form" => Primitive::Form, - 239
"action" => Primitive::Row, - 240
"transaction" | "table" | "dataframe" => Primitive::Table, - 241
"document" => Primitive::Section, - 242
"alert" => Primitive::Alert, - 243
"conversation" => Primitive::Stack, - 244
"simulation" => Primitive::Chart, - 245
"checklist" | "shopping_list" | "reading_list" | "habit_plan" => Primitive::Checklist, - 246
"decision" | "decision_analysis" | "comparison" | "comparison_table" | "pros_cons" - 247
| "scorecard" => Primitive::Comparison, - 248
"budget" | "finance_summary" | "invoice_summary" | "inventory" => Primitive::Table, - 249
"steps" | "lesson" | "event_plan" | "care_plan" => Primitive::Steps, - 250
"progress" | "status" => Primitive::Progress, - 251
"metric" | "benchmark" => Primitive::Metric, - 252
"recipe" | "recipe_summary" | "lifestyle.recipe" => Primitive::Recipe, - 253
"research_brief" | "news" => Primitive::Research, - 254
"preview" | "ui.preview" => Primitive::UiPreview, - 255
"coding.diff" => Primitive::Diff, - 256
"test.report" => Primitive::TestMatrix, - 257
"terminal.view" => Primitive::Terminal, - 258
_ => { - 259
let primitives = [ - 260
Primitive::Section, - 261
Primitive::Stack, - 262
Primitive::Row, - 263
Primitive::Timeline, - 264
Primitive::Checklist, - 265
Primitive::Table, - 266
Primitive::Comparison, - 267
Primitive::Steps, - 268
Primitive::Progress, - 269
Primitive::KeyValue, - 270
Primitive::Disclosure, - 271
]; - 272
primitives[id.bytes().map(usize::from).sum::<usize>() % primitives.len()] - 273
} - 274
}; - 275
if TABLE_TYPES.contains(&accepts) { - 276
root_primitive = Primitive::Table; - 277
} else if TIMELINE_TYPES.contains(&accepts) { - 278
root_primitive = Primitive::Timeline; - 279
} else if RECIPE_TYPES.contains(&accepts) { - 280
root_primitive = Primitive::Recipe; - 281
} else if RESEARCH_TYPES.contains(&accepts) { - 282
root_primitive = Primitive::Research; - 283
} else if UNIVERSAL_TYPES.contains(&accepts) { - 284
root_primitive = Primitive::Entity; - 285
} - 286
let certified = true; - 287
let mut root = SpecNode { - 288
primitive: root_primitive, - 289
// The validated card payload is the source of truth. Preserve its - 290
// fields in the tree, then bind ordered collections as children for - 291
// the matching renderer. No title-only projection is allowed. - 292
props: BTreeMap::from([(String::from("*"), required_binding("$"))]), - 293
children: Vec::new(), - 294
each: None, - 295
item: None, - 296
}; - 297
if id == "travel-options" { - 298
root.props.insert( - 299
"variant".into(), - 300
SpecValue::Text { - 301
value: "options".into(), - 302
}, - 303
); - 304
} - 305
let accepts: Vec<String> = match id { - 306
"timeline" => vec![accepts.into(), "plan.timeline".into()], - 307
"recipe" => vec![accepts.into(), "recipe.card".into()], - 308
"research-brief" => vec![accepts.into(), "research.synthesis".into()], - 309
_ => vec![accepts.into()], - 310
}; - 311
if TIMELINE_TYPES.contains(&accepts[0].as_str()) { - 312
root.each = Some(Binding { - 313
path: "$.items".into(), - 314
required: true, - 315
empty: EmptyValue::Omit, - 316
}); - 317
root.item = Some(Box::new(row(Primitive::Section, &[("*", "$")]))); - 318
} else if TABLE_TYPES.contains(&accepts[0].as_str()) { - 319
root.each = Some(Binding { - 320
path: "$.rows".into(), - 321
required: true, - 322
empty: EmptyValue::Omit, - 323
}); - 324
root.item = Some(Box::new(row(Primitive::Row, &[("*", "$")]))); - 325
} else if RECIPE_TYPES.contains(&accepts[0].as_str()) { - 326
root.children = vec![ - 327
list( - 328
Primitive::IngredientList, - 329
"$.ingredients", - 330
row( - 331
Primitive::Row, - 332
&[ - 333
("name", "$.name"), - 334
("amount", "$.amount"), - 335
("unit", "$.unit"), - 336
], - 337
), - 338
), - 339
list( - 340
Primitive::StepList, - 341
"$.steps", - 342
row( - 343
Primitive::Section, - 344
&[("text", "$.text"), ("timer_seconds", "$.timer_seconds")], - 345
), - 346
), - 347
]; - 348
} else if RESEARCH_TYPES.contains(&accepts[0].as_str()) { - 349
root.children = vec![ - 350
list( - 351
Primitive::TakeawayList, - 352
"$.takeaways", - 353
row( - 354
Primitive::Section, - 355
&[ - 356
("text", "$.text"), - 357
("citation_indices", "$.citation_indices"), - 358
], - 359
), - 360
), - 361
list( - 362
Primitive::SourceList, - 363
"$.sources", - 364
row( - 365
Primitive::Section, - 366
&[ - 367
("title", "$.title"), - 368
("url", "$.url"), - 369
("source_name", "$.source_name"), - 370
], - 371
), - 372
), - 373
]; - 374
} else if accepts[0] == "coding.diff" { - 375
root.each = Some(Binding { - 376
path: "$.files".into(), - 377
required: true, - 378
empty: EmptyValue::Omit, - 379
}); - 380
root.item = Some(Box::new(row(Primitive::Section, &[("*", "$")]))); - 381
} else if accepts[0] == "test.report" { - 382
root.each = Some(Binding { - 383
path: "$.tests".into(), - 384
required: true, - 385
empty: EmptyValue::Omit, - 386
}); - 387
root.item = Some(Box::new(row(Primitive::Section, &[("*", "$")]))); - 388
} - 389
let spec = PresentationSpec { - 390
schema_version: super::SPEC_SCHEMA_VERSION, - 391
id: format!("seed.{id}"), - 392
// Seed definitions are immutable revisions. Bump this whenever the - 393
// declarative starter shape changes so an older persisted seed cannot - 394
// collide with the new digest at the same (id, revision) key. - 395
revision: if id == "travel-options" { 7 } else { 6 }, - 396
accepts, - 397
root, - 398
fallback: FallbackSpec::default(), - 399
accessibility: AccessibilitySpec { - 400
summary: Some(Binding { - 401
path: "$.summary".into(), - 402
required: false, - 403
empty: EmptyValue::EmptyText, - 404
}), - 405
}, - 406
metadata: BTreeMap::from([ - 407
(String::from("seed"), String::from("true")), - 408
(String::from("certified"), certified.to_string()), - 409
]), - 410
}; - 411
StoredPresentation { - 412
digest: match digest(&spec) { - 413
Ok(value) => value, - 414
Err(_) => String::new(), - 415
}, - 416
spec, - 417
origin: PresentationOrigin { - 418
scope: LibraryScope::Workspace, - 419
owner: "builtin".into(), - 420
plugin_id: None, - 421
generation: Some("seed-6-all-live-shapes".into()), - 422
}, - 423
enabled: false, - 424
} - 425
} - 426
- 427
/// Built-in domain and universal experiences. Records themselves are disabled - 428
/// so importing a definition grants no activation; the host applies defaults - 429
/// unless a person has explicitly suppressed or replaced a pack. - 430
pub fn built_in_seed_pack() -> Vec<StoredPresentation> { - 431
EVERYDAY - 432
.iter() - 433
.chain(CODING.iter()) - 434
.chain(UNIVERSAL.iter()) - 435
.map(|(id, accepts)| seed(id, accepts)) - 436
.collect() - 437
} - 438
- 439
#[cfg(test)] - 440
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 441
mod tests { - 442
use super::*; - 443
use crate::{CompileInput, CompiledPresentation, compile}; - 444
- 445
#[test] - 446
fn seed_pack_is_rich_disabled_and_validated_by_host_types() { - 447
let pack = built_in_seed_pack(); - 448
assert_eq!(pack.len(), 75); - 449
assert!(pack.iter().all(|record| !record.enabled)); - 450
assert!( - 451
pack.iter() - 452
.any(|record| record.spec.accepts == ["coding.diff"]) - 453
); - 454
assert!( - 455
pack.iter().all(|record| !record.digest.is_empty()), - 456
"{:?}", - 457
pack.iter() - 458
.filter(|record| record.digest.is_empty()) - 459
.map(|record| &record.spec.id) - 460
.collect::<Vec<_>>() - 461
); - 462
let mut primitives = Vec::new(); - 463
for record in &pack { - 464
if !primitives.contains(&record.spec.root.primitive) { - 465
primitives.push(record.spec.root.primitive); - 466
} - 467
} - 468
assert!(primitives.len() >= 6); - 469
} - 470
- 471
#[test] - 472
fn travel_options_pack_carries_its_choice_variant_through_storage_and_compile() { - 473
let record = built_in_seed_pack() - 474
.into_iter() - 475
.find(|record| record.spec.id == "seed.travel-options") - 476
.expect("travel options pack"); - 477
let encoded = serde_json::to_vec(&record.spec).expect("serialize built-in pack"); - 478
let parsed = crate::parse_spec(&encoded).expect("parse built-in pack"); - 479
let result = compile( - 480
&parsed, - 481
&CompileInput { - 482
semantic_type: "travel_options".into(), - 483
payload: serde_json::json!({ - 484
"title": "Saturday choices", - 485
"rows": [{"option": "Museum", "fit": "Indoor"}], - 486
}), - 487
fallback_text: "fallback".into(), - 488
}, - 489
); - 490
let CompiledPresentation::Rich(tree) = result else { - 491
panic!("travel pack did not render: {result:?}") - 492
}; - 493
assert_eq!( - 494
tree.root.props.get("variant"), - 495
Some(&serde_json::json!("options")) - 496
); - 497
} - 498
- 499
#[test] - 500
fn seeds_reach_the_newly_added_primitives() { - 501
let pack = built_in_seed_pack(); - 502
for expected in [Primitive::Recipe, Primitive::Research, Primitive::UiPreview] { - 503
assert!( - 504
pack.iter() - 505
.any(|record| record.spec.root.primitive == expected), - 506
"no seed reaches {expected:?}" - 507
); - 508
} - 509
} - 510
- 511
#[test] - 512
fn every_seed_has_a_known_live_payload_family() { - 513
for seed in built_in_seed_pack() { - 514
let kind = seed.spec.accepts[0].as_str(); - 515
assert!( - 516
TABLE_TYPES.contains(&kind) - 517
|| TIMELINE_TYPES.contains(&kind) - 518
|| RECIPE_TYPES.contains(&kind) - 519
|| RESEARCH_TYPES.contains(&kind) - 520
|| UNIVERSAL_TYPES.contains(&kind) - 521
|| matches!( - 522
kind, - 523
"metric" | "ui.preview" | "coding.diff" | "test.report" | "terminal.view" - 524
), - 525
"{} has no complete payload family", - 526
seed.spec.id - 527
); - 528
} - 529
} - 530
- 531
#[test] - 532
fn new_primitives_compile_rich_with_their_own_payloads() { - 533
// The enum additions are reachable end to end, not decorative: each - 534
// compiles through the same generic pipeline as every other seed. - 535
let cases = [ - 536
( - 537
"recipe", - 538
serde_json::json!({ - 539
"title": "Weeknight dal", - 540
"summary": "A fast lentil dal", - 541
"ingredients": [{"name":"Lentils","amount":1,"unit":"cup"}], - 542
"steps": [{"text":"Rinse lentils"},{"text":"Simmer 20 minutes"}], - 543
}), - 544
), - 545
( - 546
"news", - 547
serde_json::json!({ - 548
"title": "Grid storage in 2026", - 549
"summary": "Three takeaways with sources", - 550
"takeaways": [{"text":"Costs fell","citation_indices":[1]}], - 551
"sources": [{"title":"Report","url":"https://example.com"}], - 552
}), - 553
), - 554
( - 555
"ui.preview", - 556
serde_json::json!({ - 557
"title": "Settings panel", - 558
"summary": "Sandboxed preview of the authored markup", - 559
"items": ["Light", "Dark"], - 560
}), - 561
), - 562
]; - 563
let pack = built_in_seed_pack(); - 564
for (semantic_type, payload) in cases { - 565
let found = pack.iter().find(|record| { - 566
record - 567
.spec - 568
.accepts - 569
.iter() - 570
.any(|accepted| accepted == semantic_type) - 571
}); - 572
assert!(found.is_some(), "no seed accepts {semantic_type}"); - 573
let Some(record) = found else { continue }; - 574
let result = compile( - 575
&record.spec, - 576
&CompileInput { - 577
semantic_type: semantic_type.into(), - 578
payload, - 579
fallback_text: "Example result".into(), - 580
}, - 581
); - 582
assert!( - 583
matches!(&result, CompiledPresentation::Rich(tree) - 584
if tree.root.primitive == record.spec.root.primitive), - 585
"{semantic_type} did not compile rich through its own primitive: {result:?}" - 586
); - 587
} - 588
} - 589
- 590
#[test] - 591
fn every_seed_compiles_with_minimal_generic_payload() { - 592
// Keep the starter pack honest: a newly added seed must be consumable - 593
// by the same generic compiler used for user and plugin definitions. - 594
// This intentionally does not assert a domain-specific renderer. - 595
for record in built_in_seed_pack() { - 596
assert!( - 597
!record.spec.accepts.is_empty(), - 598
"seed accepts one semantic type" - 599
); - 600
let semantic_type = &record.spec.accepts[0]; - 601
let result = compile( - 602
&record.spec, - 603
&CompileInput { - 604
semantic_type: semantic_type.clone(), - 605
payload: serde_json::json!({ - 606
"title": "Example", - 607
"subtitle": "A reusable starter", - 608
"summary": "A concise example result", - 609
"items": [{"label":"One"},{"label":"Two"}], - 610
"columns": [{"key":"label","label":"Label"}], - 611
"rows": [{"label":"One"}], - 612
"ingredients": [{"name":"Oats","amount":1,"unit":"cup"}], - 613
"steps": [{"text":"Cook the oats"}], - 614
"takeaways": [{"text":"One takeaway"}], - 615
"sources": [{"title":"Source","url":"https://example.com"}], - 616
"files": [{"filename":"a.txt","hunks":"+hello","additions":1,"deletions":0}], - 617
"tests": [{"name":"works","status":"passed"}], - 618
}), - 619
fallback_text: "Example result".into(), - 620
}, - 621
); - 622
assert!( - 623
matches!(result, CompiledPresentation::Rich(_)), - 624
"seed {} failed generic compilation: {:?}", - 625
record.spec.id, - 626
result - 627
); - 628
} - 629
} - 630
- 631
#[test] - 632
fn certified_live_cards_keep_their_real_collections() { - 633
let cases = [ - 634
( - 635
"plan.timeline", - 636
serde_json::json!({"title":"Trip", "items":[{"label":"Leave","detail":"At eight"},{"label":"Arrive","detail":"At nine"}]}), - 637
2, - 638
), - 639
( - 640
"table", - 641
serde_json::json!({"title":"Options", "columns":[{"key":"name","label":"Name"}], "rows":[{"name":"Train"},{"name":"Bus"}]}), - 642
2, - 643
), - 644
( - 645
"recipe.card", - 646
serde_json::json!({"title":"Dal", "ingredients":[{"name":"Lentils","amount":1}], "steps":[{"text":"Rinse"}]}), - 647
2, - 648
), - 649
( - 650
"research.synthesis", - 651
serde_json::json!({"title":"Brief", "takeaways":[{"text":"Costs fell"}], "sources":[{"title":"Report","url":"https://example.com"}]}), - 652
2, - 653
), - 654
]; - 655
let pack = built_in_seed_pack(); - 656
for (semantic_type, payload, child_count) in cases { - 657
let record = pack - 658
.iter() - 659
.find(|record| record.spec.accepts.iter().any(|kind| kind == semantic_type)) - 660
.expect("certified seed"); - 661
assert_eq!( - 662
record.spec.metadata.get("certified").map(String::as_str), - 663
Some("true") - 664
); - 665
let CompiledPresentation::Rich(tree) = compile( - 666
&record.spec, - 667
&CompileInput { - 668
semantic_type: semantic_type.into(), - 669
payload, - 670
fallback_text: "Fallback".into(), - 671
}, - 672
) else { - 673
panic!("{semantic_type} must compile rich"); - 674
}; - 675
assert_eq!(tree.root.children.len(), child_count, "{semantic_type}"); - 676
match semantic_type { - 677
"plan.timeline" => assert_eq!(tree.root.children[0].props["label"], "Leave"), - 678
"table" => assert_eq!(tree.root.children[0].props["name"], "Train"), - 679
"recipe.card" => { - 680
assert_eq!(tree.root.children[0].children[0].props["name"], "Lentils") - 681
} - 682
"research.synthesis" => assert_eq!( - 683
tree.root.children[0].children[0].props["text"], - 684
"Costs fell" - 685
), - 686
_ => unreachable!(), - 687
} - 688
} - 689
} - 690
} - 691
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.