@emeryld/rrroutes-export 1.0.18 → 1.0.19

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.19",
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,31 @@
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
+
377
406
  .schema-block {
378
407
  border-top: 1px solid var(--border);
379
408
  padding: 8px 0 2px;
@@ -497,15 +526,29 @@
497
526
  </label>
498
527
  <label class="control-block">
499
528
  <span class="control-label">Search text</span>
500
- <input id="searchInput" type="text" placeholder="Type to search..." />
529
+ <input
530
+ id="searchInput"
531
+ type="text"
532
+ placeholder="Type to search..."
533
+ />
501
534
  </label>
502
535
  <div class="control-block">
503
536
  <span class="control-label">Quick filters</span>
504
537
  <div class="quick-toggles">
505
- <button id="caseSensitive" class="pill-toggle" type="button" aria-pressed="false">
538
+ <button
539
+ id="caseSensitive"
540
+ class="pill-toggle"
541
+ type="button"
542
+ aria-pressed="false"
543
+ >
506
544
  Case sensitive
507
545
  </button>
508
- <button id="regexSearch" class="pill-toggle" type="button" aria-pressed="false">
546
+ <button
547
+ id="regexSearch"
548
+ class="pill-toggle"
549
+ type="button"
550
+ aria-pressed="false"
551
+ >
509
552
  Regex
510
553
  </button>
511
554
  </div>
@@ -514,11 +557,21 @@
514
557
 
515
558
  <div class="scope-row">
516
559
  <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>
560
+ <button id="selectAllFields" class="scope-action" type="button">
561
+ All
562
+ </button>
563
+ <button id="clearAllFields" class="scope-action" type="button">
564
+ None
565
+ </button>
566
+ <button id="coreFields" class="scope-action" type="button">
567
+ Core
568
+ </button>
569
+ <button id="schemasOnlyFields" class="scope-action" type="button">
570
+ Schema
571
+ </button>
572
+ <button id="sourceOnlyFields" class="scope-action" type="button">
573
+ Source
574
+ </button>
522
575
  </div>
523
576
  <div id="fieldChipGroups" class="scope-groups"></div>
524
577
  </div>
@@ -549,10 +602,20 @@
549
602
  <div class="scope-group">
550
603
  <div class="scope-group-title">Views</div>
551
604
  <div class="scope-group-chips">
552
- <button id="tabLeaves" class="field-chip" type="button" aria-pressed="true">
605
+ <button
606
+ id="tabLeaves"
607
+ class="field-chip"
608
+ type="button"
609
+ aria-pressed="true"
610
+ >
553
611
  Leaves
554
612
  </button>
555
- <button id="tabChangelog" class="field-chip" type="button" aria-pressed="false">
613
+ <button
614
+ id="tabChangelog"
615
+ class="field-chip"
616
+ type="button"
617
+ aria-pressed="false"
618
+ >
556
619
  Changelog
557
620
  </button>
558
621
  </div>
@@ -564,7 +627,10 @@
564
627
  <div id="activeFilterChips" class="chips filters"></div>
565
628
  </div>
566
629
 
567
- <div id="status" class="meta">Load a leaves export, changelog export, or unified bundle JSON to begin.</div>
630
+ <div id="status" class="meta">
631
+ Load a leaves export, changelog export, or unified bundle JSON to
632
+ begin.
633
+ </div>
568
634
  </div>
569
635
 
570
636
  <div id="results"></div>
@@ -582,10 +648,22 @@
582
648
  label: 'description',
583
649
  get: (leaf) => [leaf.cfg?.description],
584
650
  },
585
- { id: 'docsGroup', label: 'docsGroup', get: (leaf) => [leaf.cfg?.docsGroup] },
651
+ {
652
+ id: 'docsGroup',
653
+ label: 'docsGroup',
654
+ get: (leaf) => [leaf.cfg?.docsGroup],
655
+ },
586
656
  { 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] },
657
+ {
658
+ id: 'stability',
659
+ label: 'stability',
660
+ get: (leaf) => [leaf.cfg?.stability],
661
+ },
662
+ {
663
+ id: 'docsMeta',
664
+ label: 'docsMeta',
665
+ get: (leaf) => [leaf.cfg?.docsMeta],
666
+ },
589
667
  {
590
668
  id: 'sourceDefinition',
591
669
  label: 'source definition',
@@ -614,7 +692,8 @@
614
692
  {
615
693
  id: 'types',
616
694
  label: 'types',
617
- get: (leaf, schemaFlatByLeaf) => schemaTypeTokens(schemaFlatByLeaf?.[leaf.key]),
695
+ get: (leaf, schemaFlatByLeaf) =>
696
+ schemaTypeTokens(schemaFlatByLeaf?.[leaf.key]),
618
697
  },
619
698
  {
620
699
  id: 'params',
@@ -704,23 +783,23 @@
704
783
  {
705
784
  id: 'core',
706
785
  label: 'Core fields',
707
- fieldIds: SEARCH_FIELDS.filter((field) => CORE_FIELD_IDS.has(field.id)).map(
708
- (field) => field.id,
709
- ),
786
+ fieldIds: SEARCH_FIELDS.filter((field) =>
787
+ CORE_FIELD_IDS.has(field.id),
788
+ ).map((field) => field.id),
710
789
  },
711
790
  {
712
791
  id: 'schema',
713
792
  label: 'Schema fields',
714
- fieldIds: SEARCH_FIELDS.filter((field) => SCHEMA_SCOPE_FIELD_IDS.has(field.id)).map(
715
- (field) => field.id,
716
- ),
793
+ fieldIds: SEARCH_FIELDS.filter((field) =>
794
+ SCHEMA_SCOPE_FIELD_IDS.has(field.id),
795
+ ).map((field) => field.id),
717
796
  },
718
797
  {
719
798
  id: 'source',
720
799
  label: 'Source fields',
721
- fieldIds: SEARCH_FIELDS.filter((field) => SOURCE_FIELD_IDS.has(field.id)).map(
722
- (field) => field.id,
723
- ),
800
+ fieldIds: SEARCH_FIELDS.filter((field) =>
801
+ SOURCE_FIELD_IDS.has(field.id),
802
+ ).map((field) => field.id),
724
803
  },
725
804
  ]
726
805
 
@@ -757,7 +836,9 @@
757
836
  },
