@emeryld/rrroutes-export 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@emeryld/rrroutes-export",
3
3
  "description": "Finalized leaves export helpers and CLI for RRRoutes",
4
- "version": "1.0.18",
4
+ "version": "1.0.20",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "dist/index.cjs",
@@ -24,7 +24,8 @@
24
24
 
25
25
  body {
26
26
  margin: 0;
27
- font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;
27
+ font-family:
28
+ 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;
28
29
  color: var(--text);
29
30
  background: var(--bg);
30
31
  }
@@ -61,7 +62,10 @@
61
62
  .primary-row {
62
63
  display: grid;
63
64
  gap: 10px;
64
- grid-template-columns: minmax(220px, 0.9fr) minmax(300px, 1.4fr) minmax(220px, 1fr);
65
+ grid-template-columns: minmax(220px, 0.9fr) minmax(300px, 1.4fr) minmax(
66
+ 220px,
67
+ 1fr
68
+ );
65
69
  align-items: end;
66
70
  }
67
71
 
@@ -374,6 +378,70 @@
374
378
  gap: 4px;
375
379
  }
376
380
 
381
+ .changelog-diff-line {
382
+ display: flex;
383
+ flex-wrap: wrap;
384
+ gap: 6px;
385
+ align-items: baseline;
386
+ }
387
+
388
+ .changelog-op.added {
389
+ font-weight: 700;
390
+ color: var(--ok);
391
+ }
392
+
393
+ .changelog-op.changed {
394
+ font-weight: 700;
395
+ color: var(--accent);
396
+ }
397
+
398
+ .changelog-op.removed {
399
+ color: var(--danger);
400
+ }
401
+
402
+ .changelog-schema-type {
403
+ color: var(--schema-accent);
404
+ }
405
+
406
+ .event-title-row {
407
+ display: inline-flex;
408
+ flex-wrap: wrap;
409
+ gap: 8px;
410
+ align-items: center;
411
+ }
412
+
413
+ .event-action-added {
414
+ color: var(--ok);
415
+ font-weight: 700;
416
+ }
417
+
418
+ .event-action-removed {
419
+ color: var(--danger);
420
+ }
421
+
422
+ .event-action-modified {
423
+ color: var(--accent);
424
+ font-weight: 700;
425
+ }
426
+
427
+ .event-icon {
428
+ display: inline-flex;
429
+ width: 14px;
430
+ height: 14px;
431
+ color: var(--muted);
432
+ }
433
+
434
+ .event-icon svg {
435
+ width: 14px;
436
+ height: 14px;
437
+ display: block;
438
+ }
439
+
440
+ .event-name {
441
+ font-weight: 700;
442
+ color: var(--text);
443
+ }
444
+
377
445
  .schema-block {
378
446
  border-top: 1px solid var(--border);
379
447
  padding: 8px 0 2px;
@@ -497,15 +565,29 @@
497
565
  </label>
498
566
  <label class="control-block">
499
567
  <span class="control-label">Search text</span>
500
- <input id="searchInput" type="text" placeholder="Type to search..." />
568
+ <input
569
+ id="searchInput"
570
+ type="text"
571
+ placeholder="Type to search..."
572
+ />
501
573
  </label>
502
574
  <div class="control-block">
503
575
  <span class="control-label">Quick filters</span>
504
576
  <div class="quick-toggles">
505
- <button id="caseSensitive" class="pill-toggle" type="button" aria-pressed="false">
577
+ <button
578
+ id="caseSensitive"
579
+ class="pill-toggle"
580
+ type="button"
581
+ aria-pressed="false"
582
+ >
506
583
  Case sensitive
507
584
  </button>
508
- <button id="regexSearch" class="pill-toggle" type="button" aria-pressed="false">
585
+ <button
586
+ id="regexSearch"
587
+ class="pill-toggle"
588
+ type="button"
589
+ aria-pressed="false"
590
+ >
509
591
  Regex
510
592
  </button>
511
593
  </div>
@@ -514,11 +596,21 @@
514
596
 
515
597
  <div class="scope-row">
516
598
  <div class="scope-actions">
517
- <button id="selectAllFields" class="scope-action" type="button">All</button>
518
- <button id="clearAllFields" class="scope-action" type="button">None</button>
519
- <button id="coreFields" class="scope-action" type="button">Core</button>
520
- <button id="schemasOnlyFields" class="scope-action" type="button">Schema</button>
521
- <button id="sourceOnlyFields" class="scope-action" type="button">Source</button>
599
+ <button id="selectAllFields" class="scope-action" type="button">
600
+ All
601
+ </button>
602
+ <button id="clearAllFields" class="scope-action" type="button">
603
+ None
604
+ </button>
605
+ <button id="coreFields" class="scope-action" type="button">
606
+ Core
607
+ </button>
608
+ <button id="schemasOnlyFields" class="scope-action" type="button">
609
+ Schema
610
+ </button>
611
+ <button id="sourceOnlyFields" class="scope-action" type="button">
612
+ Source
613
+ </button>
522
614
  </div>
523
615
  <div id="fieldChipGroups" class="scope-groups"></div>
524
616
  </div>
@@ -549,10 +641,20 @@
549
641
  <div class="scope-group">
550
642
  <div class="scope-group-title">Views</div>
551
643
  <div class="scope-group-chips">
552
- <button id="tabLeaves" class="field-chip" type="button" aria-pressed="true">
644
+ <button
645
+ id="tabLeaves"
646
+ class="field-chip"
647
+ type="button"
648
+ aria-pressed="true"
649
+ >
553
650
  Leaves
554
651
  </button>
555
- <button id="tabChangelog" class="field-chip" type="button" aria-pressed="false">
652
+ <button
653
+ id="tabChangelog"
654
+ class="field-chip"
655
+ type="button"
656
+ aria-pressed="false"
657
+ >
556
658
  Changelog
557
659
  </button>
558
660
  </div>
@@ -564,7 +666,10 @@
564
666
  <div id="activeFilterChips" class="chips filters"></div>
565
667
  </div>
566
668
 
567
- <div id="status" class="meta">Load a leaves export, changelog export, or unified bundle JSON to begin.</div>
669
+ <div id="status" class="meta">
670
+ Load a leaves export, changelog export, or unified bundle JSON to
671
+ begin.
672
+ </div>
568
673
  </div>
569
674
 
570
675
  <div id="results"></div>
@@ -582,10 +687,22 @@
582
687
  label: 'description',
583
688
  get: (leaf) => [leaf.cfg?.description],
584
689
  },
585
- { id: 'docsGroup', label: 'docsGroup', get: (leaf) => [leaf.cfg?.docsGroup] },
690
+ {
691
+ id: 'docsGroup',
692
+ label: 'docsGroup',
693
+ get: (leaf) => [leaf.cfg?.docsGroup],
694
+ },
586
695
  { id: 'tags', label: 'tags', get: (leaf) => leaf.cfg?.tags || [] },
