@emeryld/rrroutes-export 1.0.15 → 1.0.17

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.15",
4
+ "version": "1.0.17",
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>RRRoutes Export 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>RRRoutes Export 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">
@@ -746,6 +762,27 @@
746
762
  changelogSource: 'changelogSource',
747
763
  }
748
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
+
749
786
  const TAB_CONFIG = {
750
787
  [VIEW_IDS.leaves]: {
751
788
  fieldDefs: SEARCH_FIELDS,
@@ -767,6 +804,7 @@
767
804
  changelogPayload: null,
768
805
  leaves: [],
769
806
  activeTab: VIEW_IDS.leaves,
807
+ viewerMode: VIEWER_MODE.leaves,
770
808
  availableTabs: new Set([VIEW_IDS.leaves]),
771
809
  selectedFieldIdsByTab: {
772
810
  [VIEW_IDS.leaves]: new Set(TAB_CONFIG[VIEW_IDS.leaves].defaultSelected),
@@ -791,6 +829,7 @@
791
829
  const resetFiltersBtn = document.getElementById('resetFilters')
792
830
  const statusEl = document.getElementById('status')
793
831
  const resultsEl = document.getElementById('results')
832
+ const viewerHeadingEl = document.getElementById('viewerHeading')
794
833
  const controlsEl = document.querySelector('.controls')
795
834
  const tabLeavesBtn = document.getElementById('tabLeaves')
796
835
  const tabChangelogBtn = document.getElementById('tabChangelog')
@@ -799,6 +838,19 @@
799
838
  const URL_PARAM_KEY = 'filters'
800
839
  let isHydratingFromUrl = false
801
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
+
802
854
  function escapeHtml(value) {
803
855
  return String(value)
804
856
  .replace(/&/g, '&amp;')
@@ -1442,6 +1494,9 @@
1442
1494
  '',
1443
1495
  `Route Changelog (${routeHistory.length} event${routeHistory.length === 1 ? '' : 's'})`,
1444
1496
  )
1497
+ if (state.viewerMode === VIEWER_MODE.bundle) {
1498
+ routeChangelogSummary.classList.add('route-changelog-summary-gray')
1499
+ }
1445
1500
  routeChangelogDetails.appendChild(routeChangelogSummary)
1446
1501
 
1447
1502
  const routeChangelogBody = el('div', 'leaf-content')
@@ -1559,6 +1614,47 @@
1559
1614
  return card
1560
1615
  }
1561
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
+
1562
1658
  function buildChronologicalEvents(payload) {
1563
1659
  if (!payload) return []
1564
1660
  const commitBySha = new Map((payload.commits || []).map((commit) => [commit.sha, commit]))
@@ -1573,7 +1669,7 @@
1573
1669
  routeKey,
1574
1670
  event,
1575
1671
  commit: commitBySha.get(event.commitSha),
1576
- commitIndex: commitOrder.get(event.commitSha) ?? Number.MAX_SAFE_INTEGER,
1672
+ commitIndex: commitOrder.get(event.commitSha) ?? -1,
1577
1673
  eventIndex: sequence++,
1578
1674
  })
1579
1675
  })
@@ -1587,15 +1683,15 @@
1587
1683
  displayName: event.displayName || sourceObjectId,
1588
1684
  event,
1589
1685
  commit: commitBySha.get(event.commitSha),
1590
- commitIndex: commitOrder.get(event.commitSha) ?? Number.MAX_SAFE_INTEGER,
1686
+ commitIndex: commitOrder.get(event.commitSha) ?? -1,
1591
1687
  eventIndex: sequence++,
1592
1688
  })
1593
1689
  })
1594
1690
  })
1595
1691
 
1596
1692
  rows.sort((a, b) => {
1597
- if (a.commitIndex !== b.commitIndex) return a.commitIndex - b.commitIndex
1598
- return a.eventIndex - b.eventIndex
1693
+ if (a.commitIndex !== b.commitIndex) return b.commitIndex - a.commitIndex
1694
+ return b.eventIndex - a.eventIndex
1599
1695
  })
1600
1696
 
1601
1697
  return rows
@@ -1608,13 +1704,34 @@
1608
1704
  return buildChronologicalEvents(payload)
1609
1705
  }
