@qiaolei81/copilot-session-viewer 0.2.0 → 0.2.1

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-be-responsive-on-mobile-viewport-1771605454041.json +0 -435
  4. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-display-sessions-if-available-1771605462872.json +0 -435
  5. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-handle-JavaScript-errors-gracefully-1771605463381.json +0 -435
  6. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-handle-session-import-dialog-1771605466264.json +0 -435
  7. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-have-working-infinite-scroll-elements-1771605454038.json +0 -435
  8. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-load-homepage-with-basic-elements-1771605454001.json +0 -435
  9. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-load-time-analysis-page-1771605464990.json +0 -1236
  10. package/.nyc_output/coverage-core-functionality-spec-js-Core-Functionality-Tests-should-navigate-to-session-detail-page-1771605472595.json +0 -1177
  11. package/.nyc_output/coverage-e2e-merged.json +0 -1
  12. package/.nyc_output/coverage-homepage-spec-js-Homepage-should-display-session-list-1771605453565.json +0 -435
  13. package/.nyc_output/coverage-homepage-spec-js-Homepage-should-load-homepage-successfully-1771605453552.json +0 -435
  14. package/.nyc_output/coverage-homepage-spec-js-Homepage-should-navigate-to-session-detail-on-click-1771605469317.json +0 -1134
  15. package/.nyc_output/coverage-homepage-spec-js-Homepage-should-show-session-metadata-1771605460581.json +0 -435
  16. package/.nyc_output/coverage-infinite-scroll-spec-js-Infinite-Scroll-should-display-Load-More-Sessions-button-when-there-are-more-sessions-1771605468486.json +0 -435
  17. package/.nyc_output/coverage-infinite-scroll-spec-js-Infinite-Scroll-should-handle-API-errors-gracefully-during-infinite-scroll-1771605482161.json +0 -471
  18. package/.nyc_output/coverage-infinite-scroll-spec-js-Infinite-Scroll-should-hide-Load-More-button-when-no-more-sessions-available-1771605478370.json +0 -471
  19. package/.nyc_output/coverage-infinite-scroll-spec-js-Infinite-Scroll-should-load-additional-sessions-when-Load-More-button-is-clicked-1771605475059.json +0 -471
  20. package/.nyc_output/coverage-infinite-scroll-spec-js-Infinite-Scroll-should-preserve-session-list-state-during-navigation-1771605494575.json +0 -1633
  21. package/.nyc_output/coverage-infinite-scroll-spec-js-Infinite-Scroll-should-show-loading-state-when-Load-More-button-is-clicked-1771605475401.json +0 -471
  22. package/.nyc_output/coverage-infinite-scroll-spec-js-Infinite-Scroll-should-trigger-infinite-scroll-when-scrolling-near-bottom-1771605476949.json +0 -471
  23. package/.nyc_output/coverage-session-detail-spec-js-Session-Detail-Page-should-clear-search-filter-1771605508542.json +0 -1255
  24. package/.nyc_output/coverage-session-detail-spec-js-Session-Detail-Page-should-display-event-list-1771605505572.json +0 -1156
  25. package/.nyc_output/coverage-session-detail-spec-js-Session-Detail-Page-should-display-session-metadata-1771605504552.json +0 -701
  26. package/.nyc_output/coverage-session-detail-spec-js-Session-Detail-Page-should-expand-and-collapse-tool-details-1771605515809.json +0 -1182
  27. package/.nyc_output/coverage-session-detail-spec-js-Session-Detail-Page-should-filter-events-by-search-1771605513421.json +0 -1245
  28. package/.nyc_output/coverage-session-detail-spec-js-Session-Detail-Page-should-load-session-detail-page-1771605494974.json +0 -701
  29. package/.nyc_output/coverage-session-detail-spec-js-Session-Detail-Page-should-toggle-content-visibility-1771605550729.json +0 -1177
  30. package/.nyc_output/coverage-unit.json +0 -21
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.1] - 2026-02-25
9
+
10
+ ### Changed
11
+ - `.nyc_output/` and `coverage/` directories now properly ignored in `.gitignore`
12
+
13
+ ### Removed
14
+ - NYC coverage intermediate files (`.nyc_output/`, 1.6 MB) from repository
15
+ - Removed 28 coverage data files that should not be committed
16
+
17
+ ### Docs
18
+ - Translated `lib/parsers/README.md` from Chinese to English for international contributors
19
+
8
20
  ## [0.2.0] - 2026-02-25