587
- { id: 'stability', label: 'stability', get: (leaf) => [leaf.cfg?.stability] },
588
- { id: 'docsMeta', label: 'docsMeta', get: (leaf) => [leaf.cfg?.docsMeta] },
696
+ {
697
+ id: 'stability',
698
+ label: 'stability',
699
+ get: (leaf) => [leaf.cfg?.stability],
700
+ },
701
+ {
702
+ id: 'docsMeta',
703
+ label: 'docsMeta',
704
+ get: (leaf) => [leaf.cfg?.docsMeta],
705
+ },
589
706
  {
590
707
  id: 'sourceDefinition',
591
708
  label: 'source definition',
@@ -614,7 +731,8 @@
614
731
  {
615
732
  id: 'types',
616
733
  label: 'types',
617
- get: (leaf, schemaFlatByLeaf) => schemaTypeTokens(schemaFlatByLeaf?.[leaf.key]),
734
+ get: (leaf, schemaFlatByLeaf) =>
735
+ schemaTypeTokens(schemaFlatByLeaf?.[leaf.key]),
618
736
  },
619
737
  {
620
738
  id: 'params',
@@ -704,23 +822,23 @@
704
822
  {
705
823
  id: 'core',
706
824
  label: 'Core fields',
707
- fieldIds: SEARCH_FIELDS.filter((field) => CORE_FIELD_IDS.has(field.id)).map(
708
- (field) => field.id,
709
- ),
825
+ fieldIds: SEARCH_FIELDS.filter((field) =>
826
+ CORE_FIELD_IDS.has(field.id),
827
+ ).map((field) => field.id),
710
828
  },
711
829
  {
712
830
  id: 'schema',
713
831
  label: 'Schema fields',
714
- fieldIds: SEARCH_FIELDS.filter((field) => SCHEMA_SCOPE_FIELD_IDS.has(field.id)).map(
715
- (field) => field.id,
716
- ),
832
+ fieldIds: SEARCH_FIELDS.filter((field) =>
833
+ SCHEMA_SCOPE_FIELD_IDS.has(field.id),
834
+ ).map((field) => field.id),
717
835
  },
718
836
  {
719
837
  id: 'source',
720
838
  label: 'Source fields',
721
- fieldIds: SEARCH_FIELDS.filter((field) => SOURCE_FIELD_IDS.has(field.id)).map(
722
- (field) => field.id,
723
- ),
839
+ fieldIds: SEARCH_FIELDS.filter((field) =>
840
+ SOURCE_FIELD_IDS.has(field.id),
841
+ ).map((field) => field.id),
724
842
  },
725
843
  ]
726
844
 
@@ -757,7 +875,9 @@
757
875
  },
758
876
  [VIEW_IDS.changelog]: {
759
877
  fieldDefs: CHANGELOG_TIMELINE_FIELDS,
760
- defaultSelected: new Set(CHANGELOG_TIMELINE_FIELDS.map((field) => field.id)),
878
+ defaultSelected: new Set(
879
+ CHANGELOG_TIMELINE_FIELDS.map((field) => field.id),
880
+ ),
761
881
  },
762
882
  }
763
883
 
@@ -770,8 +890,12 @@
770
890
  viewerMode: VIEWER_MODE.leaves,
771
891
  availableTabs: new Set([VIEW_IDS.leaves]),
772
892
  selectedFieldIdsByTab: {
773
- [VIEW_IDS.leaves]: new Set(TAB_CONFIG[VIEW_IDS.leaves].defaultSelected),
774
- [VIEW_IDS.changelog]: new Set(TAB_CONFIG[VIEW_IDS.changelog].defaultSelected),
893
+ [VIEW_IDS.leaves]: new Set(
894
+ TAB_CONFIG[VIEW_IDS.leaves].defaultSelected,
895
+ ),
896
+ [VIEW_IDS.changelog]: new Set(
897
+ TAB_CONFIG[VIEW_IDS.changelog].defaultSelected,
898
+ ),
775
899
  },
776
900
  }
777
901
 
@@ -780,7 +904,9 @@
780
904
  const caseSensitiveToggle = document.getElementById('caseSensitive')
781
905
  const regexSearchToggle = document.getElementById('regexSearch')
782
906
  const typeMatchModeInput = document.getElementById('typeMatchMode')
783
- const schemaRowsMatchOnlyToggle = document.getElementById('schemaRowsMatchOnly')
907
+ const schemaRowsMatchOnlyToggle = document.getElementById(
908
+ 'schemaRowsMatchOnly',
909
+ )
784
910
  const fieldChipGroups = document.getElementById('fieldChipGroups')
785
911
  const activeFilterChips = document.getElementById('activeFilterChips')
786
912
  const selectAllFieldsBtn = document.getElementById('selectAllFields')
@@ -807,7 +933,9 @@
807
933
  }
808
934
 
809
935
  function applyViewerModeMeta() {
810
- const meta = VIEWER_MODE_META[state.viewerMode] || VIEWER_MODE_META[VIEWER_MODE.leaves]
936
+ const meta =
937
+ VIEWER_MODE_META[state.viewerMode] ||
938
+ VIEWER_MODE_META[VIEWER_MODE.leaves]
811
939
  document.title = meta.title
812
940
  if (viewerHeadingEl) viewerHeadingEl.textContent = meta.heading
813
941
  }
