elspais 0.11.1__py3-none-any.whl → 0.43.5__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 (148) hide show
  1. elspais/__init__.py +2 -11
  2. elspais/{sponsors/__init__.py → associates.py} +102 -58
  3. elspais/cli.py +395 -79
  4. elspais/commands/__init__.py +9 -3
  5. elspais/commands/analyze.py +121 -173
  6. elspais/commands/changed.py +15 -30
  7. elspais/commands/config_cmd.py +13 -16
  8. elspais/commands/edit.py +60 -44
  9. elspais/commands/example_cmd.py +319 -0
  10. elspais/commands/hash_cmd.py +167 -183
  11. elspais/commands/health.py +1177 -0
  12. elspais/commands/index.py +98 -114
  13. elspais/commands/init.py +103 -26
  14. elspais/commands/reformat_cmd.py +41 -444
  15. elspais/commands/rules_cmd.py +7 -3
  16. elspais/commands/trace.py +444 -321
  17. elspais/commands/validate.py +195 -415
  18. elspais/config/__init__.py +799 -5
  19. elspais/{core/content_rules.py → content_rules.py} +20 -3
  20. elspais/docs/cli/assertions.md +67 -0
  21. elspais/docs/cli/commands.md +304 -0
  22. elspais/docs/cli/config.md +262 -0
  23. elspais/docs/cli/format.md +66 -0
  24. elspais/docs/cli/git.md +45 -0
  25. elspais/docs/cli/health.md +190 -0
  26. elspais/docs/cli/hierarchy.md +60 -0
  27. elspais/docs/cli/ignore.md +72 -0
  28. elspais/docs/cli/mcp.md +245 -0
  29. elspais/docs/cli/quickstart.md +58 -0
  30. elspais/docs/cli/traceability.md +89 -0
  31. elspais/docs/cli/validation.md +96 -0
  32. elspais/graph/GraphNode.py +383 -0
  33. elspais/graph/__init__.py +40 -0
  34. elspais/graph/annotators.py +927 -0
  35. elspais/graph/builder.py +1886 -0
  36. elspais/graph/deserializer.py +248 -0
  37. elspais/graph/factory.py +284 -0
  38. elspais/graph/metrics.py +127 -0
  39. elspais/graph/mutations.py +161 -0
  40. elspais/graph/parsers/__init__.py +156 -0
  41. elspais/graph/parsers/code.py +213 -0
  42. elspais/graph/parsers/comments.py +112 -0
  43. elspais/graph/parsers/config_helpers.py +29 -0
  44. elspais/graph/parsers/heredocs.py +225 -0
  45. elspais/graph/parsers/journey.py +131 -0
  46. elspais/graph/parsers/remainder.py +79 -0
  47. elspais/graph/parsers/requirement.py +347 -0
  48. elspais/graph/parsers/results/__init__.py +6 -0
  49. elspais/graph/parsers/results/junit_xml.py +229 -0
  50. elspais/graph/parsers/results/pytest_json.py +313 -0
  51. elspais/graph/parsers/test.py +305 -0
  52. elspais/graph/relations.py +78 -0
  53. elspais/graph/serialize.py +216 -0
  54. elspais/html/__init__.py +8 -0
  55. elspais/html/generator.py +731 -0
  56. elspais/html/templates/trace_view.html.j2 +2151 -0
  57. elspais/mcp/__init__.py +47 -29
  58. elspais/mcp/__main__.py +5 -1
  59. elspais/mcp/file_mutations.py +138 -0
  60. elspais/mcp/server.py +2016 -247
  61. elspais/testing/__init__.py +4 -4
  62. elspais/testing/config.py +3 -0
  63. elspais/testing/mapper.py +1 -1
  64. elspais/testing/result_parser.py +25 -21
  65. elspais/testing/scanner.py +301 -12
  66. elspais/utilities/__init__.py +1 -0
  67. elspais/utilities/docs_loader.py +115 -0
  68. elspais/utilities/git.py +607 -0
  69. elspais/{core → utilities}/hasher.py +8 -22
  70. elspais/utilities/md_renderer.py +189 -0
  71. elspais/{core → utilities}/patterns.py +58 -57
  72. elspais/utilities/reference_config.py +626 -0
  73. elspais/validation/__init__.py +19 -0
  74. elspais/validation/format.py +264 -0
  75. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/METADATA +7 -4
  76. elspais-0.43.5.dist-info/RECORD +80 -0
  77. elspais/config/defaults.py +0 -173
  78. elspais/config/loader.py +0 -494
  79. elspais/core/__init__.py +0 -21
  80. elspais/core/git.py +0 -352
  81. elspais/core/models.py +0 -320
  82. elspais/core/parser.py +0 -640
  83. elspais/core/rules.py +0 -514
  84. elspais/mcp/context.py +0 -171
  85. elspais/mcp/serializers.py +0 -112
  86. elspais/reformat/__init__.py +0 -50
  87. elspais/reformat/detector.py +0 -119
  88. elspais/reformat/hierarchy.py +0 -246
  89. elspais/reformat/line_breaks.py +0 -220
  90. elspais/reformat/prompts.py +0 -123
  91. elspais/reformat/transformer.py +0 -264
  92. elspais/trace_view/__init__.py +0 -54
  93. elspais/trace_view/coverage.py +0 -183
  94. elspais/trace_view/generators/__init__.py +0 -12
  95. elspais/trace_view/generators/base.py +0 -329
  96. elspais/trace_view/generators/csv.py +0 -122
  97. elspais/trace_view/generators/markdown.py +0 -175
  98. elspais/trace_view/html/__init__.py +0 -31
  99. elspais/trace_view/html/generator.py +0 -1006
  100. elspais/trace_view/html/templates/base.html +0 -283
  101. elspais/trace_view/html/templates/components/code_viewer_modal.html +0 -14
  102. elspais/trace_view/html/templates/components/file_picker_modal.html +0 -20
  103. elspais/trace_view/html/templates/components/legend_modal.html +0 -69
  104. elspais/trace_view/html/templates/components/review_panel.html +0 -118
  105. elspais/trace_view/html/templates/partials/review/help/help-panel.json +0 -244
  106. elspais/trace_view/html/templates/partials/review/help/onboarding.json +0 -77
  107. elspais/trace_view/html/templates/partials/review/help/tooltips.json +0 -237
  108. elspais/trace_view/html/templates/partials/review/review-comments.js +0 -928
  109. elspais/trace_view/html/templates/partials/review/review-data.js +0 -961
  110. elspais/trace_view/html/templates/partials/review/review-help.js +0 -679
  111. elspais/trace_view/html/templates/partials/review/review-init.js +0 -177
  112. elspais/trace_view/html/templates/partials/review/review-line-numbers.js +0 -429
  113. elspais/trace_view/html/templates/partials/review/review-packages.js +0 -1029
  114. elspais/trace_view/html/templates/partials/review/review-position.js +0 -540
  115. elspais/trace_view/html/templates/partials/review/review-resize.js +0 -115
  116. elspais/trace_view/html/templates/partials/review/review-status.js +0 -659
  117. elspais/trace_view/html/templates/partials/review/review-sync.js +0 -992
  118. elspais/trace_view/html/templates/partials/review-styles.css +0 -2238
  119. elspais/trace_view/html/templates/partials/scripts.js +0 -1741
  120. elspais/trace_view/html/templates/partials/styles.css +0 -1756
  121. elspais/trace_view/models.py +0 -353
  122. elspais/trace_view/review/__init__.py +0 -60
  123. elspais/trace_view/review/branches.py +0 -1149
  124. elspais/trace_view/review/models.py +0 -1205
  125. elspais/trace_view/review/position.py +0 -609
  126. elspais/trace_view/review/server.py +0 -1056
  127. elspais/trace_view/review/status.py +0 -470
  128. elspais/trace_view/review/storage.py +0 -1367
  129. elspais/trace_view/scanning.py +0 -213
  130. elspais/trace_view/specs/README.md +0 -84
  131. elspais/trace_view/specs/tv-d00001-template-architecture.md +0 -36
  132. elspais/trace_view/specs/tv-d00002-css-extraction.md +0 -37
  133. elspais/trace_view/specs/tv-d00003-js-extraction.md +0 -43
  134. elspais/trace_view/specs/tv-d00004-build-embedding.md +0 -40
  135. elspais/trace_view/specs/tv-d00005-test-format.md +0 -78
  136. elspais/trace_view/specs/tv-d00010-review-data-models.md +0 -33
  137. elspais/trace_view/specs/tv-d00011-review-storage.md +0 -33
  138. elspais/trace_view/specs/tv-d00012-position-resolution.md +0 -33
  139. elspais/trace_view/specs/tv-d00013-git-branches.md +0 -31
  140. elspais/trace_view/specs/tv-d00014-review-api-server.md +0 -31
  141. elspais/trace_view/specs/tv-d00015-status-modifier.md +0 -27
  142. elspais/trace_view/specs/tv-d00016-js-integration.md +0 -33
  143. elspais/trace_view/specs/tv-p00001-html-generator.md +0 -33
  144. elspais/trace_view/specs/tv-p00002-review-system.md +0 -29
  145. elspais-0.11.1.dist-info/RECORD +0 -101
  146. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/WHEEL +0 -0
  147. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/entry_points.txt +0 -0
  148. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/licenses/LICENSE +0 -0