9
21
 
10
22
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qiaolei81/copilot-session-viewer",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Web UI for viewing GitHub Copilot CLI session logs",
5
5
  "author": "Lei Qiao <qiaolei81@gmail.com>",
6
6
  "license": "MIT",
@@ -1,435 +0,0 @@
1
- [
2
- {
3
- "url": "http://localhost:3838/",
4
- "scriptId": "4",
5
- "source": "\n // Parse JSON data safely from script tags\n const initialSessions = JSON.parse(document.getElementById('sessions-data').textContent);\n const metaData = JSON.parse(document.getElementById('meta-data').textContent);\n const totalSessionsFromServer = metaData.totalSessions;\n const hasMoreFromServer = metaData.hasMore;\n\n // Infinite scroll state\n let allSessions = [...initialSessions];\n let currentOffset = initialSessions.length;\n let hasMore = hasMoreFromServer;\n let isLoading = false;\n\n // Filter state\n let currentSourceFilter = 'all';\n // Load more sessions\n async function loadMoreSessions() {\n if (isLoading || !hasMore) return;\n\n isLoading = true;\n const loadMoreBtn = document.getElementById('load-more-btn');\n const loadingIndicator = document.getElementById('loading-indicator');\n const loadMoreSection = document.getElementById('load-more-section');\n\n // Show loading, hide button\n loadMoreSection.style.display = 'none';\n loadingIndicator.style.display = 'block';\n\n try {\n const response = await fetch(`/api/sessions/load-more?offset=${currentOffset}&limit=20`);\n if (!response.ok) {\n throw new Error('Failed to load more sessions');\n }\n\n const data = await response.json();\n allSessions.push(...data.sessions);\n currentOffset += data.sessions.length;\n hasMore = data.hasMore;\n\n // Re-render all sessions\n renderAllSessions();\n updateLoadMoreButton();\n\n } catch (err) {\n console.error('Error loading more sessions:', err);\n // Show button again on error\n loadMoreSection.style.display = hasMore ? 'block' : 'none';\n } finally {\n isLoading = false;\n loadingIndicator.style.display = 'none';\n }\n }\n\n // Update load more button visibility\n function updateLoadMoreButton() {\n const loadMoreSection = document.getElementById('load-more-section');\n loadMoreSection.style.display = hasMore && !isLoading ? 'block' : 'none';\n }\n\n // Get filtered sessions based on current filter\n function getFilteredSessions() {\n if (currentSourceFilter === 'all') {\n return allSessions;\n }\n return allSessions.filter(session => session.source === currentSourceFilter);\n }\n\n // Render all sessions (grouped by date)\n function renderAllSessions() {\n const container = document.getElementById('sessions-container');\n container.innerHTML = ''; // Clear existing\n\n const filteredSessions = getFilteredSessions();\n\n if (filteredSessions.length === 0) {\n container.innerHTML = '<div style=\"text-align: center; color: #6e7681; padding: 40px; font-size: 14px;\">No sessions found for this filter.</div>';\n return;\n }\n\n const grouped = groupSessionsByDate(filteredSessions);\n const sortedDates = Object.keys(grouped).sort((a, b) => b.localeCompare(a)); // Descending\n\n sortedDates.forEach(dateKey => {\n const dateHeader = document.createElement('div');\n dateHeader.className = 'date-group-header';\n dateHeader.textContent = formatDateHeader(grouped[dateKey][0].createdAt);\n container.appendChild(dateHeader);\n\n const grid = document.createElement('div');\n grid.className = 'recent-list';\n grouped[dateKey].forEach(session => {\n grid.innerHTML += renderSessionCard(session);\n });\n container.appendChild(grid);\n });\n }\n\n // Check if user has scrolled near bottom\n function checkScrollPosition() {\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const windowHeight = window.innerHeight;\n const docHeight = document.documentElement.scrollHeight;\n\n // Load more when user is within 500px of bottom\n if (scrollTop + windowHeight >= docHeight - 500 && hasMore && !isLoading) {\n loadMoreSessions();\n }\n }\n\n // Throttle scroll events for performance\n let scrollTimeout;\n function throttledScroll() {\n if (scrollTimeout) return;\n scrollTimeout = setTimeout(() => {\n checkScrollPosition();\n scrollTimeout = null;\n }, 100);\n }\n\n function viewSession(e) {\n e.preventDefault();\n const sessionId = document.getElementById('sessionInput').value.trim();\n if (sessionId) {\n window.location.href = `/session/${sessionId}`;\n }\n }\n\n // File import handling\n const fileInput = document.getElementById('fileInput');\n const importLink = document.getElementById('importLink');\n const importStatus = document.getElementById('importStatus');\n \n // Click import link to select file\n importLink.addEventListener('click', (e) => {\n e.preventDefault();\n fileInput.click();\n });\n \n // Auto-upload when file is selected\n fileInput.addEventListener('change', async (e) => {\n const file = e.target.files[0];\n if (!file) return;\n \n if (!file.name.endsWith('.zip')) {\n showStatus('error', '❌ Please select a .zip file');\n return;\n }\n \n importLink.style.pointerEvents = 'none';\n importLink.style.opacity = '0.5';\n importLink.textContent = 'Importing...';\n showStatus('loading', 'Uploading and extracting session...');\n \n try {\n const formData = new FormData();\n formData.append('sessionZip', file);\n \n const response = await fetch('/session/import', {\n method: 'POST',\n body: formData\n });\n \n const result = await response.json();\n \n if (response.ok) {\n showStatus('success', `✅ Session ${result.sessionId} imported successfully!`);\n \n // Reload page after 1.5 seconds to show new session\n setTimeout(() => {\n window.location.reload();\n }, 1500);\n } else {\n showStatus('error', `❌ Import failed: ${result.error}`);\n importLink.style.pointerEvents = 'auto';\n importLink.style.opacity = '1';\n importLink.textContent = 'Import session from zip';\n }\n } catch (err) {\n showStatus('error', `❌ Import failed: ${err.message}`);\n importLink.style.pointerEvents = 'auto';\n importLink.style.opacity = '1';\n importLink.textContent = 'Import session from zip';\n } finally {\n // Reset file input\n fileInput.value = '';\n }\n });\n \n function showStatus(type, message) {\n importStatus.className = `import-status ${type}`;\n importStatus.textContent = message;\n }\n\n // Format duration from milliseconds to human-readable format\n function formatDuration(ms) {\n if (!ms || ms < 0) return '—';\n \n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n \n if (hours > 0) {\n const remainingMinutes = minutes % 60;\n return `${hours}h ${remainingMinutes}m`;\n } else if (minutes > 0) {\n const remainingSeconds = seconds % 60;\n return `${minutes}m ${remainingSeconds}s`;\n } else {\n return `${seconds}s`;\n }\n }\n \n // Format date to YYYY/MM/DD\n function formatDateHeader(dateStr) {\n const date = new Date(dateStr);\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}/${month}/${day}`;\n }\n \n // Get date key from timestamp (YYYY-MM-DD for grouping)\n function getDateKey(timestamp) {\n if (!timestamp) return 'Unknown';\n const date = new Date(timestamp);\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n }\n \n // Group sessions by date\n function groupSessionsByDate(sessions) {\n const groups = {};\n sessions.forEach(session => {\n const dateKey = getDateKey(session.createdAt);\n if (!groups[dateKey]) {\n groups[dateKey] = [];\n }\n groups[dateKey].push(session);\n });\n return groups;\n }\n \n // Render session card HTML\n function renderSessionCard(session) {\n // Add status badges\n let badges = '';\n\n // Add source badge (at the beginning)\n const sourceClass = session.source === 'claude' ? 'source-claude' : 'source-copilot';\n const sourceLabel = session.source === 'claude' ? 'Claude' : 'Copilot';\n badges += `<span class=\"status-badge ${sourceClass}\" title=\"${sourceLabel} CLI\">${sourceLabel}</span>`;\n\n if (session.sessionStatus === 'wip') {\n badges += '<span class=\"status-badge wip\" title=\"Session in progress\">🔄 WIP</span>';\n }\n if (session.isImported) {\n badges += '<span class=\"status-badge imported\" title=\"Imported session\">📥</span>';\n }\n if (session.hasInsight) {\n badges += '<span class=\"status-badge insight\" title=\"Has Copilot Insight\">💡</span>';\n }\n // Add model and version badges\n if (session.selectedModel) {\n const modelShort = session.selectedModel.replace('claude-', '').replace('gpt-', '').replace('gemini-', '');\n let modelClass = 'model-other';\n if (session.selectedModel.includes('claude')) {\n modelClass = 'model-claude';\n } else if (session.selectedModel.includes('gpt')) {\n modelClass = 'model-gpt';\n } else if (session.selectedModel.includes('gemini')) {\n modelClass = 'model-gemini';\n }\n badges += `<span class=\"status-badge model ${modelClass}\" title=\"Model: ${escapeHtml(session.selectedModel)}\">${escapeHtml(modelShort)}</span>`;\n }\n if (session.copilotVersion) {\n badges += `<span class=\"status-badge version\" title=\"Copilot CLI version\">${escapeHtml(session.copilotVersion)}</span>`;\n }\n \n let summaryHtml = '';\n if (session.summary && session.summary !== 'No summary' && session.summary !== 'Legacy session') {\n summaryHtml = `<div class=\"session-summary\">${escapeHtml(session.summary)}</div>`;\n } else {\n summaryHtml = '<div class=\"session-summary\" style=\"color: #6e7681; font-style: italic;\">No summary available</div>';\n }\n \n let workspaceHtml = '';\n if (session.workspace && session.workspace.cwd) {\n workspaceHtml = `\n <div class=\"session-info-item workspace\" title=\"${escapeHtml(session.workspace.cwd)}\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M1.75 1A1.75 1.75 0 000 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0016 13.25v-8.5A1.75 1.75 0 0014.25 3H7.5a.25.25 0 01-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75z\"></path></svg>\n <span class=\"session-info-value\">${escapeHtml(session.workspace.cwd)}</span>\n </div>\n `;\n }\n \n const createdAtStr = session.createdAt \n ? new Date(session.createdAt).toLocaleString('en-US', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false })\n : 'unknown';\n \n let durationHtml = '';\n if (session.duration) {\n durationHtml = `\n <div class=\"session-info-item\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM7.25 12.5v-5A.75.75 0 0 1 8 6.75h2.5a.75.75 0 0 1 0 1.5H8.75v4.25a.75.75 0 0 1-1.5 0Z\"></path></svg>\n <span class=\"session-info-value\">${formatDuration(session.duration)}</span>\n </div>\n `;\n }\n \n const wipClass = session.sessionStatus === 'wip' ? ' recent-item-wip' : '';\n\n return `\n <a href=\"/session/${session.id}\" class=\"recent-item${wipClass}\">\n <div class=\"session-id\">\n <span class=\"session-id-text\" title=\"${escapeHtml(session.id)}\">${escapeHtml(session.id)}</span>\n <div class=\"session-badges\">${badges}</div>\n </div>\n ${summaryHtml}\n <div class=\"session-divider\"></div>\n <div class=\"session-info\">\n ${workspaceHtml}\n <div class=\"session-info-item\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z\"></path></svg>\n <span class=\"session-info-value\">${createdAtStr}</span>\n </div>\n ${durationHtml}\n <div class=\"session-info-item\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M7.72.72a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 0 1-1.06 1.06l-.22-.22v1.69a.75.75 0 0 1-1.5 0V3.06l-.22.22a.75.75 0 0 1-1.06-1.06ZM2 7a.75.75 0 0 0 0 1.5h3.69l-.22.22a.75.75 0 1 0 1.06 1.06l1.5-1.5a.75.75 0 0 0 0-1.06l-1.5-1.5a.75.75 0 0 0-1.06 1.06l.22.22Zm8.53-.28a.75.75 0 0 0 0 1.06l1.5 1.5a.75.75 0 1 0 1.06-1.06l-.22-.22H16a.75.75 0 0 0 0-1.5h-3.13l.22-.22a.75.75 0 0 0-1.06-1.06ZM7.72 12.22a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 1 1-1.06 1.06l-.22-.22v1.69a.75.75 0 0 1-1.5 0v-1.69l-.22.22a.75.75 0 0 1-1.06-1.06Z\"></path></svg>\n <span class=\"session-info-value\">${session.eventCount || 0} events</span>\n </div>\n </div>\n </a>\n `;\n }\n \n function escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n // Filter pill click handler\n function setupFilterPills() {\n const filterPills = document.querySelectorAll('.filter-pill');\n filterPills.forEach(pill => {\n pill.addEventListener('click', () => {\n // Remove active class from all pills\n filterPills.forEach(p => p.classList.remove('active'));\n // Add active class to clicked pill\n pill.classList.add('active');\n\n // Update filter state\n currentSourceFilter = pill.getAttribute('data-source');\n\n // Re-render sessions with filter\n renderAllSessions();\n });\n });\n }\n\n // Render grouped sessions\n document.addEventListener('DOMContentLoaded', function() {\n // Initial render\n renderAllSessions();\n updateLoadMoreButton();\n\n // Add scroll listener for infinite scroll\n window.addEventListener('scroll', throttledScroll);\n\n // Add click listener for load more button\n document.getElementById('load-more-btn').addEventListener('click', loadMoreSessions);\n\n // Setup filter pills\n setupFilterPills();\n });\n ",
6
- "functions": [
7
- {
8
- "functionName": "",
9
- "isBlockCoverage": true,
10
- "ranges": [
11
- {
12
- "startOffset": 0,
13
- "endOffset": 14748,
14
- "count": 1
15
- }
16
- ]
17
- },
18
- {
19
- "functionName": "loadMoreSessions",
20
- "isBlockCoverage": false,
21
- "ranges": [
22
- {
23
- "startOffset": 607,
24
- "endOffset": 1828,
25
- "count": 0
26
- }
27
- ]
28
- },
29
- {
30
- "functionName": "updateLoadMoreButton",
31
- "isBlockCoverage": true,
32
- "ranges": [
33
- {
34
- "startOffset": 1876,
35
- "endOffset": 2071,
36
- "count": 1
37
- },
38
- {
39
- "startOffset": 2056,
40
- "endOffset": 2064,
41
- "count": 0
42
- }
43
- ]
44
- },
45
- {
46
- "functionName": "getFilteredSessions",
47
- "isBlockCoverage": true,
48
- "ranges": [
49
- {
50
- "startOffset": 2130,
51
- "endOffset": 2331,
52
- "count": 1
53
- },
54
- {
55
- "startOffset": 2241,
56
- "endOffset": 2330,
57
- "count": 0
58
- }
59
- ]
60
- },
61
- {
62
- "functionName": "",
63
- "isBlockCoverage": false,
64
- "ranges": [
65
- {
66
- "startOffset": 2274,
67
- "endOffset": 2323,
68
- "count": 0
69
- }
70
- ]
71
- },
72
- {
73
- "functionName": "renderAllSessions",
74
- "isBlockCoverage": true,
75
- "ranges": [
76
- {
77
- "startOffset": 2382,
78
- "endOffset": 3505,
79
- "count": 1
80
- },
81
- {
82
- "startOffset": 2631,
83
- "endOffset": 2811,
84
- "count": 0
85
- }
86
- ]
87
- },
88
- {
89
- "functionName": "",
90
- "isBlockCoverage": true,
91
- "ranges": [
92
- {
93
- "startOffset": 2926,
94
- "endOffset": 2954,
95
- "count": 2
96
- }
97
- ]
98
- },
99
- {
100
- "functionName": "",
101
- "isBlockCoverage": true,
102
- "ranges": [
103
- {
104
- "startOffset": 2998,
105
- "endOffset": 3497,
106
- "count": 3
107
- }
108
- ]
109
- },
110
- {
111
- "functionName": "",
112
- "isBlockCoverage": true,
113
- "ranges": [
114
- {
115
- "startOffset": 3372,
116
- "endOffset": 3450,
117
- "count": 20
118
- }
119
- ]
120
- },
121
- {
122
- "functionName": "checkScrollPosition",
123
- "isBlockCoverage": false,
124
- "ranges": [
125
- {
126
- "startOffset": 3557,
127
- "endOffset": 3961,
128
- "count": 0
129
- }
130
- ]
131
- },
132
- {
133
- "functionName": "throttledScroll",
134
- "isBlockCoverage": false,
135
- "ranges": [
136
- {
137
- "startOffset": 4036,
138
- "endOffset": 4220,
139
- "count": 0
140
- }
141
- ]
142
- },
143
- {
144
- "functionName": "viewSession",
145
- "isBlockCoverage": false,
146
- "ranges": [
147
- {
148
- "startOffset": 4226,
149
- "endOffset": 4448,
150
- "count": 0
151
- }
152
- ]
153
- },
154
- {
155
- "functionName": "",
156
- "isBlockCoverage": false,
157
- "ranges": [
158
- {
159
- "startOffset": 4752,
160
- "endOffset": 4817,
161
- "count": 0
162
- }
163
- ]
164
- },
165
- {
166
- "functionName": "",
167
- "isBlockCoverage": false,
168
- "ranges": [
169
- {
170
- "startOffset": 4907,
171
- "endOffset": 6475,
172
- "count": 0
173
- }
174
- ]
175
- },
176
- {
177
- "functionName": "showStatus",
178
- "isBlockCoverage": false,
179
- "ranges": [
180
- {
181
- "startOffset": 6487,
182
- "endOffset": 6627,
183
- "count": 0
184
- }
185
- ]
186
- },
187
- {
188
- "functionName": "formatDuration",
189
- "isBlockCoverage": true,
190
- "ranges": [
191
- {
192
- "startOffset": 6699,
193
- "endOffset": 7226,
194
- "count": 4
195
- },
196
- {
197
- "startOffset": 6754,
198
- "endOffset": 6765,
199
- "count": 0
200
- },
201
- {
202
- "startOffset": 6940,
203
- "endOffset": 7045,
204
- "count": 0
205
- },
206
- {
207
- "startOffset": 7068,
208
- "endOffset": 7220,
209
- "count": 2
210
- }
211
- ]
212
- },
213
- {
214
- "functionName": "formatDateHeader",
215
- "isBlockCoverage": true,
216
- "ranges": [
217
- {
218
- "startOffset": 7269,
219
- "endOffset": 7553,
220
- "count": 3
221
- }
222
- ]
223
- },
224
- {
225
- "functionName": "getDateKey",
226
- "isBlockCoverage": true,
227
- "ranges": [
228
- {
229
- "startOffset": 7624,
230
- "endOffset": 7946,
231
- "count": 20
232
- },
233
- {
234
- "startOffset": 7679,
235
- "endOffset": 7696,
236
- "count": 0
237
- }
238
- ]
239
- },
240
- {
241
- "functionName": "groupSessionsByDate",
242
- "isBlockCoverage": true,
243
- "ranges": [
244
- {
245
- "startOffset": 7986,
246
- "endOffset": 8292,
247
- "count": 1
248
- }
249
- ]
250
- },
251
- {
252
- "functionName": "",
253
- "isBlockCoverage": true,
254
- "ranges": [
255
- {
256
- "startOffset": 8075,
257
- "endOffset": 8263,
258
- "count": 20
259
- },
260
- {
261
- "startOffset": 8173,
262
- "endOffset": 8216,
263
- "count": 3
264
- }
265
- ]
266
- },
267
- {
268
- "functionName": "renderSessionCard",
269
- "isBlockCoverage": true,
270
- "ranges": [
271
- {
272
- "startOffset": 8334,
273
- "endOffset": 13482,
274
- "count": 20
275
- },
276
- {
277
- "startOffset": 8522,
278
- "endOffset": 8539,
279
- "count": 16
280
- },
281
- {
282
- "startOffset": 8540,
283
- "endOffset": 8558,
284
- "count": 4
285
- },
286
- {
287
- "startOffset": 8614,
288
- "endOffset": 8624,
289
- "count": 16
290
- },
291
- {
292
- "startOffset": 8625,
293
- "endOffset": 8636,
294
- "count": 4
295
- },
296
- {
297
- "startOffset": 8792,
298
- "endOffset": 8895,
299
- "count": 0
300
- },
301
- {
302
- "startOffset": 8926,
303
- "endOffset": 9027,
304
- "count": 0
305
- },
306
- {
307
- "startOffset": 9058,
308
- "endOffset": 9161,
309
- "count": 0
310
- },
311
- {
312
- "startOffset": 9233,
313
- "endOffset": 9854,
314
- "count": 14
315
- },
316
- {
317
- "startOffset": 9445,
318
- "endOffset": 9495,
319
- "count": 9
320
- },
321
- {
322
- "startOffset": 9495,
323
- "endOffset": 9693,
324
- "count": 5
325
- },
326
- {
327
- "startOffset": 9544,
328
- "endOffset": 9591,
329
- "count": 0
330
- },
331
- {
332
- "startOffset": 9643,
333
- "endOffset": 9693,
334
- "count": 0
335
- },
336
- {
337
- "startOffset": 9889,
338
- "endOffset": 10027,
339
- "count": 16
340
- },
341
- {
342
- "startOffset": 10166,
343
- "endOffset": 10266,
344
- "count": 19
345
- },
346
- {
347
- "startOffset": 10266,
348
- "endOffset": 10406,
349
- "count": 1
350
- },
351
- {
352
- "startOffset": 10498,
353
- "endOffset": 10998,
354
- "count": 16
355
- },
356
- {
357
- "startOffset": 11211,
358
- "endOffset": 11222,
359
- "count": 0
360
- },
361
- {
362
- "startOffset": 11288,
363
- "endOffset": 11727,
364
- "count": 4
365
- },
366
- {
367
- "startOffset": 11790,
368
- "endOffset": 11810,
369
- "count": 0
370
- },
371
- {
372
- "startOffset": 13399,
373
- "endOffset": 13403,
374
- "count": 0
375
- }
376
- ]
377
- },
378
- {
379
- "functionName": "escapeHtml",
380
- "isBlockCoverage": true,
381
- "ranges": [
382
- {
383
- "startOffset": 13492,
384
- "endOffset": 13632,
385
- "count": 135
386
- }
387
- ]
388
- },
389
- {
390
- "functionName": "setupFilterPills",
391
- "isBlockCoverage": true,
392
- "ranges": [
393
- {
394
- "startOffset": 13671,
395
- "endOffset": 14256,
396
- "count": 1
397
- }
398
- ]
399
- },
400
- {
401
- "functionName": "",
402
- "isBlockCoverage": true,
403
- "ranges": [
404
- {
405
- "startOffset": 13796,
406
- "endOffset": 14248,
407
- "count": 3
408
- }
409
- ]
410
- },
411
- {
412
- "functionName": "",
413
- "isBlockCoverage": false,
414
- "ranges": [
415
- {
416
- "startOffset": 13845,
417
- "endOffset": 14238,
418
- "count": 0
419
- }
420
- ]
421
- },
422
- {
423
- "functionName": "",
424
- "isBlockCoverage": true,
425
- "ranges": [
426
- {
427
- "startOffset": 14339,
428
- "endOffset": 14743,
429
- "count": 1
430
- }
431
- ]
432
- }
433
- ]
434
- }
435
- ]