@@ -919,7 +1047,8 @@
919
1047
  function toTokens(value) {
920
1048
  if (value === null || value === undefined) return []
921
1049
  if (typeof value === 'string') return [value]
922
- if (typeof value === 'number' || typeof value === 'boolean') return [String(value)]
1050
+ if (typeof value === 'number' || typeof value === 'boolean')
1051
+ return [String(value)]
923
1052
  if (Array.isArray(value)) return value.flatMap((item) => toTokens(item))
924
1053
  if (typeof value === 'object') return [JSON.stringify(value)]
925
1054
  return []
@@ -958,7 +1087,8 @@
958
1087
  function schemaSectionToSearchTokens(flatSchema, sectionName) {
959
1088
  if (!flatSchema || typeof flatSchema !== 'object') return []
960
1089
  const sectionEntries = Object.entries(flatSchema).filter(
961
- ([path]) => path === sectionName || path.startsWith(`${sectionName}.`),
1090
+ ([path]) =>
1091
+ path === sectionName || path.startsWith(`${sectionName}.`),
962
1092
  )
963
1093
 
964
1094
  if (sectionEntries.length === 0) return []
@@ -1006,12 +1136,20 @@
1006
1136
  return Array.from(tokens)
1007
1137
  }
1008
1138
 
1009
- function schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds) {
1139
+ function schemaEntryMatchesActiveFilter(
1140
+ sectionName,
1141
+ fullPath,
1142
+ info,
1143
+ engine,
1144
+ selectedIds,
1145
+ ) {
1010
1146
  if (!engine.active) return true
1011
1147
 
1012
1148
  let matched = false
1013
1149
  if (selectedIds.includes(sectionName)) {
1014
- matched = schemaEntryTokens(fullPath, info).some((token) => engine.test(token))
1150
+ matched = schemaEntryTokens(fullPath, info).some((token) =>
1151
+ engine.test(token),
1152
+ )
1015
1153
  }
1016
1154
 
1017
1155
  if (!matched && selectedIds.includes('types')) {
@@ -1025,7 +1163,9 @@
1025
1163
  }
1026
1164
 
1027
1165
  if (typeMatchModeInput.value === 'exact' && !engine.regex) {
1028
- const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
1166
+ const needle = engine.caseSensitive
1167
+ ? engine.query
1168
+ : engine.query.toLowerCase()
1029
1169
  const normalized = engine.caseSensitive
1030
1170
  ? typeTokens
1031
1171
  : typeTokens.map((token) => token.toLowerCase())
@@ -1047,8 +1187,14 @@
1047
1187
  return SEARCH_FIELDS.some((field) => {
1048
1188
  if (!selectedIds.includes(field.id)) return false
1049
1189
  const tokens = toTokens(field.get(leaf, schemaFlatByLeaf, payload))
1050
- if (field.id === 'types' && typeMatchModeInput.value === 'exact' && !engine.regex) {
1051
- const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
1190
+ if (
1191
+ field.id === 'types' &&
1192
+ typeMatchModeInput.value === 'exact' &&
1193
+ !engine.regex
1194
+ ) {
1195
+ const needle = engine.caseSensitive
1196
+ ? engine.query
1197
+ : engine.query.toLowerCase()
1052
1198
  const normalized = engine.caseSensitive
1053
1199
  ? tokens
1054
1200
  : tokens.map((token) => String(token).toLowerCase())
@@ -1086,14 +1232,25 @@
1086
1232
  }
1087
1233
 
1088
1234
  function setHighlighted(node, text, engine, selectedIds, fieldIds) {
1089
- node.innerHTML = highlightWithFieldScope(text, engine, selectedIds, fieldIds)
1235
+ node.innerHTML = highlightWithFieldScope(
1236
+ text,
1237
+ engine,
1238
+ selectedIds,
1239
+ fieldIds,
1240
+ )
1090
1241
  }
1091
1242
 
1092
1243
  function kv(key, value, engine, selectedIds, fieldIds) {
1093
1244
  const box = el('div', 'kv')
1094
1245
  box.appendChild(el('div', 'k', key))
1095
1246
  const valueNode = el('div', 'v mono')
1096
- setHighlighted(valueNode, value === undefined ? '—' : String(value), engine, selectedIds, fieldIds)
1247
+ setHighlighted(
1248
+ valueNode,
1249
+ value === undefined ? '—' : String(value),
1250
+ engine,
1251
+ selectedIds,
1252
+ fieldIds,
1253
+ )
1097
1254
  box.appendChild(valueNode)
1098
1255
  return box
1099
1256
  }
@@ -1158,7 +1315,12 @@
1158
1315
  link.href = href
1159
1316
  link.target = '_blank'
1160
1317
  link.rel = 'noopener noreferrer'
1161
- link.innerHTML = highlightWithFieldScope(label, engine, selectedIds, fieldIds)
1318
+ link.innerHTML = highlightWithFieldScope(
1319
+ label,
1320
+ engine,
1321
+ selectedIds,
1322
+ fieldIds,
1323
+ )
1162
1324
  labelNode.appendChild(link)
1163
1325
  box.appendChild(labelNode)
1164
1326
  return box
@@ -1194,7 +1356,11 @@
1194
1356
 
1195
1357
  function getSchemaSource(source, sectionName) {
1196
1358
  const schemaKey = SCHEMA_SOURCE_KEYS_BY_SECTION[sectionName]
1197
- if (!schemaKey || !source?.schemas || typeof source.schemas !== 'object') {
1359
+ if (
1360
+ !schemaKey ||
1361
+ !source?.schemas ||
1362
+ typeof source.schemas !== 'object'
1363
+ ) {
1198
1364
  return {
1199
1365
  schemaKey,
1200
1366
  sourceValue: undefined,
@@ -1208,8 +1374,11 @@
1208
1374
  }
1209
1375
 
1210
1376
  function resolveSchemaSourceLabel(schemaKey, sourceValue) {
1211
- if (!sourceValue || typeof sourceValue !== 'object') return schemaKey || 'schema'
1212
- return sourceValue.sourceName || sourceValue.tag || schemaKey || 'schema'
1377
+ if (!sourceValue || typeof sourceValue !== 'object')
1378
+ return schemaKey || 'schema'
1379
+ return (
1380
+ sourceValue.sourceName || sourceValue.tag || schemaKey || 'schema'
1381
+ )
1213
1382
  }
1214
1383
 
1215
1384
  function createTreeNode(name = '') {
@@ -1226,7 +1395,10 @@
1226
1395
  Object.entries(entries)
1227
1396
  .sort(([a], [b]) => a.localeCompare(b))
1228
1397
  .forEach(([fullPath, info]) => {
1229
- const trimmed = fullPath === sectionName ? '' : fullPath.slice(sectionName.length + 1)
1398
+ const trimmed =
1399
+ fullPath === sectionName
1400
+ ? ''
1401
+ : fullPath.slice(sectionName.length + 1)
1230
1402
  const segments = trimmed ? trimmed.split('.') : []
1231
1403
 
1232
1404
  let current = root
@@ -1268,10 +1440,13 @@
1268
1440
 
1269
1441
  const appendTypeCell = (row, info) => {
1270
1442
  const type = el('span', 'tree-pill')
1271
- setHighlighted(type, info?.type || info?.kind || '—', engine, selectedIds, [
1272
- sectionName,
1273
- 'types',
1274
- ])
1443
+ setHighlighted(
1444
+ type,
1445
+ info?.type || info?.kind || '',
1446
+ engine,
1447
+ selectedIds,
1448
+ [sectionName, 'types'],
1449
+ )
1275
1450
  row.appendChild(type)
1276
1451
  }
1277
1452
 
@@ -1292,7 +1467,13 @@
1292
1467
  .sort((a, b) => a.localeCompare(b))
1293
1468
  .forEach((key) =>
1294
1469
  container.appendChild(
1295
- renderTreeNode(node.children[key], engine, selectedIds, sectionName, false),
1470
+ renderTreeNode(
1471
+ node.children[key],
1472
+ engine,
1473
+ selectedIds,
1474
+ sectionName,
1475
+ false,
1476
+ ),
1296
1477
  ),
1297
1478
  )
1298
1479
  details.appendChild(container)
@@ -1313,7 +1494,8 @@
1313
1494
 
1314
1495
  const grouped = splitFlatSchemaBySection(flatSchema)
1315
1496
  let hasAnySchemaEntries = false
1316
- const limitToMatchedRows = isPressed(schemaRowsMatchOnlyToggle) && engine.active
1497
+ const limitToMatchedRows =
1498
+ isPressed(schemaRowsMatchOnlyToggle) && engine.active
1317
1499
 
1318
1500
  SCHEMA_SECTIONS.forEach((sectionName) => {
1319
1501
  const rawEntries = grouped[sectionName]
@@ -1322,7 +1504,13 @@
1322
1504
  const entries = limitToMatchedRows
1323
1505
  ? Object.fromEntries(
1324
1506
  Object.entries(rawEntries).filter(([fullPath, info]) =>
1325
- schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds),
1507
+ schemaEntryMatchesActiveFilter(
1508
+ sectionName,
1509
+ fullPath,
1510
+ info,
1511
+ engine,
1512
+ selectedIds,
1513
+ ),
1326
1514
  ),
1327
1515
  )
1328
1516
  : rawEntries
@@ -1351,7 +1539,9 @@
1351
1539
  }
1352
1540
 
1353
1541
  const tree = buildSchemaTree(entries, sectionName)
1354
- block.appendChild(renderTreeNode(tree, engine, selectedIds, sectionName, true))
1542
+ block.appendChild(
1543
+ renderTreeNode(tree, engine, selectedIds, sectionName, true),
1544
+ )
1355
1545
 
1356
1546
  section.appendChild(block)
1357
1547
  })
@@ -1378,7 +1568,9 @@
1378
1568
  const overview = el('div', 'section')
1379
1569
  overview.appendChild(el('h3', '', 'Overview'))
1380
1570
  const topGrid = el('div', 'grid-3')
1381
- topGrid.appendChild(kv('group', cfg.docsGroup, engine, selectedIds, 'docsGroup'))
1571
+ topGrid.appendChild(
1572
+ kv('group', cfg.docsGroup, engine, selectedIds, 'docsGroup'),
1573
+ )
1382
1574
  topGrid.appendChild(
1383
1575
  kv(
1384
1576
  'tags',
@@ -1388,10 +1580,22 @@
1388
1580
  'tags',
1389
1581
  ),
1390
1582
  )
1391
- topGrid.appendChild(kv('stability', cfg.stability, engine, selectedIds, 'stability'))
1583
+ topGrid.appendChild(
1584
+ kv('stability', cfg.stability, engine, selectedIds, 'stability'),
1585
+ )
1392
1586
  overview.appendChild(topGrid)
1393
- overview.appendChild(kv('summary', cfg.summary, engine, selectedIds, 'summary'))
1394
- overview.appendChild(kv('description', cfg.description, engine, selectedIds, 'description'))
1587
+ overview.appendChild(
1588
+ kv('summary', cfg.summary, engine, selectedIds, 'summary'),
1589
+ )
1590
+ overview.appendChild(
1591
+ kv(
1592
+ 'description',
1593
+ cfg.description,
1594
+ engine,
1595
+ selectedIds,
1596
+ 'description',
1597
+ ),
1598
+ )
1395
1599
 
1396
1600
  const iconRow = el('div', 'icon-row')
1397
1601
  if (cfg.feed) {
@@ -1417,13 +1621,6 @@
1417
1621
  }
1418
1622
  content.appendChild(overview)
1419
1623
 
1420
- const fullConfig = el('div', 'section')
1421
- fullConfig.appendChild(el('h3', '', 'Configuration (full)'))
1422
- const cfgJson = el('pre', 'mono')
1423
- cfgJson.textContent = JSON.stringify(cfg, null, 2)
1424
- fullConfig.appendChild(cfgJson)
1425
- content.appendChild(fullConfig)
1426
-
1427
1624
  const files = el('div', 'section')
1428
1625
  if (Array.isArray(cfg.bodyFiles) && cfg.bodyFiles.length > 0) {
1429
1626
  files.appendChild(el('h3', '', 'Body Files'))
@@ -1441,11 +1638,22 @@
1441
1638
  const source = sourceByLeaf[leaf.key]
1442
1639
  if (source?.definition) {
1443
1640
  overview.appendChild(
1444
- createSourceRow('definition', source.definition, engine, selectedIds, 'sourceDefinition'),
1641
+ createSourceRow(
1642
+ 'definition',
1643
+ source.definition,
1644
+ engine,
1645
+ selectedIds,
1646
+ 'sourceDefinition',
1647
+ ),
1445
1648
  )
1446
1649
  }
1447
1650
 
1448
- const separatedSchemas = renderSeparatedSchemas(flatSchema, engine, selectedIds, source)
1651
+ const separatedSchemas = renderSeparatedSchemas(
1652
+ flatSchema,
1653
+ engine,
1654
+ selectedIds,
1655
+ source,
1656
+ )
1449
1657
  if (separatedSchemas) {
1450
1658
  content.appendChild(separatedSchemas)
1451
1659
  }
@@ -1453,12 +1661,22 @@
1453
1661
  const routeHistory = state.changelogPayload?.byRoute?.[leaf.key]
1454
1662
  if (Array.isArray(routeHistory) && routeHistory.length > 0) {
1455
1663
  const commitBySha = new Map(
1456
- (state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
1664
+ (state.changelogPayload?.commits || []).map((commit) => [
1665
+ commit.sha,
1666
+ commit,
1667
+ ]),
1457
1668
  )
1458
1669
  const commitOrder = new Map(
1459
- (state.changelogPayload?.commits || []).map((commit, index) => [commit.sha, index]),
1670
+ (state.changelogPayload?.commits || []).map((commit, index) => [
1671
+ commit.sha,
1672
+ index,
1673
+ ]),
1674
+ )
1675
+ const routeHistoryNewestFirst = sortEventsNewestFirst(
1676
+ routeHistory,
1677
+ commitBySha,
1678
+ commitOrder,
1460
1679
  )
1461
- const routeHistoryNewestFirst = sortEventsNewestFirst(routeHistory, commitBySha, commitOrder)
1462
1680
  const routeChangelogSection = el('div', 'section')
1463
1681
  const routeChangelogDetails = el('details')
1464
1682
  const routeChangelogSummary = el(
@@ -1473,7 +1691,12 @@
1473
1691
 
1474
1692
  const routeChangelogBody = el('div', 'leaf-content')
1475
1693
  routeHistoryNewestFirst.forEach((event) => {
1476
- routeChangelogBody.appendChild(renderEventCard(event, commitBySha))
1694
+ routeChangelogBody.appendChild(
1695
+ renderEventCard(event, commitBySha, {
1696
+ showTitle: true,
1697
+ item: { eventKind: 'route', routeKey: leaf.key, event },
1698
+ }),
1699
+ )
1477
1700
  })
1478
1701
  routeChangelogDetails.appendChild(routeChangelogBody)
1479
1702
  routeChangelogSection.appendChild(routeChangelogDetails)
@@ -1525,18 +1748,134 @@
1525
1748
  return parts.join(', ')
1526
1749
  }
1527
1750
 
1751
+ function schemaFlags(nullable, optional) {
1752
+ return `${nullable ? '-' : ''}${optional ? '?' : ''}`
1753
+ }
1754
+
1755
+ function formatEntryTypeLabel(entry) {
1756
+ if (!entry || typeof entry !== 'object') return null
1757
+ const type = typeof entry.type === 'string' ? entry.type : 'unknown'
1758
+ return `${type}${schemaFlags(Boolean(entry.nullable), Boolean(entry.optional))}`
1759
+ }
1760
+
1761
+ function formatPathLabel(path) {
1762
+ const basePath = typeof path === 'string' ? path : ''
1763
+ return basePath
1764
+ }
1765
+
1766
+ function parseSignatureTypeLabel(signature) {
1767
+ if (typeof signature !== 'string') return null
1768
+ const typeMatch = signature.match(/(?:^|,\s*)type=([^,]+)/)
1769
+ const nullableMatch = signature.match(/(?:^|,\s*)nullable=(true|false)/)
1770
+ const optionalMatch = signature.match(/(?:^|,\s*)optional=(true|false)/)
1771
+ if (!typeMatch) return null
1772
+ const type = typeMatch[1]
1773
+ const nullable = nullableMatch?.[1] === 'true'
1774
+ const optional = optionalMatch?.[1] === 'true'
1775
+ return `${type}${schemaFlags(nullable, optional)}`
1776
+ }
1777
+
1778
+ function formatSourceTypeLabel(signatures) {
1779
+ if (!Array.isArray(signatures) || signatures.length === 0) return null
1780
+ const labels = signatures
1781
+ .map((item) => parseSignatureTypeLabel(item))
1782
+ .filter(Boolean)
1783
+ if (labels.length === 0) return null
1784
+ return labels.join(' | ')
1785
+ }
1786
+
1787
+ function schemaDiffOperation(op) {
1788
+ if (op === 'added') {
1789
+ return { symbol: '+', word: 'added', className: 'added' }
1790
+ }
1791
+ if (op === 'removed') {
1792
+ return { symbol: '-', word: 'removed', className: 'removed' }
1793
+ }
1794
+ return { symbol: '*', word: 'changed', className: 'changed' }
1795
+ }
1796
+
1797
+ function buildSchemaDiffLine(op, beforeType, pathLabel, afterType) {
1798
+ if (!pathLabel) return null
1799
+ const line = el('div', 'meta mono changelog-diff-line')
1800
+ const operation = schemaDiffOperation(op)
1801
+ line.appendChild(
1802
+ el(
1803
+ 'span',
1804
+ `changelog-op ${operation.className}`,
1805
+ `${operation.symbol} ${operation.word}`,
1806
+ ),
1807
+ )
1808
+ if (beforeType) {
1809
+ line.appendChild(el('span', 'changelog-schema-type', beforeType))
1810
+ }
1811
+ line.appendChild(el('span', '', pathLabel))
1812
+ if (operation.className === 'changed' && afterType) {
1813
+ line.appendChild(el('span', '', '->'))
1814
+ line.appendChild(el('span', 'changelog-schema-type', afterType))
1815
+ }
1816
+ return line
1817
+ }
1818
+
1819
+ function buildLegacySchemaDiffLine(text) {
1820
+ return el('div', 'meta mono', text)
1821
+ }
1822
+
1528
1823
  function formatSchemaDiffLine(delta) {
1529
1824
  if (!delta || typeof delta !== 'object') return null
1530
1825
  if ('section' in delta) {
1531
1826
  const location = `${delta.section}.${delta.path}`
1532
- if (delta.op === 'added') return `added ${location}: ${JSON.stringify(delta.after || [])}`
1533
- if (delta.op === 'removed') return `removed ${location}: ${JSON.stringify(delta.before || [])}`
1534
- return `changed ${location}: ${JSON.stringify(delta.before || [])} -> ${JSON.stringify(delta.after || [])}`
1827
+ if (delta.op === 'added') {
1828
+ const afterType = formatSourceTypeLabel(delta.after)
1829
+ if (afterType) return buildSchemaDiffLine(delta.op, afterType, location)
1830
+ return buildLegacySchemaDiffLine(
1831
+ `added ${location}: ${JSON.stringify(delta.after || [])}`,
1832
+ )
1833
+ }
1834
+ if (delta.op === 'removed') {
1835
+ const beforeType = formatSourceTypeLabel(delta.before)
1836
+ if (beforeType)
1837
+ return buildSchemaDiffLine(delta.op, beforeType, location)
1838
+ return buildLegacySchemaDiffLine(
1839
+ `removed ${location}: ${JSON.stringify(delta.before || [])}`,
1840
+ )
1841
+ }
1842
+ const beforeType = formatSourceTypeLabel(delta.before)
1843
+ const afterType = formatSourceTypeLabel(delta.after)
1844
+ if (beforeType || afterType) {
1845
+ return buildSchemaDiffLine(
1846
+ delta.op,
1847
+ beforeType || 'unknown',
1848
+ location,
1849
+ afterType || 'unknown',
1850
+ )
1851
+ }
1852
+ return buildLegacySchemaDiffLine(
1853
+ `changed ${location}: ${JSON.stringify(delta.before || [])} -> ${JSON.stringify(delta.after || [])}`,
1854
+ )
1535
1855
  }
1536
1856
  if ('path' in delta) {
1537
- if (delta.op === 'added') return `added ${delta.path}: ${formatRouteSchemaEntry(delta.after)}`
1538
- if (delta.op === 'removed') return `removed ${delta.path}: ${formatRouteSchemaEntry(delta.before)}`
1539
- return `changed ${delta.path}: ${formatRouteSchemaEntry(delta.before)} -> ${formatRouteSchemaEntry(delta.after)}`
1857
+ if (delta.op === 'added') {
1858
+ const afterType = formatEntryTypeLabel(delta.after)
1859
+ const pathLabel = formatPathLabel(delta.path)
1860
+ if (afterType) return buildSchemaDiffLine(delta.op, afterType, pathLabel)
1861
+ return buildLegacySchemaDiffLine(
1862
+ `added ${delta.path}: ${formatRouteSchemaEntry(delta.after)}`,
1863
+ )
1864
+ }
1865
+ if (delta.op === 'removed') {
1866
+ const beforeType = formatEntryTypeLabel(delta.before)
1867
+ const pathLabel = formatPathLabel(delta.path)
1868
+ if (beforeType)
1869
+ return buildSchemaDiffLine(delta.op, beforeType, pathLabel)
1870
+ return buildLegacySchemaDiffLine(
1871
+ `removed ${delta.path}: ${formatRouteSchemaEntry(delta.before)}`,
1872
+ )
1873
+ }
1874
+ const beforeType = formatEntryTypeLabel(delta.before) || 'unknown'
1875
+ const afterType = formatEntryTypeLabel(delta.after) || 'unknown'
1876
+ const pathLabel =
1877
+ formatPathLabel(delta.path) || delta.path
1878
+ return buildSchemaDiffLine(delta.op, beforeType, pathLabel, afterType)
1540
1879
  }
1541
1880
  return null
1542
1881
  }
@@ -1546,7 +1885,92 @@
1546
1885
  return `${diff.field}: ${JSON.stringify(diff.before)} -> ${JSON.stringify(diff.after)}`
1547
1886
  }
1548
1887
 
1549
- function renderEventCard(event, commitBySha) {
1888
+ const EVENT_ICON_SVGS = {
1889
+ route:
1890
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 13V3.5h11V13"/><path d="M8 3.5v9.5"/><path d="M5.5 6h.01M10.5 10h.01"/></svg>',
1891
+ source:
1892
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="8" cy="3.5" rx="4.8" ry="2.2"/><path d="M3.2 3.5v7c0 1.2 2.1 2.2 4.8 2.2s4.8-1 4.8-2.2v-7"/><path d="M3.2 7c0 1.2 2.1 2.2 4.8 2.2s4.8-1 4.8-2.2"/></svg>',
1893
+ schema:
1894
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 4.2L8 1.8l5.5 2.4L8 6.6 2.5 4.2Z"/><path d="M2.5 8L8 5.6 13.5 8 8 10.4 2.5 8Z"/><path d="M2.5 11.8 8 9.4l5.5 2.4L8 14.2l-5.5-2.4Z"/></svg>',
1895
+ config:
1896
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12"/><path d="M2 8h12"/><path d="M2 12h12"/><circle cx="6" cy="4" r="1.6" fill="currentColor" stroke="none"/><circle cx="10.5" cy="8" r="1.6" fill="currentColor" stroke="none"/><circle cx="4.5" cy="12" r="1.6" fill="currentColor" stroke="none"/></svg>',
1897
+ }
1898
+
1899
+ function eventVisualByType(type) {
1900
+ switch (type) {
1901
+ case 'route_added':
1902
+ return { actionText: 'added', actionClass: 'event-action-added', icon: 'route' }
1903
+ case 'route_removed':
1904
+ return {
1905
+ actionText: 'removed',
1906
+ actionClass: 'event-action-removed',
1907
+ icon: 'route',
1908
+ }
1909
+ case 'schema_changed':
1910
+ return {
1911
+ actionText: 'modified schema',
1912
+ actionClass: 'event-action-modified',
1913
+ icon: 'schema',
1914
+ }
1915
+ case 'cfg_changed':
1916
+ return {
1917
+ actionText: 'modified config',
1918
+ actionClass: 'event-action-modified',
1919
+ icon: 'config',
1920
+ }
1921
+ case 'source_object_added':
1922
+ return { actionText: 'added', actionClass: 'event-action-added', icon: 'source' }
1923
+ case 'source_object_removed':
1924
+ return {
1925
+ actionText: 'removed',
1926
+ actionClass: 'event-action-removed',
1927
+ icon: 'source',
1928
+ }
1929
+ case 'source_schema_changed':
1930
+ return {
1931
+ actionText: 'modified source object',
1932
+ actionClass: 'event-action-modified',
1933
+ icon: 'source',
1934
+ }
1935
+ default:
1936
+ return {
1937
+ actionText: 'modified',
1938
+ actionClass: 'event-action-modified',
1939
+ icon: 'schema',
1940
+ }
1941
+ }
1942
+ }
1943
+
1944
+ function resolveEventName(item) {
1945
+ if (!item || item.eventKind === 'source') {
1946
+ return item?.displayName || item?.sourceObjectId || 'source object'
1947
+ }
1948
+ const method =
1949
+ typeof item?.event?.method === 'string' ? item.event.method.trim() : ''
1950
+ const path =
1951
+ typeof item?.event?.path === 'string' ? item.event.path.trim() : ''
1952
+ if (method && path) return `${method.toUpperCase()} ${path}`
1953
+ if (path) return path
1954
+ return item?.routeKey || 'route'
1955
+ }
1956
+
1957
+ function renderEventIcon(iconType) {
1958
+ const icon = el('span', `event-icon event-icon-${iconType}`)
1959
+ icon.setAttribute('aria-hidden', 'true')
1960
+ icon.innerHTML = EVENT_ICON_SVGS[iconType] || EVENT_ICON_SVGS.schema
1961
+ return icon
1962
+ }
1963
+
1964
+ function renderEventTitle(item) {
1965
+ const row = el('div', 'event-title-row mono')
1966
+ const visual = eventVisualByType(item?.event?.type)
1967
+ row.appendChild(el('span', visual.actionClass, visual.actionText))
1968
+ row.appendChild(renderEventIcon(visual.icon))
1969
+ row.appendChild(el('span', 'event-name', `[${resolveEventName(item)}]`))
1970
+ return row
1971
+ }
1972
+
1973
+ function renderEventCard(event, commitBySha, options = {}) {
1550
1974
  const card = el('div', 'leaf-content')
1551
1975
  const commit = commitBySha.get(event.commitSha)
1552
1976
  const commitDateTime = formatCommitDateTime(commit?.authorDate)
@@ -1559,19 +1983,25 @@
1559
1983
  .filter(Boolean)
1560
1984
  .join(' - ')
1561
1985
  card.appendChild(el('div', 'meta', commitMeta))
1562
- card.appendChild(el('div', 'mono', event.type))
1986
+ if (options.showTitle && options.item) {
1987
+ card.appendChild(renderEventTitle(options.item))
1988
+ }
1563
1989
  if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
1564
- card.appendChild(el('div', 'meta', `schema changes: ${event.schemaDiff.length}`))
1990
+ card.appendChild(
1991
+ el('div', 'meta', `schema changes: ${event.schemaDiff.length}`),
1992
+ )
1565
1993
  const schemaChanges = el('div')
1566
1994
  event.schemaDiff.forEach((delta) => {
1567
- const line = formatSchemaDiffLine(delta)
1568
- if (!line) return
1569
- schemaChanges.appendChild(el('div', 'meta', line))
1995
+ const lineNode = formatSchemaDiffLine(delta)
1996
+ if (!lineNode) return
1997
+ schemaChanges.appendChild(lineNode)
1570
1998
  })
1571
1999
  card.appendChild(schemaChanges)
1572
2000
  }
1573
2001
  if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
1574
- card.appendChild(el('div', 'meta', `cfg changes: ${event.cfgDiff.length}`))
2002
+ card.appendChild(
2003
+ el('div', 'meta', `cfg changes: ${event.cfgDiff.length}`),
2004
+ )
1575
2005
  const cfgChanges = el('div')
1576
2006
  event.cfgDiff.forEach((diff) => {
1577
2007
  const line = formatCfgDiffLine(diff)
@@ -1580,8 +2010,17 @@
1580
2010
  })
1581
2011
  card.appendChild(cfgChanges)
1582
2012
  }
1583
- if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
1584
- card.appendChild(el('div', 'meta', `impacted routes: ${event.impactedRoutes.length}`))
2013
+ if (
2014
+ Array.isArray(event.impactedRoutes) &&
2015
+ event.impactedRoutes.length > 0
2016
+ ) {
2017
+ card.appendChild(
2018
+ el(
2019
+ 'div',
2020
+ 'meta',
2021
+ `impacted routes: ${event.impactedRoutes.length}`,
2022
+ ),
2023
+ )
1585
2024
  }
1586
2025
  return card
1587
2026
  }
@@ -1599,16 +2038,19 @@
1599
2038
  .join(' - ')
1600
2039
  }
1601
2040
 
1602
- function renderChangelogEventLines(event, commitBySha, contextLabel) {
2041
+ function renderChangelogEventLines(item, commitBySha) {
2042
+ const event = item.event
1603
2043
  const row = el('div', 'changelog-line-item')
1604
- row.appendChild(el('div', 'meta', buildCommitMetaLine(event, commitBySha)))
1605
- row.appendChild(el('div', 'mono', `${contextLabel} (${event.type})`))
2044
+ row.appendChild(
2045
+ el('div', 'meta', buildCommitMetaLine(event, commitBySha)),
2046
+ )
2047
+ row.appendChild(renderEventTitle(item))
1606
2048
 
1607
2049
  if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
1608
2050
  event.schemaDiff.forEach((delta) => {
1609
- const line = formatSchemaDiffLine(delta)
1610
- if (!line) return
1611
- row.appendChild(el('div', 'meta mono', line))
2051
+ const lineNode = formatSchemaDiffLine(delta)
2052
+ if (!lineNode) return
2053
+ row.appendChild(lineNode)
1612
2054
  })
1613
2055
  }
1614
2056
  if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
@@ -1618,9 +2060,16 @@
1618
2060
  row.appendChild(el('div', 'meta mono', line))
1619
2061
  })
1620
2062
  }
1621
- if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
2063
+ if (
2064
+ Array.isArray(event.impactedRoutes) &&
2065
+ event.impactedRoutes.length > 0
2066
+ ) {
1622
2067
  row.appendChild(
1623
- el('div', 'meta mono', `impacted routes: ${event.impactedRoutes.join(', ')}`),
2068
+ el(
2069
+ 'div',
2070
+ 'meta mono',
2071
+ `impacted routes: ${event.impactedRoutes.join(', ')}`,
2072
+ ),
1624
2073
  )
1625
2074
  }
1626
2075
 
@@ -1643,7 +2092,9 @@
1643
2092
  const indexB = commitOrder.get(b?.commitSha) ?? -1
1644
2093
  if (indexA !== indexB) return indexB - indexA
1645
2094
 
1646
- return String(b?.commitSha || '').localeCompare(String(a?.commitSha || ''))
2095
+ return String(b?.commitSha || '').localeCompare(
2096
+ String(a?.commitSha || ''),
2097
+ )
1647
2098
  }
1648
2099
 
1649
2100
  function sortEventsNewestFirst(events, commitBySha, commitOrder) {
@@ -1654,8 +2105,12 @@
1654
2105
 
1655
2106
  function buildChronologicalEvents(payload) {
1656
2107
  if (!payload) return []
1657
- const commitBySha = new Map((payload.commits || []).map((commit) => [commit.sha, commit]))
1658
- const commitOrder = new Map((payload.commits || []).map((commit, index) => [commit.sha, index]))
2108
+ const commitBySha = new Map(
2109
+ (payload.commits || []).map((commit) => [commit.sha, commit]),
2110
+ )
2111
+ const commitOrder = new Map(
2112
+ (payload.commits || []).map((commit, index) => [commit.sha, index]),
2113
+ )
1659
2114
  const rows = []
1660
2115
  let sequence = 0
1661
2116
 
@@ -1673,24 +2128,27 @@
1673
2128
  })
1674
2129
  })
1675
2130
 
1676
- Object.entries(payload.bySourceObject || {}).forEach(([sourceObjectId, events]) => {
1677
- ;(events || []).forEach((event) => {
1678
- rows.push({
1679
- eventKind: 'source',
1680
- sourceObjectId,
1681
- displayName: event.displayName || sourceObjectId,
1682
- event,
1683
- commit: commitBySha.get(event.commitSha),
1684
- commitSort: commitTimestamp(commitBySha.get(event.commitSha)),
1685
- commitIndex: commitOrder.get(event.commitSha) ?? -1,
1686
- eventIndex: sequence++,
2131
+ Object.entries(payload.bySourceObject || {}).forEach(
2132
+ ([sourceObjectId, events]) => {
2133
+ ;(events || []).forEach((event) => {
2134
+ rows.push({
2135
+ eventKind: 'source',
2136
+ sourceObjectId,
2137
+ displayName: event.displayName || sourceObjectId,
2138
+ event,
2139
+ commit: commitBySha.get(event.commitSha),
2140
+ commitSort: commitTimestamp(commitBySha.get(event.commitSha)),
2141
+ commitIndex: commitOrder.get(event.commitSha) ?? -1,
2142
+ eventIndex: sequence++,
2143
+ })
1687
2144
  })
1688
- })
1689
- })
2145
+ },
2146
+ )
1690
2147
 
1691
2148
  rows.sort((a, b) => {
1692
2149
  if (a.commitSort !== b.commitSort) return b.commitSort - a.commitSort
1693
- if (a.commitIndex !== b.commitIndex) return b.commitIndex - a.commitIndex
2150
+ if (a.commitIndex !== b.commitIndex)
2151
+ return b.commitIndex - a.commitIndex
1694
2152
  return b.eventIndex - a.eventIndex
1695
2153
  })
1696
2154
 
@@ -1709,7 +2167,8 @@
1709
2167
  function matchesGenericItem(item, fieldDefs, engine, selectedIds) {
1710
2168
  if (!engine.active) return true
1711
2169
  return fieldDefs.some((field) => {
1712
- if (selectedIds.length > 0 && !selectedIds.includes(field.id)) return false
2170
+ if (selectedIds.length > 0 && !selectedIds.includes(field.id))
2171
+ return false
1713
2172
  const tokens = toTokens(field.get(item))
1714
2173
  return tokens.some((token) => engine.test(token))
1715
2174
  })
@@ -1728,8 +2187,14 @@
1728
2187
  function renderTabButtons() {
1729
2188
  setPressed(tabLeavesBtn, state.activeTab === VIEW_IDS.leaves)
1730
2189
  setPressed(tabChangelogBtn, state.activeTab === VIEW_IDS.changelog)
1731
- tabLeavesBtn.style.display = state.availableTabs.has(VIEW_IDS.leaves) ? '' : 'none'
1732
- tabChangelogBtn.style.display = state.availableTabs.has(VIEW_IDS.changelog) ? '' : 'none'
2190
+ tabLeavesBtn.style.display = state.availableTabs.has(VIEW_IDS.leaves)
2191
+ ? ''
2192
+ : 'none'
2193
+ tabChangelogBtn.style.display = state.availableTabs.has(
2194
+ VIEW_IDS.changelog,
2195
+ )
2196
+ ? ''
2197
+ : 'none'
1733
2198
  const isLeavesTab = state.activeTab === VIEW_IDS.leaves
1734
2199
  coreFieldsBtn.style.display = isLeavesTab ? '' : 'none'
1735
2200
  schemasOnlyFieldsBtn.style.display = isLeavesTab ? '' : 'none'
@@ -1745,7 +2210,9 @@
1745
2210
  if (engine.error) {
1746
2211
  statusEl.textContent = `Invalid regex: ${engine.error}`
1747
2212
  resultsEl.innerHTML = ''
1748
- resultsEl.appendChild(el('div', 'empty', 'Fix the regex to continue.'))
2213
+ resultsEl.appendChild(
2214
+ el('div', 'empty', 'Fix the regex to continue.'),
2215
+ )
1749
2216
  renderActiveFilterChips({
1750
2217
  selectedIds: selectedFieldIds(),
1751
2218
  hasRegexError: true,
@@ -1758,27 +2225,32 @@
1758
2225
  renderTabButtons()
1759
2226
  renderActiveFilterChips({ selectedIds, hasRegexError: false })
1760
2227
  if (state.activeTab === VIEW_IDS.leaves) {
1761
- const filtered = state.leaves.filter((leaf) => matchesLeaf(leaf, engine, selectedIds))
2228
+ const filtered = state.leaves.filter((leaf) =>
2229
+ matchesLeaf(leaf, engine, selectedIds),
2230
+ )
1762
2231
  statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
1763
- renderCollection(filtered, (leaf) => renderLeaf(leaf, engine, selectedIds), 'No matches.')
2232
+ renderCollection(
2233
+ filtered,
2234
+ (leaf) => renderLeaf(leaf, engine, selectedIds),
2235
+ 'No matches.',
2236
+ )
1764
2237
  } else {
1765
2238
  const fieldDefs = currentFieldDefs()
1766
2239
  const items = changelogItemsByTab(state.activeTab)
1767
- const filtered = items.filter((item) => matchesGenericItem(item, fieldDefs, engine, selectedIds))
2240
+ const filtered = items.filter((item) =>
2241
+ matchesGenericItem(item, fieldDefs, engine, selectedIds),
2242
+ )
1768
2243
  statusEl.textContent = `${filtered.length} / ${items.length} changelog events matched.`
1769
2244
  const commitBySha = new Map(
1770
- (state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
2245
+ (state.changelogPayload?.commits || []).map((commit) => [
2246
+ commit.sha,
2247
+ commit,
2248
+ ]),
1771
2249
  )
1772
2250
  if (state.viewerMode === VIEWER_MODE.changelog) {
1773
2251
  renderCollection(
1774
2252
  filtered,
1775
- (item) => {
1776
- const contextLabel =
1777
- item.eventKind === 'route'
1778
- ? `Route: ${item.routeKey}`
1779
- : `Source: ${item.displayName || item.sourceObjectId}`
1780
- return renderChangelogEventLines(item.event, commitBySha, contextLabel)
1781
- },
2253
+ (item) => renderChangelogEventLines(item, commitBySha),
1782
2254
  'No matches.',
1783
2255
  )
1784
2256
  } else {
@@ -1786,9 +2258,15 @@
1786
2258
  filtered,
1787
2259
  (item) => {
1788
2260
  const details = el('details', 'leaf')
1789
- const title = `${item.eventKind === 'route' ? `Route: ${item.routeKey}` : `Source: ${item.displayName || item.sourceObjectId}`} (${item.event.type})`
1790
- details.appendChild(el('summary', '', title))
1791
- details.appendChild(renderEventCard(item.event, commitBySha))
2261
+ const summary = el('summary')
2262
+ summary.appendChild(renderEventTitle(item))
2263
+ details.appendChild(summary)
2264
+ details.appendChild(
2265
+ renderEventCard(item.event, commitBySha, {
2266
+ showTitle: false,
2267
+ item,
2268
+ }),
2269
+ )
1792
2270
  return details
1793
2271
  },
1794
2272
  'No matches.',
@@ -1799,15 +2277,17 @@
1799
2277
  }
1800
2278
 
1801
2279
  function selectedFieldIds() {
1802
- const selected = state.selectedFieldIdsByTab[state.activeTab] || new Set()
1803
- return currentFieldDefs().filter((field) => selected.has(field.id)).map(
1804
- (field) => field.id,
1805
- )
2280
+ const selected =
2281
+ state.selectedFieldIdsByTab[state.activeTab] || new Set()
2282
+ return currentFieldDefs()
2283
+ .filter((field) => selected.has(field.id))
2284
+ .map((field) => field.id)
1806
2285
  }
1807
2286
 
1808
2287
  function renderFieldChips() {
1809
2288
  const fieldDefs = currentFieldDefs()
1810
- const selected = state.selectedFieldIdsByTab[state.activeTab] || new Set()
2289
+ const selected =
2290
+ state.selectedFieldIdsByTab[state.activeTab] || new Set()
1811
2291
  fieldChipGroups.innerHTML = ''
1812
2292
  const groups =
1813
2293
  state.activeTab === VIEW_IDS.leaves
@@ -1815,7 +2295,10 @@
1815
2295
  : [
1816
2296
  {
1817
2297
  id: 'tabFields',
1818
- label: state.activeTab === VIEW_IDS.changelog ? 'Changelog fields' : 'Source fields',
2298
+ label:
2299
+ state.activeTab === VIEW_IDS.changelog
2300
+ ? 'Changelog fields'
2301
+ : 'Source fields',
1819
2302
  fieldIds: fieldDefs.map((field) => field.id),
1820
2303
  },
1821
2304
  ]
@@ -1854,7 +2337,9 @@
1854
2337
  function setFieldSelection(allowedIds, options = {}) {
1855
2338
  const { rerender = true } = options
1856
2339
  state.selectedFieldIdsByTab[state.activeTab] = new Set(
1857
- currentFieldDefs().map((field) => field.id).filter((id) => allowedIds.has(id)),
2340
+ currentFieldDefs()
2341
+ .map((field) => field.id)
2342
+ .filter((id) => allowedIds.has(id)),
1858
2343
  )
1859
2344
  renderFieldChips()
1860
2345
  if (rerender) renderResults()
@@ -1866,7 +2351,10 @@
1866
2351
  setPressed(regexSearchToggle, false)
1867
2352
  typeMatchModeInput.value = 'contains'
1868
2353
  setPressed(schemaRowsMatchOnlyToggle, false)
1869
- setFieldSelection(new Set(currentFieldDefs().map((field) => field.id)), { rerender: false })
2354
+ setFieldSelection(
2355
+ new Set(currentFieldDefs().map((field) => field.id)),
2356
+ { rerender: false },
2357
+ )
1870
2358
  renderResults()
1871
2359
  }
1872
2360
 
@@ -1931,12 +2419,16 @@
1931
2419
  }
1932
2420
 
1933
2421
  if (chipModels.length === 0) {
1934
- activeFilterChips.appendChild(el('span', 'empty', 'No active filters'))
2422
+ activeFilterChips.appendChild(
2423
+ el('span', 'empty', 'No active filters'),
2424
+ )
1935
2425
  return
1936
2426
  }
1937
2427
 
1938
2428
  chipModels.forEach((chipModel) => {
1939
- const chip = document.createElement(chipModel.onClick ? 'button' : 'span')
2429
+ const chip = document.createElement(
2430
+ chipModel.onClick ? 'button' : 'span',
2431
+ )
1940
2432
  chip.className = chipModel.onClick ? 'chip chip-btn' : 'chip'
1941
2433
  chip.textContent = chipModel.text
1942
2434
  if (chipModel.onClick) {
@@ -1953,10 +2445,14 @@
1953
2445
  search: searchInput.value,
1954
2446
  caseSensitive: isPressed(caseSensitiveToggle),
1955
2447
  regex: isPressed(regexSearchToggle),
1956
- typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
2448
+ typeMatchMode:
2449
+ typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
1957
2450
  schemaRowsMatchOnly: isPressed(schemaRowsMatchOnlyToggle),
1958
2451
  selectedFieldsByTab: Object.fromEntries(
1959
- Object.entries(state.selectedFieldIdsByTab).map(([key, value]) => [key, Array.from(value)]),
2452
+ Object.entries(state.selectedFieldIdsByTab).map(([key, value]) => [
2453
+ key,
2454
+ Array.from(value),
2455
+ ]),
1960
2456
  ),
1961
2457
  }
1962
2458
  }
@@ -1965,7 +2461,9 @@
1965
2461
  if (isHydratingFromUrl) return
1966
2462
 
1967
2463
  const data = collectFilterState()
1968
- const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
2464
+ const params = new URLSearchParams(
2465
+ window.location.hash.replace(/^#/, ''),
2466
+ )
1969
2467
  const serialized = encodeURIComponent(JSON.stringify(data))
1970
2468
  params.set(URL_PARAM_KEY, serialized)
1971
2469
  const nextHash = params.toString()
@@ -1975,7 +2473,9 @@
1975
2473
  }
1976
2474
 
1977
2475
  function hydrateFilterStateFromUrl() {
1978
- const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
2476
+ const params = new URLSearchParams(
2477
+ window.location.hash.replace(/^#/, ''),
2478
+ )
1979
2479
  const raw = params.get(URL_PARAM_KEY)
1980
2480
  if (!raw) return
1981
2481
 
@@ -1991,20 +2491,32 @@
1991
2491
  if (parsed.activeTab === 'changelogRoute') {
1992
2492
  state.activeTab = VIEW_IDS.changelog
1993
2493
  }
1994
- if (typeof parsed.search === 'string') searchInput.value = parsed.search
2494
+ if (typeof parsed.search === 'string')
2495
+ searchInput.value = parsed.search
1995
2496
  setPressed(caseSensitiveToggle, Boolean(parsed.caseSensitive))
1996
2497
  setPressed(regexSearchToggle, Boolean(parsed.regex))
1997
- if (parsed.typeMatchMode === 'exact' || parsed.typeMatchMode === 'contains') {
2498
+ if (
2499
+ parsed.typeMatchMode === 'exact' ||
2500
+ parsed.typeMatchMode === 'contains'
2501
+ ) {
1998
2502
  typeMatchModeInput.value = parsed.typeMatchMode
1999
2503
  }
2000
- setPressed(schemaRowsMatchOnlyToggle, Boolean(parsed.schemaRowsMatchOnly))
2504
+ setPressed(
2505
+ schemaRowsMatchOnlyToggle,
2506
+ Boolean(parsed.schemaRowsMatchOnly),
2507
+ )
2001
2508
 
2002
- if (parsed.selectedFieldsByTab && typeof parsed.selectedFieldsByTab === 'object') {
2003
- Object.entries(parsed.selectedFieldsByTab).forEach(([tabId, ids]) => {
2004
- if (!Array.isArray(ids)) return
2005
- if (!state.selectedFieldIdsByTab[tabId]) return
2006
- state.selectedFieldIdsByTab[tabId] = new Set(ids)
2007
- })
2509
+ if (
2510
+ parsed.selectedFieldsByTab &&
2511
+ typeof parsed.selectedFieldsByTab === 'object'
2512
+ ) {
2513
+ Object.entries(parsed.selectedFieldsByTab).forEach(
2514
+ ([tabId, ids]) => {
2515
+ if (!Array.isArray(ids)) return
2516
+ if (!state.selectedFieldIdsByTab[tabId]) return
2517
+ state.selectedFieldIdsByTab[tabId] = new Set(ids)
2518
+ },
2519
+ )
2008
2520
  }
2009
2521
  } catch (error) {
2010
2522
  // Ignore malformed hash state.
@@ -2070,15 +2582,13 @@
2070
2582
  }
2071
2583
 
2072
2584
  if (state.leavesPayload && state.changelogPayload) {
2073
- statusEl.textContent =
2074
- `Loaded unified bundle with ${state.leaves.length} routes and ${
2075
- state.changelogPayload.commits?.length || 0
2076
- } commits.`
2585
+ statusEl.textContent = `Loaded unified bundle with ${state.leaves.length} routes and ${
2586
+ state.changelogPayload.commits?.length || 0
2587
+ } commits.`
2077
2588
  } else if (state.leavesPayload) {
2078
2589
  statusEl.textContent = `Loaded payload with ${state.leaves.length} routes.`
2079
2590
  } else if (state.changelogPayload) {
2080
- statusEl.textContent =
2081
- `Loaded changelog payload with ${state.changelogPayload.commits?.length || 0} commits.`
2591
+ statusEl.textContent = `Loaded changelog payload with ${state.changelogPayload.commits?.length || 0} commits.`
2082
2592
  } else {
2083
2593
  statusEl.textContent = 'No payload loaded.'
2084
2594
  }
@@ -2104,7 +2614,8 @@
2104
2614
  try {
2105
2615
  await handleFile(file)
2106
2616
  } catch (error) {
2107
- statusEl.textContent = error instanceof Error ? error.message : String(error)
2617
+ statusEl.textContent =
2618
+ error instanceof Error ? error.message : String(error)
2108
2619
  resultsEl.innerHTML = ''
2109
2620
  }
2110
2621
  })
@@ -2124,16 +2635,28 @@
2124
2635
  renderResults()
2125
2636
  })
2126
2637
 
2127
- tabLeavesBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.leaves))
2128
- tabChangelogBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.changelog))
2638
+ tabLeavesBtn.addEventListener('click', () =>
2639
+ setActiveTab(VIEW_IDS.leaves),
2640
+ )
2641
+ tabChangelogBtn.addEventListener('click', () =>
2642
+ setActiveTab(VIEW_IDS.changelog),
2643
+ )
2129
2644
 
2130
2645
  selectAllFieldsBtn.addEventListener('click', () =>
2131
2646
  setFieldSelection(new Set(currentFieldDefs().map((field) => field.id))),
2132
2647
  )
2133
- clearAllFieldsBtn.addEventListener('click', () => setFieldSelection(new Set()))
2134
- coreFieldsBtn.addEventListener('click', () => setFieldSelection(CORE_FIELD_IDS))
2135
- schemasOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(SCHEMA_SCOPE_FIELD_IDS))
2136
- sourceOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(SOURCE_FIELD_IDS))
2648
+ clearAllFieldsBtn.addEventListener('click', () =>
2649
+ setFieldSelection(new Set()),
2650
+ )
2651
+ coreFieldsBtn.addEventListener('click', () =>
2652
+ setFieldSelection(CORE_FIELD_IDS),
2653
+ )
2654
+ schemasOnlyFieldsBtn.addEventListener('click', () =>
2655
+ setFieldSelection(SCHEMA_SCOPE_FIELD_IDS),
2656
+ )
2657
+ sourceOnlyFieldsBtn.addEventListener('click', () =>
2658
+ setFieldSelection(SOURCE_FIELD_IDS),
2659
+ )
2137
2660
  resetFiltersBtn.addEventListener('click', resetFiltersToDefault)
2138
2661
 
2139
2662
  renderFieldChips()