1610
1706
  if (tabId === VIEW_IDS.changelogSource) {
1707
+ const commitOrder = new Map((payload.commits || []).map((commit, index) => [commit.sha, index]))
1708
+ const sortEventsNewestFirst = (events) =>
1709
+ [...(events || [])].sort((a, b) => {
1710
+ const aIndex = commitOrder.get(a?.commitSha) ?? -1
1711
+ const bIndex = commitOrder.get(b?.commitSha) ?? -1
1712
+ if (aIndex !== bIndex) return bIndex - aIndex
1713
+ return String(b?.commitSha || '').localeCompare(String(a?.commitSha || ''))
1714
+ })
1715
+
1611
1716
  return Object.entries(payload.bySourceObject || {})
1612
- .sort(([a], [b]) => a.localeCompare(b))
1613
- .map(([sourceId, events]) => ({
1717
+ .map(([sourceId, events]) => {
1718
+ const sortedEvents = sortEventsNewestFirst(events)
1719
+ const latestCommitIndex = sortedEvents.length
1720
+ ? commitOrder.get(sortedEvents[0]?.commitSha) ?? -1
1721
+ : -1
1722
+ return {
1614
1723
  sourceId,
1615
- events,
1616
- displayName: events[0]?.displayName || sourceId,
1617
- }))
1724
+ events: sortedEvents,
1725
+ displayName: sortedEvents[0]?.displayName || sourceId,
1726
+ latestCommitIndex,
1727
+ }
1728
+ })
1729
+ .sort((a, b) => {
1730
+ if (a.latestCommitIndex !== b.latestCommitIndex) {
1731
+ return b.latestCommitIndex - a.latestCommitIndex
1732
+ }
1733
+ return a.displayName.localeCompare(b.displayName)
1734
+ })
1618
1735
  }
1619
1736
  return []
1620
1737
  }
@@ -1687,24 +1804,52 @@
1687
1804
  const commitBySha = new Map(
1688
1805
  (state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
1689
1806
  )
1690
- renderCollection(
1691
- filtered,
1692
- (item) => {
1693
- const details = el('details', 'leaf')
1694
- const title =
1695
- state.activeTab === VIEW_IDS.changelog
1696
- ? `${item.eventKind === 'route' ? `Route: ${item.routeKey}` : `Source: ${item.displayName || item.sourceObjectId}`} (${item.event.type})`
1697
- : `${item.displayName} (${item.events.length})`
1698
- details.appendChild(el('summary', '', title))
1699
- if (state.activeTab === VIEW_IDS.changelog) {
1700
- details.appendChild(renderEventCard(item.event, commitBySha))
1701
- } else {
1702
- item.events.forEach((event) => details.appendChild(renderEventCard(event, commitBySha)))
1703
- }
1704
- return details
1705
- },
1706
- 'No matches.',
1707
- )
1807
+ if (state.viewerMode === VIEWER_MODE.changelog) {
1808
+ renderCollection(
1809
+ filtered,
1810
+ (item) => {
1811
+ if (state.activeTab === VIEW_IDS.changelog) {
1812
+ const contextLabel =
1813
+ item.eventKind === 'route'
1814
+ ? `Route: ${item.routeKey}`
1815
+ : `Source: ${item.displayName || item.sourceObjectId}`
1816
+ return renderChangelogEventLines(item.event, commitBySha, contextLabel)
1817
+ }
1818
+
1819
+ const lines = el('div', 'changelog-lines')
1820
+ ;(item.events || []).forEach((event) => {
1821
+ lines.appendChild(
1822
+ renderChangelogEventLines(
1823
+ event,
1824
+ commitBySha,
1825
+ `Source: ${item.displayName || item.sourceId}`,
1826
+ ),
1827
+ )
1828
+ })
1829
+ return lines
1830
+ },
1831
+ 'No matches.',
1832
+ )
1833
+ } else {
1834
+ renderCollection(
1835
+ filtered,
1836
+ (item) => {
1837
+ const details = el('details', 'leaf')
1838
+ const title =
1839
+ state.activeTab === VIEW_IDS.changelog
1840
+ ? `${item.eventKind === 'route' ? `Route: ${item.routeKey}` : `Source: ${item.displayName || item.sourceObjectId}`} (${item.event.type})`
1841
+ : `${item.displayName} (${item.events.length})`
1842
+ details.appendChild(el('summary', '', title))
1843
+ if (state.activeTab === VIEW_IDS.changelog) {
1844
+ details.appendChild(renderEventCard(item.event, commitBySha))
1845
+ } else {
1846
+ item.events.forEach((event) => details.appendChild(renderEventCard(event, commitBySha)))
1847
+ }
1848
+ return details
1849
+ },
1850
+ 'No matches.',
1851
+ )
1852
+ }
1708
1853
  }
1709
1854
  syncFilterStateToUrl()
1710
1855
  }
@@ -1949,7 +2094,8 @@
1949
2094
  }
1950
2095
 
1951
2096
  function applyLoadedPayload(payload) {
1952
- state.payload = payload
2097
+ state.viewerMode = resolveViewerMode(payload)
2098
+ applyViewerModeMeta()
1953
2099
  state.availableTabs = new Set()
1954
2100
  state.leavesPayload = null
1955
2101
  state.changelogPayload = null
@@ -1964,6 +2110,8 @@
1964
2110
  state.leavesPayload = payload
1965
2111
  }
1966
2112
 
2113
+ state.payload = state.leavesPayload || payload
2114
+
1967
2115
  if (state.leavesPayload) {
1968
2116
  state.availableTabs.add(VIEW_IDS.leaves)
1969
2117
  state.leaves = state.leavesPayload.leaves || []
@@ -2052,6 +2200,7 @@
2052
2200
  renderFieldChips()
2053
2201
  hydrateFilterStateFromUrl()
2054
2202
  initializeFromBakedPayload()
2203
+ applyViewerModeMeta()
2055
2204
  renderResults()
2056
2205
  </script>
2057
2206
  </body>