@emeryld/rrroutes-export 1.0.14 → 1.0.16

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.14",
4
+ "version": "1.0.16",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "dist/index.cjs",
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Finalized Leaves Viewer</title>
6
+ <title>RRRoutes Leaves Viewer</title>
7
7
  <style>
8
8
  :root {
9
9
  --bg: #212121;
@@ -170,6 +170,10 @@
170
170
  color: var(--accent);
171
171
  }
172
172
 
173
+ .route-changelog-summary-gray {
174
+ color: #c6c6c6;
175
+ }
176
+
173
177
  .leaf-content {
174
178
  display: grid;
175
179
  gap: 4px;
@@ -358,6 +362,18 @@
358
362
  font-family: inherit;
359
363
  }
360
364
 
365
+ .changelog-lines {
366
+ display: grid;
367
+ gap: 10px;
368
+ }
369
+
370
+ .changelog-line-item {
371
+ border-top: 1px solid var(--border);
372
+ padding: 10px 2px;
373
+ display: grid;
374
+ gap: 4px;
375
+ }
376
+
361
377
  .schema-block {
362
378
  border-top: 1px solid var(--border);
363
379
  padding: 8px 0 2px;
@@ -472,7 +488,7 @@
472
488
  </head>
473
489
  <body>
474
490
  <div class="wrap">
475
- <h1>Finalized Leaves Viewer</h1>
491
+ <h1 id="viewerHeading">RRRoutes Leaves Viewer</h1>
476
492
  <div class="card controls">
477
493
  <div class="primary-row">
478
494
  <label class="control-block">
@@ -536,8 +552,8 @@
536
552
  <button id="tabLeaves" class="field-chip" type="button" aria-pressed="true">
537
553
  Leaves
538
554
  </button>
539
- <button id="tabChangelogRoute" class="field-chip" type="button" aria-pressed="false">
540
- Changelog / By Route
555
+ <button id="tabChangelog" class="field-chip" type="button" aria-pressed="false">
556
+ Changelog
541
557
  </button>
542
558
  <button id="tabChangelogSource" class="field-chip" type="button" aria-pressed="false">
543
559
  Changelog / By Source
@@ -551,7 +567,7 @@
551
567
  <div id="activeFilterChips" class="chips filters"></div>
552
568
  </div>
553
569
 
554
- <div id="status" class="meta">Load a JSON export to begin.</div>
570
+ <div id="status" class="meta">Load a leaves export, changelog export, or unified bundle JSON to begin.</div>
555
571
  </div>
556
572
 
557
573
  <div id="results"></div>
@@ -629,36 +645,52 @@
629
645
  },
630
646
  ]
631
647
 