@@ -1,659 +0,0 @@
1
- /**
2
- * TraceView Review Status Request UI Module
3
- *
4
- * User interface for status change requests:
5
- * - Status change request form
6
- * - Approval workflow display
7
- * - Pending request badges
8
- *
9
- * IMPLEMENTS REQUIREMENTS:
10
- * REQ-tv-d00016: Review JavaScript Integration
11
- */
12
-
13
- // Ensure TraceView.review namespace exists
14
- window.TraceView = window.TraceView || {};
15
- TraceView.review = TraceView.review || {};
16
-
17
- (function(review) {
18
- 'use strict';
19
-
20
- // ==========================================================================
21
- // Templates
22
- // ==========================================================================
23
-
24
- /**
25
- * Check if a REQ is in the active package
26
- * @param {string} reqId - Requirement ID
27
- * @returns {boolean} True if REQ is in active package
28
- */
29
- function isReqInActivePackage(reqId) {
30
- const packages = review.packages;
31
- if (!packages || !packages.activeId) return false;
32
- const activePkg = packages.items.find(p => p.packageId === packages.activeId);
33
- return activePkg && activePkg.reqIds && activePkg.reqIds.includes(reqId);
34
- }
35
-
36
- /**
37
- * Create status request panel HTML
38
- * @param {string} reqId - Requirement ID
39
- * @param {string} currentStatus - Current status of the requirement
40
- * @returns {string} HTML
41
- */
42
- function statusPanelTemplate(reqId, currentStatus) {
43
- // Show quick add button for Draft REQs - adds to review package
44
- const quickToggle = currentStatus === 'Draft' ? `
45
- <button class="rs-btn rs-btn-primary rs-quick-toggle" data-req-id="${reqId}">
46
- Add to Review
47
- </button>
48
- ` : '';
49
-
50
- // Package membership button
51
- const inPackage = isReqInActivePackage(reqId);
52
- const packageBtn = `
53
- <button class="rs-btn ${inPackage ? 'rs-btn-danger' : 'rs-btn-secondary'} rs-package-toggle" data-req-id="${reqId}">
54
- ${inPackage ? '− Remove from Package' : '+ Add to Package'}
55
- </button>
56
- `;
57
-
58
- return `
59
- <div class="rs-status-panel" data-req-id="${reqId}">
60
- <div class="rs-status-header">
61
- <h4>Status</h4>
62
- <span class="rs-current-status status-badge status-${currentStatus.toLowerCase()}">
63
- ${escapeHtml(currentStatus)}
64
- </span>
65
- </div>
66
- <div class="rs-quick-actions">
67
- ${quickToggle}
68
- ${packageBtn}
69
- </div>
70
- <div class="rs-status-content">
71
- <div class="rs-requests"></div>
72
- <div class="rs-no-requests" style="display: none;">
73
- No pending status change requests.
74
- </div>
75
- </div>
76
- <div class="rs-status-actions">
77
- <button class="rs-btn rs-btn-secondary rs-request-change-btn">
78
- Request Status Change
79
- </button>
80
- </div>
81
- </div>
82
- `;
83
- }
84
-
85
- /**
86
- * Create status request card HTML
87
- * @param {StatusRequest} request - Request object
88
- * @returns {string} HTML
89
- */
90
- function requestCardTemplate(request) {
91
- const stateClass = `rs-state-${request.state}`;
92
- const stateLabel = getStateLabel(request.state);
93
- const progressPercent = getApprovalProgress(request);
94
-
95
- return `
96
- <div class="rs-request-card ${stateClass}" data-request-id="${request.requestId}">
97
- <div class="rs-request-header">
98
- <span class="rs-request-transition">
99
- ${escapeHtml(request.fromStatus)} -> ${escapeHtml(request.toStatus)}
100
- </span>
101
- <span class="rs-request-state rs-badge rs-badge-${request.state}">
102
- ${stateLabel}
103
- </span>
104
- </div>
105
- <div class="rs-request-meta">
106
- <span>Requested by <strong>${escapeHtml(request.requestedBy)}</strong></span>
107
- <span>${formatTime(request.requestedAt)}</span>
108
- </div>
109
- <div class="rs-request-justification">
110
- ${formatCommentBody(request.justification)}
111
- </div>
112
- <div class="rs-approval-progress">
113
- <div class="rs-progress-bar">
114
- <div class="rs-progress-fill" style="width: ${progressPercent}%"></div>
115
- </div>
116
- <span class="rs-progress-label">
117
- ${request.approvals.length}/${request.requiredApprovers.length} approvals
118
- </span>
119
- </div>
120
- <div class="rs-approvers-list">
121
- ${renderApproversList(request)}
122
- </div>
123
- ${request.state === review.RequestState.PENDING ? renderApprovalActions(request) : ''}
124
- </div>
125
- `;
126
- }
127
-
128
- /**
129
- * Render approvers list with status
130
- * @param {StatusRequest} request - Request object
131
- * @returns {string} HTML
132
- */
133
- function renderApproversList(request) {
134
- const approvalMap = {};
135
- request.approvals.forEach(a => {
136
- approvalMap[a.user] = a;
137
- });
138
-
139
- return `
140
- <div class="rs-approvers">
141
- ${request.requiredApprovers.map(approver => {
142
- const approval = approvalMap[approver];
143
- if (approval) {
144
- const icon = approval.decision === 'approve' ? '[+]' : '[-]';
145
- const cls = approval.decision === 'approve' ? 'approved' : 'rejected';
146
- return `
147
- <span class="rs-approver rs-approver-${cls}" title="${approval.comment || ''}">
148
- ${icon} ${escapeHtml(approver)}
149
- </span>
150
- `;
151
- } else {
152
- return `
153
- <span class="rs-approver rs-approver-pending">
154
- [ ] ${escapeHtml(approver)}
155
- </span>
156
- `;
157
- }
158
- }).join('')}
159
- </div>
160
- `;
161
- }
162
-
163
- /**
164
- * Render approval action buttons
165
- * @param {StatusRequest} request - Request object
166
- * @returns {string} HTML
167
- */
168
- function renderApprovalActions(request) {
169
- const user = review.state.currentUser;
170
- if (!user || !request.requiredApprovers.includes(user)) {
171
- return '';
172
- }
173
-
174
- // Check if user already approved
175
- const existing = request.approvals.find(a => a.user === user);
176
- if (existing) {
177
- return `<div class="rs-already-voted">You have already ${existing.decision}d this request.</div>`;
178
- }
179
-
180
- return `
181
- <div class="rs-approval-actions">
182
- <button class="rs-btn rs-btn-success rs-approve-btn">Approve</button>
183
- <button class="rs-btn rs-btn-danger rs-reject-btn">Reject</button>
184
- <input type="text" class="rs-approval-comment" placeholder="Comment (optional)">
185
- </div>
186
- `;
187
- }
188
-
189
- /**
190
- * Create new request form HTML
191
- * @param {string} reqId - Requirement ID
192
- * @param {string} currentStatus - Current status
193
- * @returns {string} HTML
194
- */
195
- function requestFormTemplate(reqId, currentStatus) {
196
- const transitions = getValidTransitions(currentStatus);
197
-
198
- return `
199
- <div class="rs-request-form" data-req-id="${reqId}">
200
- <h4>Request Status Change</h4>
201
- <div class="rs-form-group">
202
- <label>Current Status</label>
203
- <span class="rs-current-status-display">${escapeHtml(currentStatus)}</span>
204
- </div>
205
- <div class="rs-form-group">
206
- <label>New Status</label>
207
- <select class="rs-new-status">
208
- ${transitions.map(status =>
209
- `<option value="${status}">${status}</option>`
210
- ).join('')}
211
- </select>
212
- </div>
213
- <div class="rs-form-group">
214
- <label>Justification</label>
215
- <textarea class="rs-justification" rows="3"
216
- placeholder="Explain why this status change is needed..."></textarea>
217
- </div>
218
- <div class="rs-required-approvers">
219
- <label>Required Approvers</label>
220
- <span class="rs-approvers-display"></span>
221
- </div>
222
- <div class="rs-form-actions">
223
- <button class="rs-btn rs-btn-primary rs-submit-request">Submit Request</button>
224
- <button class="rs-btn rs-cancel-request">Cancel</button>
225
- </div>
226
- </div>
227
- `;
228
- }
229
-
230
- // ==========================================================================
231
- // Helper Functions
232
- // ==========================================================================
233
-
234
- function escapeHtml(text) {
235
- const div = document.createElement('div');
236
- div.textContent = text;
237
- return div.innerHTML;
238
- }
239
-
240
- function formatTime(isoString) {
241
- try {
242
- const date = new Date(isoString);
243
- return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
244
- } catch (e) {
245
- return isoString;
246
- }
247
- }
248
-
249
- function formatCommentBody(body) {
250
- let html = escapeHtml(body);
251
- html = html.replace(/\n/g, '<br>');
252
- return html;
253
- }
254
-
255
- function getStateLabel(state) {
256
- switch (state) {
257
- case review.RequestState.PENDING: return 'Pending';
258
- case review.RequestState.APPROVED: return 'Approved';
259
- case review.RequestState.REJECTED: return 'Rejected';
260
- case review.RequestState.APPLIED: return 'Applied';
261
- default: return state;
262
- }
263
- }
264
-
265
- function getApprovalProgress(request) {
266
- if (request.requiredApprovers.length === 0) return 100;
267
- const approved = request.approvals.filter(a => a.decision === 'approve').length;
268
- return Math.round((approved / request.requiredApprovers.length) * 100);
269
- }
270
-
271
- function getValidTransitions(currentStatus) {
272
- const transitions = {
273
- 'Draft': ['Review', 'Active', 'Deprecated'],
274
- 'Review': ['Active', 'Draft', 'Deprecated'],
275
- 'Active': ['Deprecated'],
276
- 'Deprecated': [] // No transitions from Deprecated
277
- };
278
- return transitions[currentStatus] || [];
279
- }
280
-
281
- /**
282
- * Change status directly via API (no approval workflow)
283
- * @param {string} reqId - Requirement ID
284
- * @param {string} newStatus - New status to set
285
- * @returns {Promise<object>} API response
286
- */
287
- async function changeStatusDirect(reqId, newStatus) {
288
- const user = review.state.currentUser || 'anonymous';
289
- try {
290
- const response = await fetch(`/api/reviews/reqs/${reqId}/status`, {
291
- method: 'POST',
292
- headers: { 'Content-Type': 'application/json' },
293
- body: JSON.stringify({ newStatus, user })
294
- });
295
- const result = await response.json();
296
- if (result.success) {
297
- // Update local state
298
- const reqData = window.REQ_CONTENT_DATA && window.REQ_CONTENT_DATA[reqId];
299
- if (reqData) {
300
- reqData.status = newStatus;
301
- }
302
- // Refresh status display
303
- updateStatusBadge(reqId, newStatus);
304
-
305
- // Handle auto-add to package when status changes to Review
306
- if (result.addedToPackage && typeof review.renderPackagesPanel === 'function') {
307
- // Update local package state
308
- const pkg = review.packages && review.packages.items &&
309
- review.packages.items.find(p => p.packageId === result.addedToPackage.packageId);
310
- if (pkg && !pkg.reqIds.includes(reqId)) {
311
- pkg.reqIds.push(reqId);
312
- }
313
- // Re-render packages panel to update counts
314
- review.renderPackagesPanel();
315
- console.log(`REQ-${reqId} added to package: ${result.addedToPackage.packageName}`);
316
- }
317
- }
318
- return result;
319
- } catch (error) {
320
- console.error('Error changing status:', error);
321
- return { success: false, error: error.message };
322
- }
323
- }
324
- review.changeStatusDirect = changeStatusDirect;
325
-
326
- /**
327
- * Update status badge in the UI
328
- * @param {string} reqId - Requirement ID
329
- * @param {string} newStatus - New status
330
- */
331
- function updateStatusBadge(reqId, newStatus) {
332
- // Update in grid/tree
333
- const statusBadge = document.querySelector(`[data-req-id="${reqId}"] .status-badge`);
334
- if (statusBadge) {
335
- statusBadge.className = `status-badge status-${newStatus.toLowerCase()}`;
336
- statusBadge.textContent = newStatus;
337
- }
338
- // Update in middle column if visible
339
- const middleStatusBadge = document.querySelector(`#req-card-${reqId} .status-badge`);
340
- if (middleStatusBadge) {
341
- middleStatusBadge.className = `status-badge status-${newStatus.toLowerCase()}`;
342
- middleStatusBadge.textContent = newStatus;
343
- }
344
- }
345
- review.updateStatusBadge = updateStatusBadge;
346
-
347
- /**
348
- * Add Draft REQ to review package (shortcut for review mode)
349
- * Note: Does NOT change status - valid statuses are Draft, Active, Deprecated only.
350
- * This just adds the REQ to the active review package for tracking.
351
- * @param {string} reqId - Requirement ID
352
- * @returns {Promise<object>} API response
353
- */
354
- async function toggleToReview(reqId) {
355
- const reqData = window.REQ_CONTENT_DATA && window.REQ_CONTENT_DATA[reqId];
356
- if (!reqData) return { success: false, error: 'REQ not found' };
357
-
358
- if (reqData.status === 'Draft') {
359
- // Add to active package for review tracking (don't change status)
360
- if (review.addReqToActivePackage) {
361
- const packageResult = await review.addReqToActivePackage(reqId);
362
- if (packageResult && packageResult.success) {
363
- console.log(`REQ-${reqId} added to review package`);
364
- return { success: true, addedToPackage: packageResult };
365
- } else {
366
- return { success: false, error: packageResult?.error || 'Failed to add to package' };
367
- }
368
- }
369
- return { success: false, error: 'Package system not available' };
370
- }
371
- return { success: false, error: 'REQ is not in Draft status' };
372
- }
373
- review.toggleToReview = toggleToReview;
374
-
375
- // ==========================================================================
376
- // UI Components
377
- // ==========================================================================
378
-
379
- /**
380
- * Render status panel for a requirement
381
- * @param {Element} container - Container element
382
- * @param {string} reqId - Requirement ID
383
- * @param {string} currentStatus - Current requirement status
384
- */
385
- function renderStatusPanel(container, reqId, currentStatus) {
386
- container.innerHTML = statusPanelTemplate(reqId, currentStatus);
387
-
388
- const requests = review.state.getRequests(reqId);
389
- const requestsContainer = container.querySelector('.rs-requests');
390
- const noRequests = container.querySelector('.rs-no-requests');
391
-
392
- if (requests.length === 0) {
393
- noRequests.style.display = 'block';
394
- } else {
395
- requests.forEach(request => {
396
- requestsContainer.insertAdjacentHTML('beforeend', requestCardTemplate(request));
397
- });
398
- bindRequestEvents(container);
399
- }
400
-
401
- // Bind quick toggle button
402
- const quickToggleBtn = container.querySelector('.rs-quick-toggle');
403
- if (quickToggleBtn) {
404
- quickToggleBtn.addEventListener('click', async () => {
405
- quickToggleBtn.disabled = true;
406
- quickToggleBtn.textContent = 'Updating...';
407
- const result = await toggleToReview(reqId);
408
- if (result.success) {
409
- // Re-render the panel with new status
410
- renderStatusPanel(container, reqId, 'Review');
411
- } else {
412
- quickToggleBtn.disabled = false;
413
- quickToggleBtn.textContent = 'Set to Review';
414
- alert('Failed to change status: ' + (result.error || 'Unknown error'));
415
- }
416
- });
417
- }
418
-
419
- // Bind request change button
420
- const requestBtn = container.querySelector('.rs-request-change-btn');
421
- if (requestBtn) {
422
- const transitions = getValidTransitions(currentStatus);
423
- if (transitions.length === 0) {
424
- requestBtn.disabled = true;
425
- requestBtn.title = 'No valid transitions from current status';
426
- } else {
427
- requestBtn.addEventListener('click', () => {
428
- showRequestForm(container, reqId, currentStatus);
429
- });
430
- }
431
- }
432
-
433
- // Bind package toggle button
434
- const packageToggleBtn = container.querySelector('.rs-package-toggle');
435
- if (packageToggleBtn) {
436
- packageToggleBtn.addEventListener('click', async () => {
437
- packageToggleBtn.disabled = true;
438
- const originalText = packageToggleBtn.textContent;
439
- packageToggleBtn.textContent = 'Updating...';
440
-
441
- const inPackage = isReqInActivePackage(reqId);
442
- let result;
443
-
444
- if (inPackage) {
445
- // Remove from active package
446
- const packageId = review.packages.activeId;
447
- result = await review.removeReqFromPackage(packageId, reqId);
448
- } else {
449
- // Add to active package (or default if none active)
450
- result = await review.addReqToActivePackage(reqId);
451
- }
452
-
453
- if (result && result.success) {
454
- // Re-render the panel to update button state
455
- renderStatusPanel(container, reqId, currentStatus);
456
- } else {
457
- packageToggleBtn.disabled = false;
458
- packageToggleBtn.textContent = originalText;
459
- alert('Failed to update package: ' + (result?.error || 'Unknown error'));
460
- }
461
- });
462
- }
463
- }
464
- review.renderStatusPanel = renderStatusPanel;
465
-
466
- /**
467
- * Show request form
468
- * @param {Element} container - Container element
469
- * @param {string} reqId - Requirement ID
470
- * @param {string} currentStatus - Current status
471
- */
472
- function showRequestForm(container, reqId, currentStatus) {
473
- // Remove existing form
474
- let form = container.querySelector('.rs-request-form');
475
- if (form) form.remove();
476
-
477
- container.insertAdjacentHTML('afterbegin', requestFormTemplate(reqId, currentStatus));
478
- form = container.querySelector('.rs-request-form');
479
-
480
- // Update approvers display on status change
481
- const newStatus = form.querySelector('.rs-new-status');
482
- const approversDisplay = form.querySelector('.rs-approvers-display');
483
-
484
- function updateApprovers() {
485
- const toStatus = newStatus.value;
486
- const approvers = review.state.config.getRequiredApprovers(currentStatus, toStatus);
487
- approversDisplay.textContent = approvers.join(', ');
488
- }
489
- updateApprovers();
490
- newStatus.addEventListener('change', updateApprovers);
491
-
492
- // Submit handler
493
- form.querySelector('.rs-submit-request').addEventListener('click', () => {
494
- submitStatusRequest(form, reqId, currentStatus);
495
- });
496
-
497
- // Cancel handler
498
- form.querySelector('.rs-cancel-request').addEventListener('click', () => {
499
- form.remove();
500
- });
501
-
502
- // Focus justification
503
- form.querySelector('.rs-justification').focus();
504
- }
505
- review.showRequestForm = showRequestForm;
506
-
507
- /**
508
- * Submit status change request
509
- * @param {Element} form - Form element
510
- * @param {string} reqId - Requirement ID
511
- * @param {string} currentStatus - Current status
512
- */
513
- function submitStatusRequest(form, reqId, currentStatus) {
514
- const newStatus = form.querySelector('.rs-new-status').value;
515
- const justification = form.querySelector('.rs-justification').value.trim();
516
-
517
- if (!justification) {
518
- alert('Please provide a justification');
519
- return;
520
- }
521
-
522
- const user = review.state.currentUser || 'anonymous';
523
- const approvers = review.state.config.getRequiredApprovers(currentStatus, newStatus);
524
-
525
- // Create request
526
- const request = review.StatusRequest.create(
527
- reqId, currentStatus, newStatus, user, justification, approvers
528
- );
529
- review.state.addRequest(request);
530
-
531
- // Trigger event
532
- document.dispatchEvent(new CustomEvent('traceview:request-created', {
533
- detail: { request, reqId }
534
- }));
535
-
536
- // Re-render
537
- const panel = form.closest('.rs-status-panel');
538
- if (panel) {
539
- renderStatusPanel(panel.parentElement, reqId, currentStatus);
540
- } else {
541
- form.remove();
542
- }
543
- }
544
-
545
- /**
546
- * Bind event handlers to request elements
547
- * @param {Element} container - Container element
548
- */
549
- function bindRequestEvents(container) {
550
- // Approve buttons
551
- container.querySelectorAll('.rs-approve-btn').forEach(btn => {
552
- btn.addEventListener('click', () => {
553
- const card = btn.closest('.rs-request-card');
554
- submitApproval(card, container, 'approve');
555
- });
556
- });
557
-
558
- // Reject buttons
559
- container.querySelectorAll('.rs-reject-btn').forEach(btn => {
560
- btn.addEventListener('click', () => {
561
- const card = btn.closest('.rs-request-card');
562
- submitApproval(card, container, 'reject');
563
- });
564
- });
565
- }
566
-
567
- /**
568
- * Submit approval/rejection
569
- * @param {Element} card - Request card element
570
- * @param {Element} container - Container element
571
- * @param {string} decision - 'approve' or 'reject'
572
- */
573
- function submitApproval(card, container, decision) {
574
- const requestId = card.getAttribute('data-request-id');
575
- const comment = card.querySelector('.rs-approval-comment')?.value || '';
576
- const user = review.state.currentUser;
577
- const reqId = container.querySelector('[data-req-id]')?.getAttribute('data-req-id') ||
578
- container.closest('[data-req-id]')?.getAttribute('data-req-id');
579
-
580
- if (!user) {
581
- alert('Please set your username first');
582
- return;
583
- }
584
-
585
- if (reqId) {
586
- const requests = review.state.getRequests(reqId);
587
- const request = requests.find(r => r.requestId === requestId);
588
- if (request) {
589
- request.addApproval(user, decision, comment);
590
-
591
- // Trigger event
592
- document.dispatchEvent(new CustomEvent('traceview:approval-added', {
593
- detail: { request, reqId, user, decision }
594
- }));
595
-
596
- // Get current status from display
597
- const currentStatus = container.querySelector('.rs-current-status strong')?.textContent || 'Draft';
598
-
599
- // Re-render
600
- renderStatusPanel(container.parentElement || container, reqId, currentStatus);
601
- }
602
- }
603
- }
604
-
605
- /**
606
- * Get pending request count for a requirement
607
- * @param {string} reqId - Requirement ID
608
- * @returns {number} Count of pending requests
609
- */
610
- function getPendingRequestCount(reqId) {
611
- const requests = review.state.getRequests(reqId);
612
- return requests.filter(r => r.state === review.RequestState.PENDING).length;
613
- }
614
- review.getPendingRequestCount = getPendingRequestCount;
615
-
616
- /**
617
- * Create status badge for display in REQ list
618
- * @param {string} reqId - Requirement ID
619
- * @returns {string} HTML for badge or empty string
620
- */
621
- function createStatusBadge(reqId) {
622
- const pending = getPendingRequestCount(reqId);
623
- if (pending === 0) return '';
624
-
625
- return `<span class="rs-badge rs-badge-pending" title="${pending} pending status request(s)">
626
- [P] ${pending}
627
- </span>`;
628
- }
629
- review.createStatusBadge = createStatusBadge;
630
-
631
- // ==========================================================================
632
- // Review Panel Integration
633
- // ==========================================================================
634
-
635
- /**
636
- * Handle review panel ready event - add status section
637
- * @param {CustomEvent} event - Event with reqId, req, and sectionsContainer
638
- */
639
- function handleReviewPanelReady(event) {
640
- const { reqId, req, sectionsContainer } = event.detail;
641
- if (!sectionsContainer) return;
642
-
643
- // Get current status from requirement data
644
- const currentStatus = req ? req.status : 'Draft';
645
-
646
- // Create status section
647
- const statusSection = document.createElement('div');
648
- statusSection.className = 'rs-status-section';
649
- statusSection.setAttribute('data-req-id', reqId);
650
- sectionsContainer.appendChild(statusSection);
651
-
652
- // Render status panel
653
- renderStatusPanel(statusSection, reqId, currentStatus);
654
- }
655
-
656
- // Register event listener
657
- document.addEventListener('traceview:review-panel-ready', handleReviewPanelReady);
658
-
659
- })(TraceView.review);