758
837
  [VIEW_IDS.changelog]: {
759
838
  fieldDefs: CHANGELOG_TIMELINE_FIELDS,
760
- defaultSelected: new Set(CHANGELOG_TIMELINE_FIELDS.map((field) => field.id)),
839
+ defaultSelected: new Set(
840
+ CHANGELOG_TIMELINE_FIELDS.map((field) => field.id),
841
+ ),
761
842
  },
762
843
  }
763
844
 
@@ -770,8 +851,12 @@
770
851
  viewerMode: VIEWER_MODE.leaves,
771
852
  availableTabs: new Set([VIEW_IDS.leaves]),
772
853
  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),
854
+ [VIEW_IDS.leaves]: new Set(
855
+ TAB_CONFIG[VIEW_IDS.leaves].defaultSelected,
856
+ ),
857
+ [VIEW_IDS.changelog]: new Set(
858
+ TAB_CONFIG[VIEW_IDS.changelog].defaultSelected,
859
+ ),
775
860
  },
776
861
  }
777
862
 
@@ -780,7 +865,9 @@
780
865
  const caseSensitiveToggle = document.getElementById('caseSensitive')
781
866
  const regexSearchToggle = document.getElementById('regexSearch')
782
867
  const typeMatchModeInput = document.getElementById('typeMatchMode')
783
- const schemaRowsMatchOnlyToggle = document.getElementById('schemaRowsMatchOnly')
868
+ const schemaRowsMatchOnlyToggle = document.getElementById(
869
+ 'schemaRowsMatchOnly',
870
+ )
784
871
  const fieldChipGroups = document.getElementById('fieldChipGroups')
785
872
  const activeFilterChips = document.getElementById('activeFilterChips')
786
873
  const selectAllFieldsBtn = document.getElementById('selectAllFields')
@@ -807,7 +894,9 @@
807
894
  }
808
895
 
809
896
  function applyViewerModeMeta() {
810
- const meta = VIEWER_MODE_META[state.viewerMode] || VIEWER_MODE_META[VIEWER_MODE.leaves]
897
+ const meta =
898
+ VIEWER_MODE_META[state.viewerMode] ||
899
+ VIEWER_MODE_META[VIEWER_MODE.leaves]
811
900
  document.title = meta.title
812
901
  if (viewerHeadingEl) viewerHeadingEl.textContent = meta.heading
813
902
  }