632
- const CHANGELOG_ROUTE_FIELDS = [
648
+ const CHANGELOG_TIMELINE_FIELDS = [
649
+ {
650
+ id: 'eventKind',
651
+ label: 'kind',
652
+ get: (item) => [item.eventKind],
653
+ },
654
+ {
655
+ id: 'eventType',
656
+ label: 'event',
657
+ get: (item) => [item.event.type],
658
+ },
633
659
  {
634
660
  id: 'routeKey',
635
661
  label: 'route',
636
662
  get: (item) => [item.routeKey],
637
663
  },
638
664
  {
639
- id: 'eventType',
640
- label: 'event type',
641
- get: (item) => item.events.map((event) => event.type),
665
+ id: 'sourceObject',
666
+ label: 'source object',
667
+ get: (item) => [item.sourceObjectId, item.displayName],
668
+ },
669
+ {
670
+ id: 'commit',
671
+ label: 'commit',
672
+ get: (item) => [
673
+ item.event.commitSha,
674
+ item.commit?.authorDate,
675
+ item.commit?.authorName,
676
+ item.commit?.subject,
677
+ ],
642
678
  },
643
679
  {
644
680
  id: 'schemaDiff',
645
681
  label: 'schema diff',
646
682
  get: (item) =>
647
- item.events.flatMap((event) =>
648
- Array.isArray(event.schemaDiff)
649
- ? event.schemaDiff.map((delta) => JSON.stringify(delta))
650
- : [],
651
- ),
683
+ Array.isArray(item.event.schemaDiff)
684
+ ? item.event.schemaDiff.map((delta) => JSON.stringify(delta))
685
+ : [],
652
686
  },
653
687
  {
654
688
  id: 'cfgDiff',
655
689
  label: 'cfg diff',
656
690
  get: (item) =>
657
- item.events.flatMap((event) =>
658
- Array.isArray(event.cfgDiff)
659
- ? event.cfgDiff.map((delta) => JSON.stringify(delta))
660
- : [],
661
- ),
691
+ Array.isArray(item.event.cfgDiff)
692
+ ? item.event.cfgDiff.map((delta) => JSON.stringify(delta))
693
+ : [],
662
694
  },
663
695
  ]
664
696
 
@@ -726,18 +758,39 @@
726
758
 
727
759
  const VIEW_IDS = {
728
760
  leaves: 'leaves',
729
- changelogRoute: 'changelogRoute',
761
+ changelog: 'changelog',
730
762
  changelogSource: 'changelogSource',
731
763
  }
732
764
 
765
+ const VIEWER_MODE = {
766
+ leaves: 'leaves',
767
+ bundle: 'bundle',
768
+ changelog: 'changelog',
769
+ }
770
+
771
+ const VIEWER_MODE_META = {
772
+ [VIEWER_MODE.leaves]: {
773
+ title: 'RRRoutes Leaves Viewer',
774
+ heading: 'RRRoutes Leaves Viewer',
775
+ },
776
+ [VIEWER_MODE.bundle]: {
777
+ title: 'RRRoutes Bundle Viewer',
778
+ heading: 'RRRoutes Bundle Viewer',
779
+ },
780
+ [VIEWER_MODE.changelog]: {
781
+ title: 'RRRoutes Changelog Viewer',
782
+ heading: 'RRRoutes Changelog Viewer',
783
+ },
784
+ }
785
+
733
786
  const TAB_CONFIG = {
734
787
  [VIEW_IDS.leaves]: {
735
788
  fieldDefs: SEARCH_FIELDS,
736
789
  defaultSelected: new Set(SEARCH_FIELDS.map((field) => field.id)),
737
790
  },
738
- [VIEW_IDS.changelogRoute]: {
739
- fieldDefs: CHANGELOG_ROUTE_FIELDS,
740
- defaultSelected: new Set(CHANGELOG_ROUTE_FIELDS.map((field) => field.id)),
791
+ [VIEW_IDS.changelog]: {
792
+ fieldDefs: CHANGELOG_TIMELINE_FIELDS,
793
+ defaultSelected: new Set(CHANGELOG_TIMELINE_FIELDS.map((field) => field.id)),
741
794
  },
742
795
  [VIEW_IDS.changelogSource]: {
743
796
  fieldDefs: CHANGELOG_SOURCE_FIELDS,
@@ -751,10 +804,11 @@
751
804
  changelogPayload: null,
752
805
  leaves: [],
753
806
  activeTab: VIEW_IDS.leaves,
807
+ viewerMode: VIEWER_MODE.leaves,
754
808
  availableTabs: new Set([VIEW_IDS.leaves]),
755
809
  selectedFieldIdsByTab: {
756
810
  [VIEW_IDS.leaves]: new Set(TAB_CONFIG[VIEW_IDS.leaves].defaultSelected),
757
- [VIEW_IDS.changelogRoute]: new Set(TAB_CONFIG[VIEW_IDS.changelogRoute].defaultSelected),
811
+ [VIEW_IDS.changelog]: new Set(TAB_CONFIG[VIEW_IDS.changelog].defaultSelected),
758
812
  [VIEW_IDS.changelogSource]: new Set(TAB_CONFIG[VIEW_IDS.changelogSource].defaultSelected),
759
813
  },
760
814
  }
@@ -775,14 +829,28 @@
775
829
  const resetFiltersBtn = document.getElementById('resetFilters')
776
830
  const statusEl = document.getElementById('status')
777
831
  const resultsEl = document.getElementById('results')
832
+ const viewerHeadingEl = document.getElementById('viewerHeading')
778
833
  const controlsEl = document.querySelector('.controls')
779
834
  const tabLeavesBtn = document.getElementById('tabLeaves')
780
- const tabChangelogRouteBtn = document.getElementById('tabChangelogRoute')
835
+ const tabChangelogBtn = document.getElementById('tabChangelog')
781
836
  const tabChangelogSourceBtn = document.getElementById('tabChangelogSource')
782
837
 
783
838
  const URL_PARAM_KEY = 'filters'
784
839
  let isHydratingFromUrl = false
785
840
 
841
+ function resolveViewerMode(payload) {
842
+ if (isBundlePayload(payload)) return VIEWER_MODE.bundle
843
+ if (isChangelogPayload(payload)) return VIEWER_MODE.changelog
844
+ if (payload && Array.isArray(payload.leaves)) return VIEWER_MODE.leaves
845
+ return VIEWER_MODE.leaves
846
+ }
847
+
848
+ function applyViewerModeMeta() {
849
+ const meta = VIEWER_MODE_META[state.viewerMode] || VIEWER_MODE_META[VIEWER_MODE.leaves]
850
+ document.title = meta.title
851
+ if (viewerHeadingEl) viewerHeadingEl.textContent = meta.heading
852
+ }
853
+
786
854
  function escapeHtml(value) {
787
855
  return String(value)
788
856
  .replace(/&/g, '&amp;')
@@ -1414,6 +1482,32 @@
1414
1482
  content.appendChild(separatedSchemas)
1415
1483
  }
1416
1484
 
1485
+ const routeHistory = state.changelogPayload?.byRoute?.[leaf.key]
1486
+ if (Array.isArray(routeHistory) && routeHistory.length > 0) {
1487
+ const commitBySha = new Map(
1488
+ (state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
1489
+ )
1490
+ const routeChangelogSection = el('div', 'section')
1491
+ const routeChangelogDetails = el('details')
1492
+ const routeChangelogSummary = el(
1493
+ 'summary',
1494
+ '',
1495
+ `Route Changelog (${routeHistory.length} event${routeHistory.length === 1 ? '' : 's'})`,
1496
+ )
1497
+ if (state.viewerMode === VIEWER_MODE.bundle) {
1498
+ routeChangelogSummary.classList.add('route-changelog-summary-gray')
1499
+ }
1500
+ routeChangelogDetails.appendChild(routeChangelogSummary)
1501
+
1502
+ const routeChangelogBody = el('div', 'leaf-content')
1503
+ routeHistory.forEach((event) => {
1504
+ routeChangelogBody.appendChild(renderEventCard(event, commitBySha))
1505
+ })
1506
+ routeChangelogDetails.appendChild(routeChangelogBody)
1507
+ routeChangelogSection.appendChild(routeChangelogDetails)
1508
+ content.appendChild(routeChangelogSection)
1509
+ }
1510
+
1417
1511
  details.appendChild(content)
1418
1512
  return details
1419
1513
  }
@@ -1520,13 +1614,94 @@
1520
1614
  return card
1521
1615
  }
1522
1616
 
1617
+ function buildCommitMetaLine(event, commitBySha) {
1618
+ const commit = commitBySha.get(event.commitSha)
1619
+ const commitDateTime = formatCommitDateTime(commit?.authorDate)
1620
+ return [
1621
+ event.commitSha.slice(0, 12),
1622
+ commitDateTime,
1623
+ commit?.authorName || null,
1624
+ commit?.subject || null,
1625
+ ]
1626
+ .filter(Boolean)
1627
+ .join(' - ')
1628
+ }
1629
+
1630
+ function renderChangelogEventLines(event, commitBySha, contextLabel) {
1631
+ const row = el('div', 'changelog-line-item')
1632
+ row.appendChild(el('div', 'meta', buildCommitMetaLine(event, commitBySha)))
1633
+ row.appendChild(el('div', 'mono', `${contextLabel} (${event.type})`))
1634
+
1635
+ if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
1636
+ event.schemaDiff.forEach((delta) => {
1637
+ const line = formatSchemaDiffLine(delta)
1638
+ if (!line) return
1639
+ row.appendChild(el('div', 'meta mono', line))
1640
+ })
1641
+ }
1642
+ if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
1643
+ event.cfgDiff.forEach((diff) => {
1644
+ const line = formatCfgDiffLine(diff)
1645
+ if (!line) return
1646
+ row.appendChild(el('div', 'meta mono', line))
1647
+ })
1648
+ }
1649
+ if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
1650
+ row.appendChild(
1651
+ el('div', 'meta mono', `impacted routes: ${event.impactedRoutes.join(', ')}`),
1652
+ )
1653
+ }
1654
+
1655
+ return row
1656
+ }
1657
+
1658
+ function buildChronologicalEvents(payload) {
1659
+ if (!payload) return []
1660
+ const commitBySha = new Map((payload.commits || []).map((commit) => [commit.sha, commit]))
1661
+ const commitOrder = new Map((payload.commits || []).map((commit, index) => [commit.sha, index]))
1662
+ const rows = []
1663
+ let sequence = 0
1664
+
1665
+ Object.entries(payload.byRoute || {}).forEach(([routeKey, events]) => {
1666
+ ;(events || []).forEach((event) => {
1667
+ rows.push({
1668
+ eventKind: 'route',
1669
+ routeKey,
1670
+ event,
1671
+ commit: commitBySha.get(event.commitSha),
1672
+ commitIndex: commitOrder.get(event.commitSha) ?? Number.MAX_SAFE_INTEGER,
1673
+ eventIndex: sequence++,
1674
+ })
1675
+ })
1676
+ })
1677
+
1678
+ Object.entries(payload.bySourceObject || {}).forEach(([sourceObjectId, events]) => {
1679
+ ;(events || []).forEach((event) => {
1680
+ rows.push({
1681
+ eventKind: 'source',
1682
+ sourceObjectId,
1683
+ displayName: event.displayName || sourceObjectId,
1684
+ event,
1685
+ commit: commitBySha.get(event.commitSha),
1686
+ commitIndex: commitOrder.get(event.commitSha) ?? Number.MAX_SAFE_INTEGER,
1687
+ eventIndex: sequence++,
1688
+ })
1689
+ })
1690
+ })
1691
+
1692
+ rows.sort((a, b) => {
1693
+ if (a.commitIndex !== b.commitIndex) return a.commitIndex - b.commitIndex
1694
+ return a.eventIndex - b.eventIndex
1695
+ })
1696
+
1697
+ return rows
1698
+ }
1699
+
1523
1700
  function changelogItemsByTab(tabId) {
1524
1701
  const payload = state.changelogPayload
1525
1702
  if (!payload) return []
1526
- if (tabId === VIEW_IDS.changelogRoute) {
1527
- return Object.entries(payload.byRoute || {})
1528
- .sort(([a], [b]) => a.localeCompare(b))
1529
- .map(([routeKey, events]) => ({ routeKey, events }))
1703
+ if (tabId === VIEW_IDS.changelog) {
1704
+ return buildChronologicalEvents(payload)
1530
1705
  }
1531
1706
  if (tabId === VIEW_IDS.changelogSource) {
1532
1707
  return Object.entries(payload.bySourceObject || {})
@@ -1561,10 +1736,10 @@
1561
1736
 
1562
1737
  function renderTabButtons() {
1563
1738
  setPressed(tabLeavesBtn, state.activeTab === VIEW_IDS.leaves)
1564
- setPressed(tabChangelogRouteBtn, state.activeTab === VIEW_IDS.changelogRoute)
1739
+ setPressed(tabChangelogBtn, state.activeTab === VIEW_IDS.changelog)
1565
1740
  setPressed(tabChangelogSourceBtn, state.activeTab === VIEW_IDS.changelogSource)
1566
1741
  tabLeavesBtn.style.display = state.availableTabs.has(VIEW_IDS.leaves) ? '' : 'none'
1567
- tabChangelogRouteBtn.style.display = state.availableTabs.has(VIEW_IDS.changelogRoute) ? '' : 'none'
1742
+ tabChangelogBtn.style.display = state.availableTabs.has(VIEW_IDS.changelog) ? '' : 'none'
1568
1743
  tabChangelogSourceBtn.style.display = state.availableTabs.has(VIEW_IDS.changelogSource) ? '' : 'none'
1569
1744
  const isLeavesTab = state.activeTab === VIEW_IDS.leaves
1570
1745
  coreFieldsBtn.style.display = isLeavesTab ? '' : 'none'
@@ -1602,26 +1777,58 @@
1602
1777
  const items = changelogItemsByTab(state.activeTab)
1603
1778
  const filtered = items.filter((item) => matchesGenericItem(item, fieldDefs, engine, selectedIds))
1604
1779
  statusEl.textContent =
1605
- state.activeTab === VIEW_IDS.changelogRoute
1606
- ? `${filtered.length} / ${items.length} route changelog groups matched.`
1780
+ state.activeTab === VIEW_IDS.changelog
1781
+ ? `${filtered.length} / ${items.length} changelog events matched.`
1607
1782
  : `${filtered.length} / ${items.length} source changelog groups matched.`
1608
1783
  const commitBySha = new Map(
1609
1784
  (state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
1610
1785
  )
1611
- renderCollection(
1612
- filtered,
1613
- (item) => {
1614
- const details = el('details', 'leaf')
1615
- const title =
1616
- state.activeTab === VIEW_IDS.changelogRoute
1617
- ? `${item.routeKey} (${item.events.length})`
1618
- : `${item.displayName} (${item.events.length})`
1619
- details.appendChild(el('summary', '', title))
1620
- item.events.forEach((event) => details.appendChild(renderEventCard(event, commitBySha)))
1621
- return details
1622
- },
1623
- 'No matches.',
1624
- )
1786
+ if (state.viewerMode === VIEWER_MODE.changelog) {
1787
+ renderCollection(
1788
+ filtered,
1789
+ (item) => {
1790
+ if (state.activeTab === VIEW_IDS.changelog) {
1791
+ const contextLabel =
1792
+ item.eventKind === 'route'
1793
+ ? `Route: ${item.routeKey}`
1794
+ : `Source: ${item.displayName || item.sourceObjectId}`
1795
+ return renderChangelogEventLines(item.event, commitBySha, contextLabel)
1796
+ }
1797
+
1798
+ const lines = el('div', 'changelog-lines')
1799
+ ;(item.events || []).forEach((event) => {
1800
+ lines.appendChild(
1801
+ renderChangelogEventLines(
1802
+ event,
1803
+ commitBySha,
1804
+ `Source: ${item.displayName || item.sourceId}`,
1805
+ ),
1806
+ )
1807
+ })
1808
+ return lines
1809
+ },
1810
+ 'No matches.',
1811
+ )
1812
+ } else {
1813
+ renderCollection(
1814
+ filtered,
1815
+ (item) => {
1816
+ const details = el('details', 'leaf')
1817
+ const title =
1818
+ state.activeTab === VIEW_IDS.changelog
1819
+ ? `${item.eventKind === 'route' ? `Route: ${item.routeKey}` : `Source: ${item.displayName || item.sourceObjectId}`} (${item.event.type})`
1820
+ : `${item.displayName} (${item.events.length})`
1821
+ details.appendChild(el('summary', '', title))
1822
+ if (state.activeTab === VIEW_IDS.changelog) {
1823
+ details.appendChild(renderEventCard(item.event, commitBySha))
1824
+ } else {
1825
+ item.events.forEach((event) => details.appendChild(renderEventCard(event, commitBySha)))
1826
+ }
1827
+ return details
1828
+ },
1829
+ 'No matches.',
1830
+ )
1831
+ }
1625
1832
  }
1626
1833
  syncFilterStateToUrl()
1627
1834
  }
@@ -1643,7 +1850,7 @@
1643
1850
  : [
1644
1851
  {
1645
1852
  id: 'tabFields',
1646
- label: state.activeTab === VIEW_IDS.changelogRoute ? 'Route fields' : 'Source fields',
1853
+ label: state.activeTab === VIEW_IDS.changelog ? 'Changelog fields' : 'Source fields',
1647
1854
  fieldIds: fieldDefs.map((field) => field.id),
1648
1855
  },
1649
1856
  ]
@@ -1812,11 +2019,14 @@
1812
2019
  isHydratingFromUrl = true
1813
2020
  if (
1814
2021
  parsed.activeTab === VIEW_IDS.leaves ||
1815
- parsed.activeTab === VIEW_IDS.changelogRoute ||
2022
+ parsed.activeTab === VIEW_IDS.changelog ||
1816
2023
  parsed.activeTab === VIEW_IDS.changelogSource
1817
2024
  ) {
1818
2025
  state.activeTab = parsed.activeTab
1819
2026
  }
2027
+ if (parsed.activeTab === 'changelogRoute') {
2028
+ state.activeTab = VIEW_IDS.changelog
2029
+ }
1820
2030
  if (typeof parsed.search === 'string') searchInput.value = parsed.search
1821
2031
  setPressed(caseSensitiveToggle, Boolean(parsed.caseSensitive))
1822
2032
  setPressed(regexSearchToggle, Boolean(parsed.regex))
@@ -1863,6 +2073,8 @@
1863
2073
  }
1864
2074
 
1865
2075
  function applyLoadedPayload(payload) {
2076
+ state.viewerMode = resolveViewerMode(payload)
2077
+ applyViewerModeMeta()
1866
2078
  state.payload = payload
1867
2079
  state.availableTabs = new Set()
1868
2080
  state.leavesPayload = null
@@ -1883,15 +2095,15 @@
1883
2095
  state.leaves = state.leavesPayload.leaves || []
1884
2096
  }
1885
2097
  if (state.changelogPayload) {
1886
- state.availableTabs.add(VIEW_IDS.changelogRoute)
2098
+ state.availableTabs.add(VIEW_IDS.changelog)
1887
2099
  state.availableTabs.add(VIEW_IDS.changelogSource)
1888
2100
  }
1889
2101
 
1890
2102
  if (!state.availableTabs.has(state.activeTab)) {
1891
2103
  state.activeTab = state.availableTabs.has(VIEW_IDS.leaves)
1892
2104
  ? VIEW_IDS.leaves
1893
- : state.availableTabs.has(VIEW_IDS.changelogRoute)
1894
- ? VIEW_IDS.changelogRoute
2105
+ : state.availableTabs.has(VIEW_IDS.changelog)
2106
+ ? VIEW_IDS.changelog
1895
2107
  : VIEW_IDS.changelogSource
1896
2108
  }
1897
2109
 
@@ -1951,7 +2163,7 @@
1951
2163
  })
1952
2164
 
1953
2165
  tabLeavesBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.leaves))
1954
- tabChangelogRouteBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.changelogRoute))
2166
+ tabChangelogBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.changelog))
1955
2167
  tabChangelogSourceBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.changelogSource))
1956
2168
 
1957
2169
  selectAllFieldsBtn.addEventListener('click', () =>
@@ -1966,6 +2178,7 @@
1966
2178
  renderFieldChips()
1967
2179
  hydrateFilterStateFromUrl()
1968
2180
  initializeFromBakedPayload()
2181
+ applyViewerModeMeta()
1969
2182
  renderResults()
1970
2183
  </script>
1971
2184
  </body>