elspais 0.9.3__py3-none-any.whl → 0.11.0__py3-none-any.whl

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 (73) hide show
  1. elspais/cli.py +99 -1
  2. elspais/commands/hash_cmd.py +72 -26
  3. elspais/commands/reformat_cmd.py +458 -0
  4. elspais/commands/trace.py +157 -3
  5. elspais/commands/validate.py +44 -16
  6. elspais/core/models.py +2 -0
  7. elspais/core/parser.py +68 -24
  8. elspais/reformat/__init__.py +50 -0
  9. elspais/reformat/detector.py +119 -0
  10. elspais/reformat/hierarchy.py +246 -0
  11. elspais/reformat/line_breaks.py +220 -0
  12. elspais/reformat/prompts.py +123 -0
  13. elspais/reformat/transformer.py +264 -0
  14. elspais/sponsors/__init__.py +432 -0
  15. elspais/trace_view/__init__.py +54 -0
  16. elspais/trace_view/coverage.py +183 -0
  17. elspais/trace_view/generators/__init__.py +12 -0
  18. elspais/trace_view/generators/base.py +329 -0
  19. elspais/trace_view/generators/csv.py +122 -0
  20. elspais/trace_view/generators/markdown.py +175 -0
  21. elspais/trace_view/html/__init__.py +31 -0
  22. elspais/trace_view/html/generator.py +1006 -0
  23. elspais/trace_view/html/templates/base.html +283 -0
  24. elspais/trace_view/html/templates/components/code_viewer_modal.html +14 -0
  25. elspais/trace_view/html/templates/components/file_picker_modal.html +20 -0
  26. elspais/trace_view/html/templates/components/legend_modal.html +69 -0
  27. elspais/trace_view/html/templates/components/review_panel.html +118 -0
  28. elspais/trace_view/html/templates/partials/review/help/help-panel.json +244 -0
  29. elspais/trace_view/html/templates/partials/review/help/onboarding.json +77 -0
  30. elspais/trace_view/html/templates/partials/review/help/tooltips.json +237 -0
  31. elspais/trace_view/html/templates/partials/review/review-comments.js +928 -0
  32. elspais/trace_view/html/templates/partials/review/review-data.js +961 -0
  33. elspais/trace_view/html/templates/partials/review/review-help.js +679 -0
  34. elspais/trace_view/html/templates/partials/review/review-init.js +177 -0
  35. elspais/trace_view/html/templates/partials/review/review-line-numbers.js +429 -0
  36. elspais/trace_view/html/templates/partials/review/review-packages.js +1029 -0
  37. elspais/trace_view/html/templates/partials/review/review-position.js +540 -0
  38. elspais/trace_view/html/templates/partials/review/review-resize.js +115 -0
  39. elspais/trace_view/html/templates/partials/review/review-status.js +659 -0
  40. elspais/trace_view/html/templates/partials/review/review-sync.js +992 -0
  41. elspais/trace_view/html/templates/partials/review-styles.css +2238 -0
  42. elspais/trace_view/html/templates/partials/scripts.js +1741 -0
  43. elspais/trace_view/html/templates/partials/styles.css +1756 -0
  44. elspais/trace_view/models.py +353 -0
  45. elspais/trace_view/review/__init__.py +60 -0
  46. elspais/trace_view/review/branches.py +1149 -0
  47. elspais/trace_view/review/models.py +1205 -0
  48. elspais/trace_view/review/position.py +609 -0
  49. elspais/trace_view/review/server.py +1056 -0
  50. elspais/trace_view/review/status.py +470 -0
  51. elspais/trace_view/review/storage.py +1367 -0
  52. elspais/trace_view/scanning.py +213 -0
  53. elspais/trace_view/specs/README.md +84 -0
  54. elspais/trace_view/specs/tv-d00001-template-architecture.md +36 -0
  55. elspais/trace_view/specs/tv-d00002-css-extraction.md +37 -0
  56. elspais/trace_view/specs/tv-d00003-js-extraction.md +43 -0
  57. elspais/trace_view/specs/tv-d00004-build-embedding.md +40 -0
  58. elspais/trace_view/specs/tv-d00005-test-format.md +78 -0
  59. elspais/trace_view/specs/tv-d00010-review-data-models.md +33 -0
  60. elspais/trace_view/specs/tv-d00011-review-storage.md +33 -0
  61. elspais/trace_view/specs/tv-d00012-position-resolution.md +33 -0
  62. elspais/trace_view/specs/tv-d00013-git-branches.md +31 -0
  63. elspais/trace_view/specs/tv-d00014-review-api-server.md +31 -0
  64. elspais/trace_view/specs/tv-d00015-status-modifier.md +27 -0
  65. elspais/trace_view/specs/tv-d00016-js-integration.md +33 -0
  66. elspais/trace_view/specs/tv-p00001-html-generator.md +33 -0
  67. elspais/trace_view/specs/tv-p00002-review-system.md +29 -0
  68. {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/METADATA +33 -18
  69. elspais-0.11.0.dist-info/RECORD +101 -0
  70. elspais-0.9.3.dist-info/RECORD +0 -40
  71. {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/WHEEL +0 -0
  72. {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/entry_points.txt +0 -0
  73. {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * TraceView Review Init Module
3
+ * Orchestrates review mode initialization and provides global functions.
4
+ * IMPLEMENTS REQUIREMENTS: REQ-d00092
5
+ */
6
+ window.TraceView = window.TraceView || {};
7
+ TraceView.review = TraceView.review || {};
8
+ window.ReviewSystem = window.ReviewSystem || {};
9
+ var RS = window.ReviewSystem;
10
+
11
+ (function(review, RS) {
12
+ 'use strict';
13
+
14
+ let reviewModeActive = false;
15
+ let selectedCardElement = null;
16
+
17
+ /**
18
+ * Toggle review mode on/off
19
+ * @param {boolean} enabled - Optional: explicitly set mode state
20
+ * @returns {boolean} Current review mode state
21
+ */
22
+ function toggleReviewMode(enabled) {
23
+ if (enabled === undefined) {
24
+ reviewModeActive = !reviewModeActive;
25
+ } else {
26
+ reviewModeActive = !!enabled;
27
+ }
28
+
29
+ const column = document.getElementById('review-column');
30
+ const btn = document.getElementById('btnReviewMode');
31
+ const packagesPanel = document.getElementById('reviewPackagesPanel');
32
+
33
+ // Use classList.toggle for body class (REQ-d00092-G)
34
+ document.body.classList.toggle('review-mode-active', reviewModeActive);
35
+
36
+ if (reviewModeActive) {
37
+ if (column) column.classList.remove('hidden');
38
+ if (btn) btn.classList.add('active');
39
+ if (packagesPanel) packagesPanel.style.display = 'block';
40
+
41
+ // Initialize if first activation
42
+ if (review.init && !review._initialized) {
43
+ review.init();
44
+ review._initialized = true;
45
+ }
46
+
47
+ // If there are already open REQ cards, apply interactive line numbers to all of them
48
+ if (window.TraceView && window.TraceView.state && window.TraceView.state.reqCardStack) {
49
+ const openCards = window.TraceView.state.reqCardStack;
50
+
51
+ // Apply line numbers to ALL open cards
52
+ openCards.forEach(cardReqId => {
53
+ const cardElement = document.getElementById(`req-card-${cardReqId}`);
54
+ if (cardElement && review.applyLineNumbersToCard) {
55
+ review.applyLineNumbersToCard(cardElement, cardReqId);
56
+ }
57
+ });
58
+
59
+ // Select the most recently opened card (first in stack) for review
60
+ if (openCards.length > 0) {
61
+ const reqId = openCards[0];
62
+ const req = window.REQ_CONTENT_DATA ? window.REQ_CONTENT_DATA[reqId] : null;
63
+ document.dispatchEvent(new CustomEvent('traceview:req-selected', {
64
+ detail: { reqId, req }
65
+ }));
66
+ }
67
+ }
68
+ } else {
69
+ if (column) column.classList.add('hidden');
70
+ if (btn) btn.classList.remove('active');
71
+ if (packagesPanel) packagesPanel.style.display = 'none';
72
+
73
+ // Clear selection when deactivating (REQ-d00092-G)
74
+ clearCurrentSelection();
75
+ }
76
+
77
+ // Dispatch event for review mode change (REQ-d00092-E)
78
+ document.dispatchEvent(new CustomEvent('rs:review-mode-changed', {
79
+ detail: { active: reviewModeActive }
80
+ }));
81
+
82
+ return reviewModeActive;
83
+ }
84
+
85
+ /**
86
+ * Clear current selection (card and line selections)
87
+ */
88
+ function clearCurrentSelection() {
89
+ if (selectedCardElement) {
90
+ selectedCardElement.classList.remove('rs-selected');
91
+ // Clear line selections using RS namespace
92
+ if (RS.clearAllLineSelections) {
93
+ RS.clearAllLineSelections();
94
+ }
95
+ selectedCardElement = null;
96
+ }
97
+ review.selectedReqId = null;
98
+ }
99
+
100
+ /**
101
+ * Apply line numbers to the currently selected requirement card
102
+ * @param {HTMLElement} cardElement - The card element
103
+ * @param {string} reqId - Requirement ID
104
+ */
105
+ function applyLineNumbersToReqCard(cardElement, reqId) {
106
+ // Use RS namespace to apply line numbers (REQ-d00092)
107
+ if (RS.applyLineNumbersToCard) {
108
+ RS.applyLineNumbersToCard(cardElement, reqId);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Select a requirement for review
114
+ * @param {string} reqId - Requirement ID to select
115
+ */
116
+ function selectReqForReview(reqId) {
117
+ if (!reviewModeActive) {
118
+ toggleReviewMode(true);
119
+ }
120
+
121
+ // Clear previous selection
122
+ clearCurrentSelection();
123
+
124
+ review.selectedReqId = reqId;
125
+ const req = window.REQ_CONTENT_DATA ? window.REQ_CONTENT_DATA[reqId] : null;
126
+
127
+ // Find and mark the card element
128
+ const cardElement = document.querySelector(`[data-req-id="${reqId}"]`);
129
+ if (cardElement) {
130
+ selectedCardElement = cardElement;
131
+ cardElement.classList.add('rs-selected');
132
+
133
+ // Apply line numbers to the selected card (REQ-d00092-E)
134
+ applyLineNumbersToReqCard(cardElement, reqId);
135
+ }
136
+
137
+ document.dispatchEvent(new CustomEvent('traceview:req-selected', {
138
+ detail: { reqId, req }
139
+ }));
140
+ }
141
+
142
+ /**
143
+ * Check if review mode is currently active
144
+ * @returns {boolean} True if review mode is active
145
+ */
146
+ function isReviewModeActive() {
147
+ return reviewModeActive;
148
+ }
149
+
150
+ // Auto-initialize on DOMContentLoaded if review mode data exists
151
+ document.addEventListener('DOMContentLoaded', function() {
152
+ if (document.body.getAttribute('data-review-mode') === 'true') {
153
+ if (window.REVIEW_DATA) {
154
+ console.log('Review mode auto-initializing...');
155
+ // Don't auto-enable review mode, just set up the system
156
+ if (review.init && !review._initialized) {
157
+ review.init();
158
+ review._initialized = true;
159
+ }
160
+ }
161
+ }
162
+ });
163
+
164
+ // Export functions to review namespace
165
+ review.toggleReviewMode = toggleReviewMode;
166
+ review.selectReqForReview = selectReqForReview;
167
+ review.isReviewModeActive = isReviewModeActive;
168
+ review.clearCurrentSelection = clearCurrentSelection;
169
+ review.applyLineNumbersToReqCard = applyLineNumbersToReqCard;
170
+
171
+ // Export to ReviewSystem alias (RS pattern)
172
+ RS.toggleReviewMode = toggleReviewMode;
173
+ RS.selectReqForReview = selectReqForReview;
174
+ RS.isReviewModeActive = isReviewModeActive;
175
+ RS.clearCurrentSelection = clearCurrentSelection;
176
+
177
+ })(TraceView.review, RS);
@@ -0,0 +1,429 @@
1
+ /**
2
+ * TraceView Review Line Numbers Module
3
+ * Add line numbers to requirement content for click-to-comment.
4
+ * IMPLEMENTS REQUIREMENTS: REQ-d00092
5
+ */
6
+ window.TraceView = window.TraceView || {};
7
+ TraceView.review = TraceView.review || {};
8
+ window.ReviewSystem = window.ReviewSystem || {};
9
+ var RS = window.ReviewSystem;
10
+
11
+ // Global selection state (REQ-d00092-H)
12
+ window.selectedLineNumber = null;
13
+ window.selectedLineRange = null;
14
+
15
+ (function(review, RS) {
16
+ 'use strict';
17
+
18
+ // Track current selection state
19
+ let currentReqId = null;
20
+ let selectionType = 'general'; // 'line', 'block', or 'general'
21
+
22
+ // Drag selection state
23
+ let isDragging = false;
24
+ let dragStartLine = null;
25
+ let dragContainer = null;
26
+ let dragReqId = null;
27
+
28
+ /**
29
+ * Convert markdown content to line-numbered HTML view
30
+ * @param {string} content - Raw markdown/text content
31
+ * @param {string} reqId - The requirement ID
32
+ * @returns {HTMLElement} Container element with line-numbered content
33
+ */
34
+ function convertToLineNumberedView(content, reqId) {
35
+ const container = document.createElement('div');
36
+ container.className = 'rs-line-numbers-container';
37
+ container.setAttribute('data-req-id', reqId);
38
+
39
+ // Create hint bar (shows click instructions in review mode)
40
+ const hint = document.createElement('div');
41
+ hint.className = 'rs-line-numbers-hint';
42
+ hint.textContent = 'Click or drag to select lines for commenting';
43
+ container.appendChild(hint);
44
+
45
+ // Create table structure for line numbers
46
+ const table = document.createElement('div');
47
+ table.className = 'rs-lines-table';
48
+
49
+ const lines = (content || '').split('\n');
50
+ lines.forEach((line, index) => {
51
+ const row = document.createElement('div');
52
+ row.className = 'rs-line-row';
53
+ row.setAttribute('data-line', index + 1);
54
+ row.style.cursor = 'pointer';
55
+
56
+ // Mousedown handler to start selection/drag
57
+ row.addEventListener('mousedown', function(event) {
58
+ event.preventDefault(); // Prevent text selection
59
+ handleLineMouseDown(event, reqId, index + 1, container);
60
+ });
61
+
62
+ // Mouseover handler for drag selection
63
+ row.addEventListener('mouseover', function(event) {
64
+ if (isDragging && dragContainer === container) {
65
+ handleLineDragOver(reqId, index + 1, container);
66
+ }
67
+ });
68
+
69
+ // Line number
70
+ const lineNum = document.createElement('span');
71
+ lineNum.className = 'rs-line-number';
72
+ lineNum.textContent = index + 1;
73
+
74
+ // Line text
75
+ const lineText = document.createElement('span');
76
+ lineText.className = 'rs-line-text';
77
+ lineText.textContent = line || ' '; // Preserve empty lines
78
+
79
+ row.appendChild(lineNum);
80
+ row.appendChild(lineText);
81
+ table.appendChild(row);
82
+ });
83
+
84
+ container.appendChild(table);
85
+ return container;
86
+ }
87
+
88
+ /**
89
+ * Handle mousedown on a line - start selection or drag
90
+ * @param {MouseEvent} event - Mouse event
91
+ * @param {string} reqId - Requirement ID
92
+ * @param {number} lineNumber - Line number
93
+ * @param {HTMLElement} container - Container element
94
+ */
95
+ function handleLineMouseDown(event, reqId, lineNumber, container) {
96
+ // Start drag tracking
97
+ isDragging = true;
98
+ dragStartLine = lineNumber;
99
+ dragContainer = container;
100
+ dragReqId = reqId;
101
+
102
+ // Select the starting line
103
+ selectSingleLine(reqId, lineNumber, container);
104
+
105
+ // Dispatch initial selection event
106
+ dispatchSelectionEvent(reqId, lineNumber);
107
+ }
108
+
109
+ /**
110
+ * Handle mouseover during drag - extend selection
111
+ * @param {string} reqId - Requirement ID
112
+ * @param {number} lineNumber - Line number being hovered
113
+ * @param {HTMLElement} container - Container element
114
+ */
115
+ function handleLineDragOver(reqId, lineNumber, container) {
116
+ if (!isDragging || dragStartLine === null) return;
117
+
118
+ if (lineNumber !== dragStartLine) {
119
+ // Extend selection to range
120
+ selectLineRange(reqId, dragStartLine, lineNumber, container);
121
+ } else {
122
+ // Back to single line
123
+ selectSingleLine(reqId, lineNumber, container);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Handle mouseup - finalize selection
129
+ */
130
+ function handleLineMouseUp() {
131
+ if (isDragging && dragReqId) {
132
+ // Dispatch final selection event
133
+ dispatchSelectionEvent(dragReqId, window.selectedLineNumber);
134
+ }
135
+
136
+ // Reset drag state
137
+ isDragging = false;
138
+ dragStartLine = null;
139
+ dragContainer = null;
140
+ dragReqId = null;
141
+ }
142
+
143
+ /**
144
+ * Dispatch selection event for line selection
145
+ * @param {string} reqId - Requirement ID
146
+ * @param {number} lineNumber - Primary line number
147
+ */
148
+ function dispatchSelectionEvent(reqId, lineNumber) {
149
+ // Dispatch event for line selection (REQ-d00092-C)
150
+ document.dispatchEvent(new CustomEvent('rs:line-selected', {
151
+ detail: {
152
+ reqId: reqId,
153
+ lineNumber: lineNumber,
154
+ lineRange: window.selectedLineRange,
155
+ selectionType: selectionType
156
+ }
157
+ }));
158
+
159
+ // Also update Review Panel focus to this REQ
160
+ const req = window.REQ_CONTENT_DATA ? window.REQ_CONTENT_DATA[reqId] : null;
161
+ document.dispatchEvent(new CustomEvent('traceview:req-selected', {
162
+ detail: { reqId: reqId, req: req }
163
+ }));
164
+ }
165
+
166
+ /**
167
+ * Handle click on a line (legacy support, also handles shift-click)
168
+ * @param {Event} event - Click event
169
+ * @param {string} reqId - Requirement ID
170
+ * @param {number} lineNumber - Line number clicked
171
+ * @param {HTMLElement} container - Container element
172
+ */
173
+ function handleLineClick(event, reqId, lineNumber, container) {
174
+ if (event.shiftKey && window.selectedLineNumber !== null) {
175
+ // Shift-click: select range
176
+ selectLineRange(reqId, window.selectedLineNumber, lineNumber, container);
177
+ } else {
178
+ // Single click: select single line
179
+ selectSingleLine(reqId, lineNumber, container);
180
+ }
181
+
182
+ dispatchSelectionEvent(reqId, lineNumber);
183
+ }
184
+
185
+ // Global mouseup handler to finalize drag selection
186
+ document.addEventListener('mouseup', handleLineMouseUp);
187
+
188
+ /**
189
+ * Select a single line
190
+ * @param {string} reqId - Requirement ID
191
+ * @param {number} lineNumber - Line number to select
192
+ * @param {HTMLElement} container - Container element (optional)
193
+ */
194
+ function selectSingleLine(reqId, lineNumber, container) {
195
+ currentReqId = reqId;
196
+ window.selectedLineNumber = lineNumber;
197
+ window.selectedLineRange = null;
198
+ selectionType = 'line';
199
+
200
+ // Clear previous selection
201
+ if (container) {
202
+ const prev = container.querySelectorAll('.rs-line-row.selected');
203
+ prev.forEach(el => el.classList.remove('selected'));
204
+
205
+ // Highlight selected line
206
+ const row = container.querySelector(`.rs-line-row[data-line="${lineNumber}"]`);
207
+ if (row) {
208
+ row.classList.add('selected');
209
+ }
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Select a range of lines
215
+ * @param {string} reqId - Requirement ID
216
+ * @param {number} startLine - Start line number
217
+ * @param {number} endLine - End line number
218
+ * @param {HTMLElement} container - Container element (optional)
219
+ */
220
+ function selectLineRange(reqId, startLine, endLine, container) {
221
+ currentReqId = reqId;
222
+ const start = Math.min(startLine, endLine);
223
+ const end = Math.max(startLine, endLine);
224
+ window.selectedLineNumber = start;
225
+ window.selectedLineRange = { start: start, end: end };
226
+ selectionType = 'block';
227
+
228
+ // Clear previous selection
229
+ if (container) {
230
+ const prev = container.querySelectorAll('.rs-line-row.selected');
231
+ prev.forEach(el => el.classList.remove('selected'));
232
+
233
+ // Highlight range
234
+ for (let i = start; i <= end; i++) {
235
+ const row = container.querySelector(`.rs-line-row[data-line="${i}"]`);
236
+ if (row) {
237
+ row.classList.add('selected');
238
+ }
239
+ }
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Apply line numbers to a requirement card
245
+ * @param {HTMLElement} cardElement - The card element containing the requirement
246
+ * @param {string} reqId - The requirement ID
247
+ */
248
+ function applyLineNumbersToCard(cardElement, reqId) {
249
+ if (!cardElement) return;
250
+
251
+ // Find the body section of the requirement card
252
+ const bodySection = cardElement.querySelector('.req-body-section');
253
+ if (!bodySection) return;
254
+
255
+ // Check if already fully processed (has our container with click handlers)
256
+ if (bodySection.querySelector('.rs-line-numbers-container')) {
257
+ return;
258
+ }
259
+
260
+ // Check if line numbers already exist (from scripts.js renderMarkdownWithLines)
261
+ // If so, just add click handlers instead of recreating
262
+ const existingTable = bodySection.querySelector('.rs-lines-table');
263
+ if (existingTable) {
264
+ // Add wrapper container with hint
265
+ const container = document.createElement('div');
266
+ container.className = 'rs-line-numbers-container';
267
+ container.setAttribute('data-req-id', reqId);
268
+
269
+ // Create hint bar
270
+ const hint = document.createElement('div');
271
+ hint.className = 'rs-line-numbers-hint';
272
+ hint.textContent = 'Click or drag to select lines for commenting';
273
+ container.appendChild(hint);
274
+
275
+ // Move existing table into container
276
+ existingTable.parentNode.insertBefore(container, existingTable);
277
+ container.appendChild(existingTable);
278
+
279
+ // Add drag selection handlers to entire rows
280
+ const rows = existingTable.querySelectorAll('.rs-line-row');
281
+ rows.forEach(row => {
282
+ const lineNumber = parseInt(row.getAttribute('data-line'), 10);
283
+ if (lineNumber) {
284
+ // Mousedown to start selection/drag
285
+ row.addEventListener('mousedown', function(event) {
286
+ event.preventDefault(); // Prevent text selection
287
+ handleLineMouseDown(event, reqId, lineNumber, container);
288
+ });
289
+
290
+ // Mouseover for drag selection
291
+ row.addEventListener('mouseover', function(event) {
292
+ if (isDragging && dragContainer === container) {
293
+ handleLineDragOver(reqId, lineNumber, container);
294
+ }
295
+ });
296
+
297
+ row.style.cursor = 'pointer';
298
+ }
299
+ });
300
+
301
+ bodySection.classList.add('rs-with-line-numbers');
302
+ return;
303
+ }
304
+
305
+ // No existing line numbers - create from scratch
306
+ const content = bodySection.textContent || '';
307
+ const container = convertToLineNumberedView(content, reqId);
308
+
309
+ // Replace content with line-numbered version
310
+ bodySection.innerHTML = '';
311
+ bodySection.appendChild(container);
312
+ bodySection.classList.add('rs-with-line-numbers');
313
+ }
314
+
315
+ /**
316
+ * Get current line selection state
317
+ * @returns {Object} Selection state with type, lineNumber, and lineRange
318
+ */
319
+ function getLineSelection() {
320
+ return {
321
+ type: selectionType,
322
+ lineNumber: window.selectedLineNumber,
323
+ lineRange: window.selectedLineRange,
324
+ reqId: currentReqId
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Clear line selection for a specific card
330
+ * @param {HTMLElement} cardElement - The card element
331
+ */
332
+ function clearLineSelection(cardElement) {
333
+ if (!cardElement) return;
334
+
335
+ const highlights = cardElement.querySelectorAll('.rs-line-row.selected');
336
+ highlights.forEach(el => el.classList.remove('selected'));
337
+
338
+ // Reset global state if this was the active selection
339
+ const container = cardElement.querySelector('.rs-line-numbers-container');
340
+ if (container && container.getAttribute('data-req-id') === currentReqId) {
341
+ window.selectedLineNumber = null;
342
+ window.selectedLineRange = null;
343
+ selectionType = 'general';
344
+ currentReqId = null;
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Clear all line selections across all cards
350
+ */
351
+ function clearAllLineSelections() {
352
+ const containers = document.querySelectorAll('.rs-line-numbers-container');
353
+ containers.forEach(container => {
354
+ const highlights = container.querySelectorAll('.rs-line-row.selected');
355
+ highlights.forEach(el => el.classList.remove('selected'));
356
+ });
357
+
358
+ // Reset global state
359
+ window.selectedLineNumber = null;
360
+ window.selectedLineRange = null;
361
+ selectionType = 'general';
362
+ currentReqId = null;
363
+ }
364
+
365
+ /**
366
+ * Highlight a specific line in a requirement card
367
+ * @param {HTMLElement} cardElement - The card element
368
+ * @param {number} lineNumber - The line number to highlight
369
+ */
370
+ function highlightLine(cardElement, lineNumber) {
371
+ if (!cardElement) return;
372
+
373
+ // Remove existing highlights
374
+ const existingHighlights = cardElement.querySelectorAll('.rs-line-row.selected');
375
+ existingHighlights.forEach(el => el.classList.remove('selected'));
376
+
377
+ // Add highlight to target line
378
+ const targetRow = cardElement.querySelector(`.rs-line-row[data-line="${lineNumber}"]`);
379
+ if (targetRow) {
380
+ targetRow.classList.add('selected');
381
+ targetRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Clear all line highlights in a card (alias for clearLineSelection)
387
+ * @param {HTMLElement} cardElement - The card element
388
+ */
389
+ function clearLineHighlights(cardElement) {
390
+ clearLineSelection(cardElement);
391
+ }
392
+
393
+ /**
394
+ * Handle keyboard events for line selection (REQ-d00092-I)
395
+ * @param {KeyboardEvent} event - Keyboard event
396
+ */
397
+ function handleKeyboard(event) {
398
+ if (event.key === 'Escape') {
399
+ clearAllLineSelections();
400
+ document.dispatchEvent(new CustomEvent('rs:line-selection-cleared'));
401
+ }
402
+ }
403
+
404
+ // Bind keyboard handler (REQ-d00092-I)
405
+ document.addEventListener('keydown', handleKeyboard);
406
+
407
+ // Export to review namespace
408
+ review.convertToLineNumberedView = convertToLineNumberedView;
409
+ review.applyLineNumbersToCard = applyLineNumbersToCard;
410
+ review.getLineSelection = getLineSelection;
411
+ review.clearLineSelection = clearLineSelection;
412
+ review.clearAllLineSelections = clearAllLineSelections;
413
+ review.highlightLine = highlightLine;
414
+ review.clearLineHighlights = clearLineHighlights;
415
+ review.selectSingleLine = selectSingleLine;
416
+ review.selectLineRange = selectLineRange;
417
+ review.handleLineClick = handleLineClick;
418
+ review.handleKeyboard = handleKeyboard;
419
+
420
+ // Export to ReviewSystem alias (RS) pattern for tests
421
+ RS.convertToLineNumberedView = convertToLineNumberedView;
422
+ RS.applyLineNumbersToCard = applyLineNumbersToCard;
423
+ RS.getLineSelection = getLineSelection;
424
+ RS.clearLineSelection = clearLineSelection;
425
+ RS.clearAllLineSelections = clearAllLineSelections;
426
+ RS.highlightLine = highlightLine;
427
+ RS.clearLineHighlights = clearLineHighlights;
428
+
429
+ })(TraceView.review, RS);