@@ -919,7 +1008,8 @@
919
1008
  function toTokens(value) {
920
1009
  if (value === null || value === undefined) return []
921
1010
  if (typeof value === 'string') return [value]
922
- if (typeof value === 'number' || typeof value === 'boolean') return [String(value)]
1011
+ if (typeof value === 'number' || typeof value === 'boolean')
1012
+ return [String(value)]
923
1013
  if (Array.isArray(value)) return value.flatMap((item) => toTokens(item))
924
1014
  if (typeof value === 'object') return [JSON.stringify(value)]
925
1015
  return []
@@ -958,7 +1048,8 @@
958
1048
  function schemaSectionToSearchTokens(flatSchema, sectionName) {
959
1049
  if (!flatSchema || typeof flatSchema !== 'object') return []
960
1050
  const sectionEntries = Object.entries(flatSchema).filter(
961
- ([path]) => path === sectionName || path.startsWith(`${sectionName}.`),
1051
+ ([path]) =>
1052
+ path === sectionName || path.startsWith(`${sectionName}.`),
962
1053
  )
963
1054
 
964
1055
  if (sectionEntries.length === 0) return []
@@ -1006,12 +1097,20 @@
1006
1097
  return Array.from(tokens)
1007
1098
  }
1008
1099
 
1009
- function schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds) {
1100
+ function schemaEntryMatchesActiveFilter(
1101
+ sectionName,
1102
+ fullPath,
1103
+ info,
1104
+ engine,
1105
+ selectedIds,
1106
+ ) {
1010
1107
  if (!engine.active) return true
1011
1108
 
1012
1109
  let matched = false
1013
1110
  if (selectedIds.includes(sectionName)) {
1014
- matched = schemaEntryTokens(fullPath, info).some((token) => engine.test(token))
1111
+ matched = schemaEntryTokens(fullPath, info).some((token) =>
1112
+ engine.test(token),
1113
+ )
1015
1114
  }
1016
1115
 
1017
1116
  if (!matched && selectedIds.includes('types')) {
@@ -1025,7 +1124,9 @@
1025
1124
  }
1026
1125
 
1027
1126
  if (typeMatchModeInput.value === 'exact' && !engine.regex) {
1028
- const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
1127
+ const needle = engine.caseSensitive
1128
+ ? engine.query
1129
+ : engine.query.toLowerCase()
1029
1130
  const normalized = engine.caseSensitive
1030
1131
  ? typeTokens
1031
1132
  : typeTokens.map((token) => token.toLowerCase())
@@ -1047,8 +1148,14 @@
1047
1148
  return SEARCH_FIELDS.some((field) => {
1048
1149
  if (!selectedIds.includes(field.id)) return false
1049
1150
  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()
1151
+ if (
1152
+ field.id === 'types' &&
1153
+ typeMatchModeInput.value === 'exact' &&
1154
+ !engine.regex
1155
+ ) {
1156
+ const needle = engine.caseSensitive
1157
+ ? engine.query
1158
+ : engine.query.toLowerCase()
1052
1159
  const normalized = engine.caseSensitive
1053
1160
  ? tokens
1054
1161
  : tokens.map((token) => String(token).toLowerCase())
@@ -1086,14 +1193,25 @@
1086
1193
  }
1087
1194
 
1088
1195
  function setHighlighted(node, text, engine, selectedIds, fieldIds) {
1089
- node.innerHTML = highlightWithFieldScope(text, engine, selectedIds, fieldIds)
1196
+ node.innerHTML = highlightWithFieldScope(
1197
+ text,
1198
+ engine,
1199
+ selectedIds,
1200
+ fieldIds,
1201
+ )
1090
1202
  }
1091
1203
 
1092
1204
  function kv(key, value, engine, selectedIds, fieldIds) {
1093
1205
  const box = el('div', 'kv')
1094
1206
  box.appendChild(el('div', 'k', key))
1095
1207
  const valueNode = el('div', 'v mono')
1096
- setHighlighted(valueNode, value === undefined ? '—' : String(value), engine, selectedIds, fieldIds)
1208
+ setHighlighted(
1209
+ valueNode,
1210
+ value === undefined ? '—' : String(value),
1211
+ engine,
1212
+ selectedIds,
1213
+ fieldIds,
1214
+ )
1097
1215
  box.appendChild(valueNode)
1098
1216
  return box
1099
1217
  }
@@ -1158,7 +1276,12 @@
1158
1276
  link.href = href
1159
1277
  link.target = '_blank'
1160
1278
  link.rel = 'noopener noreferrer'
1161
- link.innerHTML = highlightWithFieldScope(label, engine, selectedIds, fieldIds)
1279
+ link.innerHTML = highlightWithFieldScope(
1280
+ label,
1281
+ engine,
1282
+ selectedIds,
1283
+ fieldIds,
1284
+ )
1162
1285
  labelNode.appendChild(link)
1163
1286
  box.appendChild(labelNode)
1164
1287
  return box
@@ -1194,7 +1317,11 @@
1194
1317
 
1195
1318
  function getSchemaSource(source, sectionName) {
1196
1319
  const schemaKey = SCHEMA_SOURCE_KEYS_BY_SECTION[sectionName]
1197
- if (!schemaKey || !source?.schemas || typeof source.schemas !== 'object') {
1320
+ if (
1321
+ !schemaKey ||
1322
+ !source?.schemas ||
1323
+ typeof source.schemas !== 'object'
1324
+ ) {
1198
1325
  return {
1199
1326
  schemaKey,
1200
1327
  sourceValue: undefined,
@@ -1208,8 +1335,11 @@
1208
1335
  }
1209
1336
 
1210
1337
  function resolveSchemaSourceLabel(schemaKey, sourceValue) {
1211
- if (!sourceValue || typeof sourceValue !== 'object') return schemaKey || 'schema'
1212
- return sourceValue.sourceName || sourceValue.tag || schemaKey || 'schema'
1338
+ if (!sourceValue || typeof sourceValue !== 'object')
1339
+ return schemaKey || 'schema'
1340
+ return (
1341
+ sourceValue.sourceName || sourceValue.tag || schemaKey || 'schema'
1342
+ )
1213
1343
  }
1214
1344
 
1215
1345
  function createTreeNode(name = '') {
@@ -1226,7 +1356,10 @@
1226
1356
  Object.entries(entries)
1227
1357
  .sort(([a], [b]) => a.localeCompare(b))
1228
1358
  .forEach(([fullPath, info]) => {
1229
- const trimmed = fullPath === sectionName ? '' : fullPath.slice(sectionName.length + 1)
1359
+ const trimmed =
1360
+ fullPath === sectionName
1361
+ ? ''
1362
+ : fullPath.slice(sectionName.length + 1)
1230
1363
  const segments = trimmed ? trimmed.split('.') : []
1231
1364
 
1232
1365
  let current = root
@@ -1268,10 +1401,13 @@
1268
1401
 
1269
1402
  const appendTypeCell = (row, info) => {
1270
1403
  const type = el('span', 'tree-pill')
1271
- setHighlighted(type, info?.type || info?.kind || '—', engine, selectedIds, [
1272
- sectionName,
1273
- 'types',
1274
- ])
1404
+ setHighlighted(
1405
+ type,
1406
+ info?.type || info?.kind || '',
1407
+ engine,
1408
+ selectedIds,
1409
+ [sectionName, 'types'],
1410
+ )
1275
1411
  row.appendChild(type)
1276
1412
  }
1277
1413
 
@@ -1292,7 +1428,13 @@
1292
1428
  .sort((a, b) => a.localeCompare(b))
1293
1429
  .forEach((key) =>
1294
1430
  container.appendChild(
1295
- renderTreeNode(node.children[key], engine, selectedIds, sectionName, false),
1431
+ renderTreeNode(
1432
+ node.children[key],
1433
+ engine,
1434
+ selectedIds,
1435
+ sectionName,
1436
+ false,
1437
+ ),
1296
1438
  ),
1297
1439
  )
1298
1440
  details.appendChild(container)
@@ -1313,7 +1455,8 @@
1313
1455
 
1314
1456
  const grouped = splitFlatSchemaBySection(flatSchema)
1315
1457
  let hasAnySchemaEntries = false
1316
- const limitToMatchedRows = isPressed(schemaRowsMatchOnlyToggle) && engine.active
1458
+ const limitToMatchedRows =
1459
+ isPressed(schemaRowsMatchOnlyToggle) && engine.active
1317
1460
 
1318
1461
  SCHEMA_SECTIONS.forEach((sectionName) => {
1319
1462
  const rawEntries = grouped[sectionName]
@@ -1322,7 +1465,13 @@
1322
1465
  const entries = limitToMatchedRows
1323
1466
  ? Object.fromEntries(
1324
1467
  Object.entries(rawEntries).filter(([fullPath, info]) =>
1325
- schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds),
1468
+ schemaEntryMatchesActiveFilter(
1469
+ sectionName,
1470
+ fullPath,
1471
+ info,
1472
+ engine,
1473
+ selectedIds,
1474
+ ),
1326
1475
  ),
1327
1476
  )
1328
1477
  : rawEntries
@@ -1351,7 +1500,9 @@
1351
1500
  }
1352
1501
 
1353
1502
  const tree = buildSchemaTree(entries, sectionName)
1354
- block.appendChild(renderTreeNode(tree, engine, selectedIds, sectionName, true))
1503
+ block.appendChild(
1504
+ renderTreeNode(tree, engine, selectedIds, sectionName, true),
1505
+ )
1355
1506
 
1356
1507
  section.appendChild(block)
1357
1508
  })
@@ -1378,7 +1529,9 @@
1378
1529
  const overview = el('div', 'section')
1379
1530
  overview.appendChild(el('h3', '', 'Overview'))
1380
1531
  const topGrid = el('div', 'grid-3')
1381
- topGrid.appendChild(kv('group', cfg.docsGroup, engine, selectedIds, 'docsGroup'))
1532
+ topGrid.appendChild(
1533
+ kv('group', cfg.docsGroup, engine, selectedIds, 'docsGroup'),
1534
+ )
1382
1535
  topGrid.appendChild(
1383
1536
  kv(
1384
1537
  'tags',
@@ -1388,10 +1541,22 @@
1388
1541
  'tags',
1389
1542
  ),
1390
1543
  )
1391
- topGrid.appendChild(kv('stability', cfg.stability, engine, selectedIds, 'stability'))
1544
+ topGrid.appendChild(
1545
+ kv('stability', cfg.stability, engine, selectedIds, 'stability'),
1546
+ )
1392
1547
  overview.appendChild(topGrid)
1393
- overview.appendChild(kv('summary', cfg.summary, engine, selectedIds, 'summary'))
1394
- overview.appendChild(kv('description', cfg.description, engine, selectedIds, 'description'))
1548
+ overview.appendChild(
1549
+ kv('summary', cfg.summary, engine, selectedIds, 'summary'),
1550
+ )
1551
+ overview.appendChild(
1552
+ kv(
1553
+ 'description',
1554
+ cfg.description,
1555
+ engine,
1556
+ selectedIds,
1557
+ 'description',
1558
+ ),
1559
+ )
1395
1560
 
1396
1561
  const iconRow = el('div', 'icon-row')
1397
1562
  if (cfg.feed) {
@@ -1417,13 +1582,6 @@
1417
1582
  }
1418
1583
  content.appendChild(overview)
1419
1584
 
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
1585
  const files = el('div', 'section')
1428
1586
  if (Array.isArray(cfg.bodyFiles) && cfg.bodyFiles.length > 0) {
1429
1587
  files.appendChild(el('h3', '', 'Body Files'))
@@ -1441,11 +1599,22 @@
1441
1599
  const source = sourceByLeaf[leaf.key]
1442
1600
  if (source?.definition) {
1443
1601
  overview.appendChild(
1444
- createSourceRow('definition', source.definition, engine, selectedIds, 'sourceDefinition'),
1602
+ createSourceRow(
1603
+ 'definition',
1604
+ source.definition,
1605
+ engine,
1606
+ selectedIds,
1607
+ 'sourceDefinition',
1608
+ ),
1445
1609
  )
1446
1610
  }
1447
1611
 
1448
- const separatedSchemas = renderSeparatedSchemas(flatSchema, engine, selectedIds, source)
1612
+ const separatedSchemas = renderSeparatedSchemas(
1613
+ flatSchema,
1614
+ engine,
1615
+ selectedIds,
1616
+ source,
1617
+ )
1449
1618
  if (separatedSchemas) {
1450
1619
  content.appendChild(separatedSchemas)
1451
1620
  }
@@ -1453,12 +1622,22 @@
1453
1622
  const routeHistory = state.changelogPayload?.byRoute?.[leaf.key]
1454
1623
  if (Array.isArray(routeHistory) && routeHistory.length > 0) {
1455
1624
  const commitBySha = new Map(
1456
- (state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
1625
+ (state.changelogPayload?.commits || []).map((commit) => [
1626
+ commit.sha,
1627
+ commit,
1628
+ ]),
1457
1629
  )
1458
1630
  const commitOrder = new Map(
1459
- (state.changelogPayload?.commits || []).map((commit, index) => [commit.sha, index]),
1631
+ (state.changelogPayload?.commits || []).map((commit, index) => [
1632
+ commit.sha,
1633
+ index,
1634
+ ]),
1635
+ )
1636
+ const routeHistoryNewestFirst = sortEventsNewestFirst(
1637
+ routeHistory,
1638
+ commitBySha,
1639
+ commitOrder,
1460
1640
  )
1461
- const routeHistoryNewestFirst = sortEventsNewestFirst(routeHistory, commitBySha, commitOrder)
1462
1641
  const routeChangelogSection = el('div', 'section')
1463
1642
  const routeChangelogDetails = el('details')
1464
1643
  const routeChangelogSummary = el(
@@ -1525,18 +1704,134 @@
1525
1704
  return parts.join(', ')
1526
1705
  }
1527
1706
 
1707
+ function schemaFlags(nullable, optional) {
1708
+ return `${nullable ? '-' : ''}${optional ? '?' : ''}`
1709
+ }
1710
+
1711
+ function formatEntryTypeLabel(entry) {
1712
+ if (!entry || typeof entry !== 'object') return null
1713
+ const type = typeof entry.type === 'string' ? entry.type : 'unknown'
1714
+ return `${type}${schemaFlags(Boolean(entry.nullable), Boolean(entry.optional))}`
1715
+ }
1716
+
1717
+ function formatPathLabel(path, entry) {
1718
+ const basePath = typeof path === 'string' ? path : ''
1719
+ return `${basePath}${schemaFlags(Boolean(entry?.nullable), Boolean(entry?.optional))}`
1720
+ }
1721
+
1722
+ function parseSignatureTypeLabel(signature) {
1723
+ if (typeof signature !== 'string') return null
1724
+ const typeMatch = signature.match(/(?:^|,\s*)type=([^,]+)/)
1725
+ const nullableMatch = signature.match(/(?:^|,\s*)nullable=(true|false)/)
1726
+ const optionalMatch = signature.match(/(?:^|,\s*)optional=(true|false)/)
1727
+ if (!typeMatch) return null
1728
+ const type = typeMatch[1]
1729
+ const nullable = nullableMatch?.[1] === 'true'
1730
+ const optional = optionalMatch?.[1] === 'true'
1731
+ return `${type}${schemaFlags(nullable, optional)}`
1732
+ }
1733
+
1734
+ function formatSourceTypeLabel(signatures) {
1735
+ if (!Array.isArray(signatures) || signatures.length === 0) return null
1736
+ const labels = signatures
1737
+ .map((item) => parseSignatureTypeLabel(item))
1738
+ .filter(Boolean)
1739
+ if (labels.length === 0) return null
1740
+ return labels.join(' | ')
1741
+ }
1742
+
1743
+ function schemaDiffOperation(op) {
1744
+ if (op === 'added') {
1745
+ return { symbol: '+', word: 'added', className: 'added' }
1746
+ }
1747
+ if (op === 'removed') {
1748
+ return { symbol: '-', word: 'removed', className: 'removed' }
1749
+ }
1750
+ return { symbol: '*', word: 'changed', className: 'changed' }
1751
+ }
1752
+
1753
+ function buildSchemaDiffLine(op, beforeType, pathLabel, afterType) {
1754
+ if (!pathLabel) return null
1755
+ const line = el('div', 'meta mono changelog-diff-line')
1756
+ const operation = schemaDiffOperation(op)
1757
+ line.appendChild(
1758
+ el(
1759
+ 'span',
1760
+ `changelog-op ${operation.className}`,
1761
+ `${operation.symbol} ${operation.word}`,
1762
+ ),
1763
+ )
1764
+ if (beforeType) {
1765
+ line.appendChild(el('span', 'changelog-schema-type', beforeType))
1766
+ }
1767
+ line.appendChild(el('span', '', pathLabel))
1768
+ if (operation.className === 'changed' && afterType) {
1769
+ line.appendChild(el('span', '', '->'))
1770
+ line.appendChild(el('span', 'changelog-schema-type', afterType))
1771
+ }
1772
+ return line
1773
+ }
1774
+
1775
+ function buildLegacySchemaDiffLine(text) {
1776
+ return el('div', 'meta mono', text)
1777
+ }
1778
+
1528
1779
  function formatSchemaDiffLine(delta) {
1529
1780
  if (!delta || typeof delta !== 'object') return null
1530
1781
  if ('section' in delta) {
1531
1782
  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 || [])}`
1783
+ if (delta.op === 'added') {
1784
+ const afterType = formatSourceTypeLabel(delta.after)
1785
+ if (afterType) return buildSchemaDiffLine(delta.op, afterType, location)
1786
+ return buildLegacySchemaDiffLine(
1787
+ `added ${location}: ${JSON.stringify(delta.after || [])}`,
1788
+ )
1789
+ }
1790
+ if (delta.op === 'removed') {
1791
+ const beforeType = formatSourceTypeLabel(delta.before)
1792
+ if (beforeType)
1793
+ return buildSchemaDiffLine(delta.op, beforeType, location)
1794
+ return buildLegacySchemaDiffLine(
1795
+ `removed ${location}: ${JSON.stringify(delta.before || [])}`,
1796
+ )
1797
+ }
1798
+ const beforeType = formatSourceTypeLabel(delta.before)
1799
+ const afterType = formatSourceTypeLabel(delta.after)
1800
+ if (beforeType || afterType) {
1801
+ return buildSchemaDiffLine(
1802
+ delta.op,
1803
+ beforeType || 'unknown',
1804
+ location,
1805
+ afterType || 'unknown',
1806
+ )
1807
+ }
1808
+ return buildLegacySchemaDiffLine(
1809
+ `changed ${location}: ${JSON.stringify(delta.before || [])} -> ${JSON.stringify(delta.after || [])}`,
1810
+ )
1535
1811
  }
1536
1812
  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)}`
1813
+ if (delta.op === 'added') {
1814
+ const afterType = formatEntryTypeLabel(delta.after)
1815
+ const pathLabel = formatPathLabel(delta.path, delta.after)
1816
+ if (afterType) return buildSchemaDiffLine(delta.op, afterType, pathLabel)
1817
+ return buildLegacySchemaDiffLine(
1818
+ `added ${delta.path}: ${formatRouteSchemaEntry(delta.after)}`,
1819
+ )
1820
+ }
1821
+ if (delta.op === 'removed') {
1822
+ const beforeType = formatEntryTypeLabel(delta.before)
1823
+ const pathLabel = formatPathLabel(delta.path, delta.before)
1824
+ if (beforeType)
1825
+ return buildSchemaDiffLine(delta.op, beforeType, pathLabel)
1826
+ return buildLegacySchemaDiffLine(
1827
+ `removed ${delta.path}: ${formatRouteSchemaEntry(delta.before)}`,
1828
+ )
1829
+ }
1830
+ const beforeType = formatEntryTypeLabel(delta.before) || 'unknown'
1831
+ const afterType = formatEntryTypeLabel(delta.after) || 'unknown'
1832
+ const pathLabel =
1833
+ formatPathLabel(delta.path, delta.after || delta.before) || delta.path
1834
+ return buildSchemaDiffLine(delta.op, beforeType, pathLabel, afterType)
1540
1835
  }
1541
1836
  return null
1542
1837
  }
@@ -1561,17 +1856,21 @@
1561
1856
  card.appendChild(el('div', 'meta', commitMeta))
1562
1857
  card.appendChild(el('div', 'mono', event.type))
1563
1858
  if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
1564
- card.appendChild(el('div', 'meta', `schema changes: ${event.schemaDiff.length}`))
1859
+ card.appendChild(
1860
+ el('div', 'meta', `schema changes: ${event.schemaDiff.length}`),
1861
+ )
1565
1862
  const schemaChanges = el('div')
1566
1863
  event.schemaDiff.forEach((delta) => {
1567
- const line = formatSchemaDiffLine(delta)
1568
- if (!line) return
1569
- schemaChanges.appendChild(el('div', 'meta', line))
1864
+ const lineNode = formatSchemaDiffLine(delta)
1865
+ if (!lineNode) return
1866
+ schemaChanges.appendChild(lineNode)
1570
1867
  })
1571
1868
  card.appendChild(schemaChanges)
1572
1869
  }
1573
1870
  if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
1574
- card.appendChild(el('div', 'meta', `cfg changes: ${event.cfgDiff.length}`))
1871
+ card.appendChild(
1872
+ el('div', 'meta', `cfg changes: ${event.cfgDiff.length}`),
1873
+ )
1575
1874
  const cfgChanges = el('div')
1576
1875
  event.cfgDiff.forEach((diff) => {
1577
1876
  const line = formatCfgDiffLine(diff)
@@ -1580,8 +1879,17 @@
1580
1879
  })
1581
1880
  card.appendChild(cfgChanges)
1582
1881
  }
1583
- if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
1584
- card.appendChild(el('div', 'meta', `impacted routes: ${event.impactedRoutes.length}`))
1882
+ if (
1883
+ Array.isArray(event.impactedRoutes) &&
1884
+ event.impactedRoutes.length > 0
1885
+ ) {
1886
+ card.appendChild(
1887
+ el(
1888
+ 'div',
1889
+ 'meta',
1890
+ `impacted routes: ${event.impactedRoutes.length}`,
1891
+ ),
1892
+ )
1585
1893
  }
1586
1894
  return card
1587
1895
  }
@@ -1601,14 +1909,16 @@
1601
1909
 
1602
1910
  function renderChangelogEventLines(event, commitBySha, contextLabel) {
1603
1911
  const row = el('div', 'changelog-line-item')
1604
- row.appendChild(el('div', 'meta', buildCommitMetaLine(event, commitBySha)))
1912
+ row.appendChild(
1913
+ el('div', 'meta', buildCommitMetaLine(event, commitBySha)),
1914
+ )
1605
1915
  row.appendChild(el('div', 'mono', `${contextLabel} (${event.type})`))
1606
1916
 
1607
1917
  if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
1608
1918
  event.schemaDiff.forEach((delta) => {
1609
- const line = formatSchemaDiffLine(delta)
1610
- if (!line) return
1611
- row.appendChild(el('div', 'meta mono', line))
1919
+ const lineNode = formatSchemaDiffLine(delta)
1920
+ if (!lineNode) return
1921
+ row.appendChild(lineNode)
1612
1922
  })
1613
1923
  }
1614
1924
  if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
@@ -1618,9 +1928,16 @@
1618
1928
  row.appendChild(el('div', 'meta mono', line))
1619
1929
  })
1620
1930
  }
1621
- if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
1931
+ if (
1932
+ Array.isArray(event.impactedRoutes) &&
1933
+ event.impactedRoutes.length > 0
1934
+ ) {
1622
1935
  row.appendChild(
1623
- el('div', 'meta mono', `impacted routes: ${event.impactedRoutes.join(', ')}`),
1936
+ el(
1937
+ 'div',
1938
+ 'meta mono',
1939
+ `impacted routes: ${event.impactedRoutes.join(', ')}`,
1940
+ ),
1624
1941
  )
1625
1942
  }
1626
1943
 
@@ -1643,7 +1960,9 @@
1643
1960
  const indexB = commitOrder.get(b?.commitSha) ?? -1
1644
1961
  if (indexA !== indexB) return indexB - indexA
1645
1962
 
1646
- return String(b?.commitSha || '').localeCompare(String(a?.commitSha || ''))
1963
+ return String(b?.commitSha || '').localeCompare(
1964
+ String(a?.commitSha || ''),
1965
+ )
1647
1966
  }
1648
1967
 
1649
1968
  function sortEventsNewestFirst(events, commitBySha, commitOrder) {
@@ -1654,8 +1973,12 @@
1654
1973
 
1655
1974
  function buildChronologicalEvents(payload) {
1656
1975
  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]))
1976
+ const commitBySha = new Map(
1977
+ (payload.commits || []).map((commit) => [commit.sha, commit]),
1978
+ )
1979
+ const commitOrder = new Map(
1980
+ (payload.commits || []).map((commit, index) => [commit.sha, index]),
1981
+ )
1659
1982
  const rows = []
1660
1983
  let sequence = 0
1661
1984
 
@@ -1673,24 +1996,27 @@
1673
1996
  })
1674
1997
  })
1675
1998
 
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++,
1999
+ Object.entries(payload.bySourceObject || {}).forEach(
2000
+ ([sourceObjectId, events]) => {
2001
+ ;(events || []).forEach((event) => {
2002
+ rows.push({
2003
+ eventKind: 'source',
2004
+ sourceObjectId,
2005
+ displayName: event.displayName || sourceObjectId,
2006
+ event,
2007
+ commit: commitBySha.get(event.commitSha),
2008
+ commitSort: commitTimestamp(commitBySha.get(event.commitSha)),
2009
+ commitIndex: commitOrder.get(event.commitSha) ?? -1,
2010
+ eventIndex: sequence++,
2011
+ })
1687
2012
  })
1688
- })
1689
- })
2013
+ },
2014
+ )
1690
2015
 
1691
2016
  rows.sort((a, b) => {
1692
2017
  if (a.commitSort !== b.commitSort) return b.commitSort - a.commitSort
1693
- if (a.commitIndex !== b.commitIndex) return b.commitIndex - a.commitIndex
2018
+ if (a.commitIndex !== b.commitIndex)
2019
+ return b.commitIndex - a.commitIndex
1694
2020
  return b.eventIndex - a.eventIndex
1695
2021
  })
1696
2022
 
@@ -1709,7 +2035,8 @@
1709
2035
  function matchesGenericItem(item, fieldDefs, engine, selectedIds) {
1710
2036
  if (!engine.active) return true
1711
2037
  return fieldDefs.some((field) => {
1712
- if (selectedIds.length > 0 && !selectedIds.includes(field.id)) return false
2038
+ if (selectedIds.length > 0 && !selectedIds.includes(field.id))
2039
+ return false
1713
2040
  const tokens = toTokens(field.get(item))
1714
2041
  return tokens.some((token) => engine.test(token))
1715
2042
  })
@@ -1728,8 +2055,14 @@
1728
2055
  function renderTabButtons() {
1729
2056
  setPressed(tabLeavesBtn, state.activeTab === VIEW_IDS.leaves)
1730
2057
  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'
2058
+ tabLeavesBtn.style.display = state.availableTabs.has(VIEW_IDS.leaves)
2059
+ ? ''
2060
+ : 'none'
2061
+ tabChangelogBtn.style.display = state.availableTabs.has(
2062
+ VIEW_IDS.changelog,
2063
+ )
2064
+ ? ''
2065
+ : 'none'
1733
2066
  const isLeavesTab = state.activeTab === VIEW_IDS.leaves
1734
2067
  coreFieldsBtn.style.display = isLeavesTab ? '' : 'none'
1735
2068
  schemasOnlyFieldsBtn.style.display = isLeavesTab ? '' : 'none'
@@ -1745,7 +2078,9 @@
1745
2078
  if (engine.error) {
1746
2079
  statusEl.textContent = `Invalid regex: ${engine.error}`
1747
2080
  resultsEl.innerHTML = ''
1748
- resultsEl.appendChild(el('div', 'empty', 'Fix the regex to continue.'))
2081
+ resultsEl.appendChild(
2082
+ el('div', 'empty', 'Fix the regex to continue.'),
2083
+ )
1749
2084
  renderActiveFilterChips({
1750
2085
  selectedIds: selectedFieldIds(),
1751
2086
  hasRegexError: true,
@@ -1758,16 +2093,27 @@
1758
2093
  renderTabButtons()
1759
2094
  renderActiveFilterChips({ selectedIds, hasRegexError: false })
1760
2095
  if (state.activeTab === VIEW_IDS.leaves) {
1761
- const filtered = state.leaves.filter((leaf) => matchesLeaf(leaf, engine, selectedIds))
2096
+ const filtered = state.leaves.filter((leaf) =>
2097
+ matchesLeaf(leaf, engine, selectedIds),
2098
+ )
1762
2099
  statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
1763
- renderCollection(filtered, (leaf) => renderLeaf(leaf, engine, selectedIds), 'No matches.')
2100
+ renderCollection(
2101
+ filtered,
2102
+ (leaf) => renderLeaf(leaf, engine, selectedIds),
2103
+ 'No matches.',
2104
+ )
1764
2105
  } else {
1765
2106
  const fieldDefs = currentFieldDefs()
1766
2107
  const items = changelogItemsByTab(state.activeTab)
1767
- const filtered = items.filter((item) => matchesGenericItem(item, fieldDefs, engine, selectedIds))
2108
+ const filtered = items.filter((item) =>
2109
+ matchesGenericItem(item, fieldDefs, engine, selectedIds),
2110
+ )
1768
2111
  statusEl.textContent = `${filtered.length} / ${items.length} changelog events matched.`
1769
2112
  const commitBySha = new Map(
1770
- (state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
2113
+ (state.changelogPayload?.commits || []).map((commit) => [
2114
+ commit.sha,
2115
+ commit,
2116
+ ]),
1771
2117
  )
1772
2118
  if (state.viewerMode === VIEWER_MODE.changelog) {
1773
2119
  renderCollection(
@@ -1777,7 +2123,11 @@
1777
2123
  item.eventKind === 'route'
1778
2124
  ? `Route: ${item.routeKey}`
1779
2125
  : `Source: ${item.displayName || item.sourceObjectId}`
1780
- return renderChangelogEventLines(item.event, commitBySha, contextLabel)
2126
+ return renderChangelogEventLines(
2127
+ item.event,
2128
+ commitBySha,
2129
+ contextLabel,
2130
+ )
1781
2131
  },
1782
2132
  'No matches.',
1783
2133
  )
@@ -1799,15 +2149,17 @@
1799
2149
  }
1800
2150
 
1801
2151
  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
- )
2152
+ const selected =
2153
+ state.selectedFieldIdsByTab[state.activeTab] || new Set()
2154
+ return currentFieldDefs()
2155
+ .filter((field) => selected.has(field.id))
2156
+ .map((field) => field.id)
1806
2157
  }
1807
2158
 
1808
2159
  function renderFieldChips() {
1809
2160
  const fieldDefs = currentFieldDefs()
1810
- const selected = state.selectedFieldIdsByTab[state.activeTab] || new Set()
2161
+ const selected =
2162
+ state.selectedFieldIdsByTab[state.activeTab] || new Set()
1811
2163
  fieldChipGroups.innerHTML = ''
1812
2164
  const groups =
1813
2165
  state.activeTab === VIEW_IDS.leaves
@@ -1815,7 +2167,10 @@
1815
2167
  : [
1816
2168
  {
1817
2169
  id: 'tabFields',
1818
- label: state.activeTab === VIEW_IDS.changelog ? 'Changelog fields' : 'Source fields',
2170
+ label:
2171
+ state.activeTab === VIEW_IDS.changelog
2172
+ ? 'Changelog fields'
2173
+ : 'Source fields',
1819
2174
  fieldIds: fieldDefs.map((field) => field.id),
1820
2175
  },
1821
2176
  ]
@@ -1854,7 +2209,9 @@
1854
2209
  function setFieldSelection(allowedIds, options = {}) {
1855
2210
  const { rerender = true } = options
1856
2211
  state.selectedFieldIdsByTab[state.activeTab] = new Set(
1857
- currentFieldDefs().map((field) => field.id).filter((id) => allowedIds.has(id)),
2212
+ currentFieldDefs()
2213
+ .map((field) => field.id)
2214
+ .filter((id) => allowedIds.has(id)),
1858
2215
  )
1859
2216
  renderFieldChips()
1860
2217
  if (rerender) renderResults()
@@ -1866,7 +2223,10 @@
1866
2223
  setPressed(regexSearchToggle, false)
1867
2224
  typeMatchModeInput.value = 'contains'
1868
2225
  setPressed(schemaRowsMatchOnlyToggle, false)
1869
- setFieldSelection(new Set(currentFieldDefs().map((field) => field.id)), { rerender: false })
2226
+ setFieldSelection(
2227
+ new Set(currentFieldDefs().map((field) => field.id)),
2228
+ { rerender: false },
2229
+ )
1870
2230
  renderResults()
1871
2231
  }
1872
2232
 
@@ -1931,12 +2291,16 @@
1931
2291
  }
1932
2292
 
1933
2293
  if (chipModels.length === 0) {
1934
- activeFilterChips.appendChild(el('span', 'empty', 'No active filters'))
2294
+ activeFilterChips.appendChild(
2295
+ el('span', 'empty', 'No active filters'),
2296
+ )
1935
2297
  return
1936
2298
  }
1937
2299
 
1938
2300
  chipModels.forEach((chipModel) => {
1939
- const chip = document.createElement(chipModel.onClick ? 'button' : 'span')
2301
+ const chip = document.createElement(
2302
+ chipModel.onClick ? 'button' : 'span',
2303
+ )
1940
2304
  chip.className = chipModel.onClick ? 'chip chip-btn' : 'chip'
1941
2305
  chip.textContent = chipModel.text
1942
2306
  if (chipModel.onClick) {
@@ -1953,10 +2317,14 @@
1953
2317
  search: searchInput.value,
1954
2318
  caseSensitive: isPressed(caseSensitiveToggle),
1955
2319
  regex: isPressed(regexSearchToggle),
1956
- typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
2320
+ typeMatchMode:
2321
+ typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
1957
2322
  schemaRowsMatchOnly: isPressed(schemaRowsMatchOnlyToggle),
1958
2323
  selectedFieldsByTab: Object.fromEntries(
1959
- Object.entries(state.selectedFieldIdsByTab).map(([key, value]) => [key, Array.from(value)]),
2324
+ Object.entries(state.selectedFieldIdsByTab).map(([key, value]) => [
2325
+ key,
2326
+ Array.from(value),
2327
+ ]),
1960
2328
  ),
1961
2329
  }
1962
2330
  }
@@ -1965,7 +2333,9 @@
1965
2333
  if (isHydratingFromUrl) return
1966
2334
 
1967
2335
  const data = collectFilterState()
1968
- const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
2336
+ const params = new URLSearchParams(
2337
+ window.location.hash.replace(/^#/, ''),
2338
+ )
1969
2339
  const serialized = encodeURIComponent(JSON.stringify(data))
1970
2340
  params.set(URL_PARAM_KEY, serialized)
1971
2341
  const nextHash = params.toString()
@@ -1975,7 +2345,9 @@
1975
2345
  }
1976
2346
 
1977
2347
  function hydrateFilterStateFromUrl() {
1978
- const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
2348
+ const params = new URLSearchParams(
2349
+ window.location.hash.replace(/^#/, ''),
2350
+ )
1979
2351
  const raw = params.get(URL_PARAM_KEY)
1980
2352
  if (!raw) return
1981
2353
 
@@ -1991,20 +2363,32 @@
1991
2363
  if (parsed.activeTab === 'changelogRoute') {
1992
2364
  state.activeTab = VIEW_IDS.changelog
1993
2365
  }
1994
- if (typeof parsed.search === 'string') searchInput.value = parsed.search
2366
+ if (typeof parsed.search === 'string')
2367
+ searchInput.value = parsed.search
1995
2368
  setPressed(caseSensitiveToggle, Boolean(parsed.caseSensitive))
1996
2369
  setPressed(regexSearchToggle, Boolean(parsed.regex))
1997
- if (parsed.typeMatchMode === 'exact' || parsed.typeMatchMode === 'contains') {
2370
+ if (
2371
+ parsed.typeMatchMode === 'exact' ||
2372
+ parsed.typeMatchMode === 'contains'
2373
+ ) {
1998
2374
  typeMatchModeInput.value = parsed.typeMatchMode
1999
2375
  }
2000
- setPressed(schemaRowsMatchOnlyToggle, Boolean(parsed.schemaRowsMatchOnly))
2376
+ setPressed(
2377
+ schemaRowsMatchOnlyToggle,
2378
+ Boolean(parsed.schemaRowsMatchOnly),
2379
+ )
2001
2380
 
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
- })
2381
+ if (
2382
+ parsed.selectedFieldsByTab &&
2383
+ typeof parsed.selectedFieldsByTab === 'object'
2384
+ ) {
2385
+ Object.entries(parsed.selectedFieldsByTab).forEach(
2386
+ ([tabId, ids]) => {
2387
+ if (!Array.isArray(ids)) return
2388
+ if (!state.selectedFieldIdsByTab[tabId]) return
2389
+ state.selectedFieldIdsByTab[tabId] = new Set(ids)
2390
+ },
2391
+ )
2008
2392
  }
2009
2393
  } catch (error) {
2010
2394
  // Ignore malformed hash state.
@@ -2070,15 +2454,13 @@
2070
2454
  }
2071
2455
 
2072
2456
  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.`
2457
+ statusEl.textContent = `Loaded unified bundle with ${state.leaves.length} routes and ${
2458
+ state.changelogPayload.commits?.length || 0
2459
+ } commits.`
2077
2460
  } else if (state.leavesPayload) {
2078
2461
  statusEl.textContent = `Loaded payload with ${state.leaves.length} routes.`
2079
2462
  } else if (state.changelogPayload) {
2080
- statusEl.textContent =
2081
- `Loaded changelog payload with ${state.changelogPayload.commits?.length || 0} commits.`
2463
+ statusEl.textContent = `Loaded changelog payload with ${state.changelogPayload.commits?.length || 0} commits.`
2082
2464
  } else {
2083
2465
  statusEl.textContent = 'No payload loaded.'
2084
2466
  }
@@ -2104,7 +2486,8 @@
2104
2486
  try {
2105
2487
  await handleFile(file)
2106
2488
  } catch (error) {
2107
- statusEl.textContent = error instanceof Error ? error.message : String(error)
2489
+ statusEl.textContent =
2490
+ error instanceof Error ? error.message : String(error)
2108
2491
  resultsEl.innerHTML = ''
2109
2492
  }
2110
2493
  })
@@ -2124,16 +2507,28 @@
2124
2507
  renderResults()
2125
2508
  })
2126
2509
 
2127
- tabLeavesBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.leaves))
2128
- tabChangelogBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.changelog))
2510
+ tabLeavesBtn.addEventListener('click', () =>
2511
+ setActiveTab(VIEW_IDS.leaves),
2512
+ )
2513
+ tabChangelogBtn.addEventListener('click', () =>
2514
+ setActiveTab(VIEW_IDS.changelog),
2515
+ )
2129
2516
 
2130
2517
  selectAllFieldsBtn.addEventListener('click', () =>
2131
2518
  setFieldSelection(new Set(currentFieldDefs().map((field) => field.id))),
2132
2519
  )
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))
2520
+ clearAllFieldsBtn.addEventListener('click', () =>
2521
+ setFieldSelection(new Set()),
2522
+ )
2523
+ coreFieldsBtn.addEventListener('click', () =>
2524
+ setFieldSelection(CORE_FIELD_IDS),
2525
+ )
2526
+ schemasOnlyFieldsBtn.addEventListener('click', () =>
2527
+ setFieldSelection(SCHEMA_SCOPE_FIELD_IDS),
2528
+ )
2529
+ sourceOnlyFieldsBtn.addEventListener('click', () =>
2530
+ setFieldSelection(SOURCE_FIELD_IDS),
2531
+ )
2137
2532
  resetFiltersBtn.addEventListener('click', resetFiltersToDefault)
2138
2533
 
2139
2534
  renderFieldChips()