elspais 0.11.2__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.
- elspais/__init__.py +1 -10
- elspais/{sponsors/__init__.py → associates.py} +102 -56
- elspais/cli.py +366 -69
- elspais/commands/__init__.py +9 -3
- elspais/commands/analyze.py +118 -169
- elspais/commands/changed.py +12 -23
- elspais/commands/config_cmd.py +10 -13
- elspais/commands/edit.py +33 -13
- elspais/commands/example_cmd.py +319 -0
- elspais/commands/hash_cmd.py +161 -183
- elspais/commands/health.py +1177 -0
- elspais/commands/index.py +98 -115
- elspais/commands/init.py +99 -22
- elspais/commands/reformat_cmd.py +41 -433
- elspais/commands/rules_cmd.py +2 -2
- elspais/commands/trace.py +443 -324
- elspais/commands/validate.py +193 -411
- elspais/config/__init__.py +799 -5
- elspais/{core/content_rules.py → content_rules.py} +20 -2
- elspais/docs/cli/assertions.md +67 -0
- elspais/docs/cli/commands.md +304 -0
- elspais/docs/cli/config.md +262 -0
- elspais/docs/cli/format.md +66 -0
- elspais/docs/cli/git.md +45 -0
- elspais/docs/cli/health.md +190 -0
- elspais/docs/cli/hierarchy.md +60 -0
- elspais/docs/cli/ignore.md +72 -0
- elspais/docs/cli/mcp.md +245 -0
- elspais/docs/cli/quickstart.md +58 -0
- elspais/docs/cli/traceability.md +89 -0
- elspais/docs/cli/validation.md +96 -0
- elspais/graph/GraphNode.py +383 -0
- elspais/graph/__init__.py +40 -0
- elspais/graph/annotators.py +927 -0
- elspais/graph/builder.py +1886 -0
- elspais/graph/deserializer.py +248 -0
- elspais/graph/factory.py +284 -0
- elspais/graph/metrics.py +127 -0
- elspais/graph/mutations.py +161 -0
- elspais/graph/parsers/__init__.py +156 -0
- elspais/graph/parsers/code.py +213 -0
- elspais/graph/parsers/comments.py +112 -0
- elspais/graph/parsers/config_helpers.py +29 -0
- elspais/graph/parsers/heredocs.py +225 -0
- elspais/graph/parsers/journey.py +131 -0
- elspais/graph/parsers/remainder.py +79 -0
- elspais/graph/parsers/requirement.py +347 -0
- elspais/graph/parsers/results/__init__.py +6 -0
- elspais/graph/parsers/results/junit_xml.py +229 -0
- elspais/graph/parsers/results/pytest_json.py +313 -0
- elspais/graph/parsers/test.py +305 -0
- elspais/graph/relations.py +78 -0
- elspais/graph/serialize.py +216 -0
- elspais/html/__init__.py +8 -0
- elspais/html/generator.py +731 -0
- elspais/html/templates/trace_view.html.j2 +2151 -0
- elspais/mcp/__init__.py +45 -29
- elspais/mcp/__main__.py +5 -1
- elspais/mcp/file_mutations.py +138 -0
- elspais/mcp/server.py +1998 -244
- elspais/testing/__init__.py +3 -3
- elspais/testing/config.py +3 -0
- elspais/testing/mapper.py +1 -1
- elspais/testing/scanner.py +301 -12
- elspais/utilities/__init__.py +1 -0
- elspais/utilities/docs_loader.py +115 -0
- elspais/utilities/git.py +607 -0
- elspais/{core → utilities}/hasher.py +8 -22
- elspais/utilities/md_renderer.py +189 -0
- elspais/{core → utilities}/patterns.py +56 -51
- elspais/utilities/reference_config.py +626 -0
- elspais/validation/__init__.py +19 -0
- elspais/validation/format.py +264 -0
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/METADATA +7 -4
- elspais-0.43.5.dist-info/RECORD +80 -0
- elspais/config/defaults.py +0 -179
- elspais/config/loader.py +0 -494
- elspais/core/__init__.py +0 -21
- elspais/core/git.py +0 -346
- elspais/core/models.py +0 -320
- elspais/core/parser.py +0 -639
- elspais/core/rules.py +0 -509
- elspais/mcp/context.py +0 -172
- elspais/mcp/serializers.py +0 -112
- elspais/reformat/__init__.py +0 -50
- elspais/reformat/detector.py +0 -112
- elspais/reformat/hierarchy.py +0 -247
- elspais/reformat/line_breaks.py +0 -218
- elspais/reformat/prompts.py +0 -133
- elspais/reformat/transformer.py +0 -266
- elspais/trace_view/__init__.py +0 -55
- elspais/trace_view/coverage.py +0 -183
- elspais/trace_view/generators/__init__.py +0 -12
- elspais/trace_view/generators/base.py +0 -334
- elspais/trace_view/generators/csv.py +0 -118
- elspais/trace_view/generators/markdown.py +0 -170
- elspais/trace_view/html/__init__.py +0 -33
- elspais/trace_view/html/generator.py +0 -1140
- elspais/trace_view/html/templates/base.html +0 -283
- elspais/trace_view/html/templates/components/code_viewer_modal.html +0 -14
- elspais/trace_view/html/templates/components/file_picker_modal.html +0 -20
- elspais/trace_view/html/templates/components/legend_modal.html +0 -69
- elspais/trace_view/html/templates/components/review_panel.html +0 -118
- elspais/trace_view/html/templates/partials/review/help/help-panel.json +0 -244
- elspais/trace_view/html/templates/partials/review/help/onboarding.json +0 -77
- elspais/trace_view/html/templates/partials/review/help/tooltips.json +0 -237
- elspais/trace_view/html/templates/partials/review/review-comments.js +0 -928
- elspais/trace_view/html/templates/partials/review/review-data.js +0 -961
- elspais/trace_view/html/templates/partials/review/review-help.js +0 -679
- elspais/trace_view/html/templates/partials/review/review-init.js +0 -177
- elspais/trace_view/html/templates/partials/review/review-line-numbers.js +0 -429
- elspais/trace_view/html/templates/partials/review/review-packages.js +0 -1029
- elspais/trace_view/html/templates/partials/review/review-position.js +0 -540
- elspais/trace_view/html/templates/partials/review/review-resize.js +0 -115
- elspais/trace_view/html/templates/partials/review/review-status.js +0 -659
- elspais/trace_view/html/templates/partials/review/review-sync.js +0 -992
- elspais/trace_view/html/templates/partials/review-styles.css +0 -2238
- elspais/trace_view/html/templates/partials/scripts.js +0 -1741
- elspais/trace_view/html/templates/partials/styles.css +0 -1756
- elspais/trace_view/models.py +0 -378
- elspais/trace_view/review/__init__.py +0 -63
- elspais/trace_view/review/branches.py +0 -1142
- elspais/trace_view/review/models.py +0 -1200
- elspais/trace_view/review/position.py +0 -591
- elspais/trace_view/review/server.py +0 -1032
- elspais/trace_view/review/status.py +0 -455
- elspais/trace_view/review/storage.py +0 -1343
- elspais/trace_view/scanning.py +0 -213
- elspais/trace_view/specs/README.md +0 -84
- elspais/trace_view/specs/tv-d00001-template-architecture.md +0 -36
- elspais/trace_view/specs/tv-d00002-css-extraction.md +0 -37
- elspais/trace_view/specs/tv-d00003-js-extraction.md +0 -43
- elspais/trace_view/specs/tv-d00004-build-embedding.md +0 -40
- elspais/trace_view/specs/tv-d00005-test-format.md +0 -78
- elspais/trace_view/specs/tv-d00010-review-data-models.md +0 -33
- elspais/trace_view/specs/tv-d00011-review-storage.md +0 -33
- elspais/trace_view/specs/tv-d00012-position-resolution.md +0 -33
- elspais/trace_view/specs/tv-d00013-git-branches.md +0 -31
- elspais/trace_view/specs/tv-d00014-review-api-server.md +0 -31
- elspais/trace_view/specs/tv-d00015-status-modifier.md +0 -27
- elspais/trace_view/specs/tv-d00016-js-integration.md +0 -33
- elspais/trace_view/specs/tv-p00001-html-generator.md +0 -33
- elspais/trace_view/specs/tv-p00002-review-system.md +0 -29
- elspais-0.11.2.dist-info/RECORD +0 -101
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/WHEEL +0 -0
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/entry_points.txt +0 -0
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1029 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TraceView Review Packages UI Module
|
|
3
|
-
*
|
|
4
|
-
* Provides UI for managing review packages:
|
|
5
|
-
* - Display collapsible packages panel
|
|
6
|
-
* - Create, edit, delete packages
|
|
7
|
-
* - Select active package for filtering
|
|
8
|
-
* - Add/remove REQs from packages
|
|
9
|
-
*
|
|
10
|
-
* IMPLEMENTS REQUIREMENTS:
|
|
11
|
-
* REQ-tv-d00016: Review JavaScript Integration
|
|
12
|
-
*
|
|
13
|
-
* =============================================================================
|
|
14
|
-
* FILTERING ARCHITECTURE
|
|
15
|
-
* =============================================================================
|
|
16
|
-
*
|
|
17
|
-
* This codebase uses a unified filter orchestrator (applyAllFilters) that
|
|
18
|
-
* coordinates multiple filtering mechanisms:
|
|
19
|
-
*
|
|
20
|
-
* CSS CLASSES:
|
|
21
|
-
* - filtered-out: View filter exclusion (display: none !important)
|
|
22
|
-
* - package-filtered: Package filter exclusion (display: none !important)
|
|
23
|
-
* - collapsed-by-parent: Hierarchy collapse (display: none)
|
|
24
|
-
* - in-active-package, in-other-package, not-in-package: Styling only
|
|
25
|
-
*
|
|
26
|
-
* HIERARCHY PROMOTION MODEL:
|
|
27
|
-
* Items are siblings with data-parent-instance-id (NOT DOM-nested).
|
|
28
|
-
* When filtering is active, matching items are "promoted" by removing
|
|
29
|
-
* collapsed-by-parent, making them visible regardless of parent state.
|
|
30
|
-
*
|
|
31
|
-
* FILTER PRECEDENCE (all ANDed):
|
|
32
|
-
* 1. View filters (filtered-out) - text inputs, dropdowns, checkboxes
|
|
33
|
-
* 2. Package filter (package-filtered) - "Show only Package REQs" toggle
|
|
34
|
-
* 3. Collapse state (collapsed-by-parent) - overridden by promotion when filtering
|
|
35
|
-
*
|
|
36
|
-
* ENTRY POINT:
|
|
37
|
-
* applyAllFilters() in generate_traceability.py - call this when ANY filter changes.
|
|
38
|
-
* Both applyFilters() and applyPackageContext() delegate to applyAllFilters().
|
|
39
|
-
*
|
|
40
|
-
* SINGLE RESPONSIBILITY:
|
|
41
|
-
* - computeViewFilterState(): Computes which items match view filters
|
|
42
|
-
* - computePackageFilterState(): Computes which items match package filter
|
|
43
|
-
* - applyViewFilterClasses(): Manages filtered-out class ONLY
|
|
44
|
-
* - applyPackageFilterClasses(): Manages package-* classes ONLY
|
|
45
|
-
* - applyPromotion(): Removes collapsed-by-parent for promoted items (ONE PLACE)
|
|
46
|
-
* - updateFilteredChildrenIcons(): Updates collapse icons (called ONCE)
|
|
47
|
-
* =============================================================================
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
(function() {
|
|
51
|
-
'use strict';
|
|
52
|
-
|
|
53
|
-
// Initialize TraceView.review if not exists
|
|
54
|
-
window.TraceView = window.TraceView || {};
|
|
55
|
-
TraceView.review = TraceView.review || { state: {} };
|
|
56
|
-
const review = TraceView.review;
|
|
57
|
-
|
|
58
|
-
// Package state
|
|
59
|
-
// REQ-d00095-B: No automatic "default" package - packages must be explicitly created
|
|
60
|
-
// REQ-d00099: Archive state for viewing archived packages
|
|
61
|
-
review.packages = {
|
|
62
|
-
items: [],
|
|
63
|
-
archivedItems: [], // REQ-d00099: Archived packages list
|
|
64
|
-
activeId: null,
|
|
65
|
-
panelExpanded: true,
|
|
66
|
-
archivePanelExpanded: false, // REQ-d00099: Archive viewer collapse state
|
|
67
|
-
filterEnabled: false, // Whether "Show only Package REQs" is active
|
|
68
|
-
isArchiveMode: false // REQ-d00099: Whether viewing archived package (read-only)
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// ==========================================================================
|
|
72
|
-
// Toast Notification
|
|
73
|
-
// ==========================================================================
|
|
74
|
-
|
|
75
|
-
let toastElement = null;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Show a toast notification positioned near the packages panel
|
|
79
|
-
*/
|
|
80
|
-
function showToast(message, showSpinner = false) {
|
|
81
|
-
if (!toastElement) {
|
|
82
|
-
toastElement = document.createElement('div');
|
|
83
|
-
toastElement.className = 'rs-toast';
|
|
84
|
-
document.body.appendChild(toastElement);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
toastElement.innerHTML = showSpinner
|
|
88
|
-
? `<div class="rs-toast-spinner"></div><span>${message}</span>`
|
|
89
|
-
: `<span>${message}</span>`;
|
|
90
|
-
|
|
91
|
-
// Position near the packages panel header
|
|
92
|
-
const packagesPanel = document.getElementById('reviewPackagesPanel');
|
|
93
|
-
if (packagesPanel) {
|
|
94
|
-
const rect = packagesPanel.getBoundingClientRect();
|
|
95
|
-
toastElement.style.top = `${rect.top + window.scrollY + 8}px`;
|
|
96
|
-
toastElement.style.left = `${rect.left + rect.width / 2}px`;
|
|
97
|
-
toastElement.style.transform = 'translateX(-50%) scale(0.9)';
|
|
98
|
-
} else {
|
|
99
|
-
// Fallback to fixed position
|
|
100
|
-
toastElement.style.position = 'fixed';
|
|
101
|
-
toastElement.style.top = '80px';
|
|
102
|
-
toastElement.style.left = '50%';
|
|
103
|
-
toastElement.style.transform = 'translateX(-50%) scale(0.9)';
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Force reflow then show
|
|
107
|
-
toastElement.offsetHeight;
|
|
108
|
-
toastElement.classList.add('visible');
|
|
109
|
-
if (packagesPanel) {
|
|
110
|
-
toastElement.style.transform = 'translateX(-50%) scale(1)';
|
|
111
|
-
} else {
|
|
112
|
-
toastElement.style.transform = 'translateX(-50%) scale(1)';
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Hide the toast notification
|
|
118
|
-
*/
|
|
119
|
-
function hideToast() {
|
|
120
|
-
if (toastElement) {
|
|
121
|
-
toastElement.classList.remove('visible');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ==========================================================================
|
|
126
|
-
// API Functions
|
|
127
|
-
// ==========================================================================
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Fetch all packages from the API
|
|
131
|
-
* REQ-d00095-B: No default package - packages must be explicitly created
|
|
132
|
-
*/
|
|
133
|
-
async function fetchPackages() {
|
|
134
|
-
try {
|
|
135
|
-
const response = await fetch('/api/reviews/packages');
|
|
136
|
-
if (!response.ok) {
|
|
137
|
-
throw new Error(`HTTP ${response.status}`);
|
|
138
|
-
}
|
|
139
|
-
const data = await response.json();
|
|
140
|
-
review.packages.items = data.packages || [];
|
|
141
|
-
review.packages.activeId = data.activePackageId || null;
|
|
142
|
-
|
|
143
|
-
// REQ-d00095-B: Validate activePackageId exists in packages list
|
|
144
|
-
if (review.packages.activeId) {
|
|
145
|
-
const exists = review.packages.items.some(p => p.packageId === review.packages.activeId);
|
|
146
|
-
if (!exists) {
|
|
147
|
-
console.warn('Stale activePackageId detected, clearing:', review.packages.activeId);
|
|
148
|
-
const staleId = review.packages.activeId;
|
|
149
|
-
review.packages.activeId = null;
|
|
150
|
-
// Persist the cleanup via API (async, don't await)
|
|
151
|
-
fetch('/api/reviews/packages/active', {
|
|
152
|
-
method: 'PUT',
|
|
153
|
-
headers: { 'Content-Type': 'application/json' },
|
|
154
|
-
body: JSON.stringify({ packageId: null, user: 'system' })
|
|
155
|
-
}).catch(err => console.error('Failed to clear stale activePackageId:', staleId, err));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
console.log('Packages loaded:', {
|
|
160
|
-
count: review.packages.items.length,
|
|
161
|
-
activeId: review.packages.activeId,
|
|
162
|
-
packages: review.packages.items.map(p => ({
|
|
163
|
-
id: p.packageId,
|
|
164
|
-
name: p.name,
|
|
165
|
-
reqCount: (p.reqIds || []).length
|
|
166
|
-
}))
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
return review.packages;
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.error('Failed to fetch packages:', error);
|
|
172
|
-
return { items: [], activeId: null };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Fetch archived packages from the API
|
|
178
|
-
* REQ-d00099: Read-only access to archived packages
|
|
179
|
-
*/
|
|
180
|
-
async function fetchArchivedPackages() {
|
|
181
|
-
try {
|
|
182
|
-
const response = await fetch('/api/reviews/archive');
|
|
183
|
-
if (!response.ok) {
|
|
184
|
-
throw new Error(`HTTP ${response.status}`);
|
|
185
|
-
}
|
|
186
|
-
const data = await response.json();
|
|
187
|
-
review.packages.archivedItems = data.packages || [];
|
|
188
|
-
|
|
189
|
-
console.log('Archived packages loaded:', {
|
|
190
|
-
count: review.packages.archivedItems.length,
|
|
191
|
-
packages: review.packages.archivedItems.map(p => ({
|
|
192
|
-
id: p.packageId,
|
|
193
|
-
name: p.name,
|
|
194
|
-
archiveReason: p.archiveReason
|
|
195
|
-
}))
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
return review.packages.archivedItems;
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error('Failed to fetch archived packages:', error);
|
|
201
|
-
return [];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Create a new package
|
|
207
|
-
*/
|
|
208
|
-
async function createPackage(name, description) {
|
|
209
|
-
const user = review.state.currentUser || 'anonymous';
|
|
210
|
-
try {
|
|
211
|
-
const response = await fetch('/api/reviews/packages', {
|
|
212
|
-
method: 'POST',
|
|
213
|
-
headers: { 'Content-Type': 'application/json' },
|
|
214
|
-
body: JSON.stringify({ name, description, user })
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
if (!response.ok) {
|
|
218
|
-
throw new Error(`HTTP ${response.status}`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const result = await response.json();
|
|
222
|
-
if (result.success) {
|
|
223
|
-
await fetchPackages();
|
|
224
|
-
renderPackagesPanel();
|
|
225
|
-
}
|
|
226
|
-
return result;
|
|
227
|
-
} catch (error) {
|
|
228
|
-
console.error('Failed to create package:', error);
|
|
229
|
-
return { success: false, error: error.message };
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Update a package's name or description
|
|
235
|
-
*/
|
|
236
|
-
async function updatePackage(packageId, updates) {
|
|
237
|
-
try {
|
|
238
|
-
const response = await fetch(`/api/reviews/packages/${packageId}`, {
|
|
239
|
-
method: 'PUT',
|
|
240
|
-
headers: { 'Content-Type': 'application/json' },
|
|
241
|
-
body: JSON.stringify(updates)
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
if (!response.ok) {
|
|
245
|
-
throw new Error(`HTTP ${response.status}`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const result = await response.json();
|
|
249
|
-
if (result.success) {
|
|
250
|
-
await fetchPackages();
|
|
251
|
-
renderPackagesPanel();
|
|
252
|
-
}
|
|
253
|
-
return result;
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error('Failed to update package:', error);
|
|
256
|
-
return { success: false, error: error.message };
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Delete a package (archives it per REQ-d00097-E)
|
|
262
|
-
* REQ-d00095-F: Package deletion SHALL archive (not destroy) the package
|
|
263
|
-
*/
|
|
264
|
-
async function deletePackage(packageId) {
|
|
265
|
-
try {
|
|
266
|
-
const response = await fetch(`/api/reviews/packages/${packageId}`, {
|
|
267
|
-
method: 'DELETE'
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
if (!response.ok) {
|
|
271
|
-
throw new Error(`HTTP ${response.status}`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const result = await response.json();
|
|
275
|
-
if (result.success) {
|
|
276
|
-
await fetchPackages();
|
|
277
|
-
await fetchArchivedPackages(); // REQ-d00097: Deleted packages go to archive
|
|
278
|
-
renderPackagesPanel();
|
|
279
|
-
}
|
|
280
|
-
return result;
|
|
281
|
-
} catch (error) {
|
|
282
|
-
console.error('Failed to delete package:', error);
|
|
283
|
-
return { success: false, error: error.message };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Manually archive a package
|
|
289
|
-
* REQ-d00097-D: Manual archive action
|
|
290
|
-
*/
|
|
291
|
-
async function archivePackage(packageId, reason = 'manual') {
|
|
292
|
-
const user = review.state.currentUser || 'anonymous';
|
|
293
|
-
try {
|
|
294
|
-
const response = await fetch(`/api/reviews/packages/${packageId}/archive`, {
|
|
295
|
-
method: 'POST',
|
|
296
|
-
headers: { 'Content-Type': 'application/json' },
|
|
297
|
-
body: JSON.stringify({ reason, user })
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
if (!response.ok) {
|
|
301
|
-
throw new Error(`HTTP ${response.status}`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const result = await response.json();
|
|
305
|
-
if (result.success) {
|
|
306
|
-
showToast(`Package archived (${reason})`);
|
|
307
|
-
await fetchPackages();
|
|
308
|
-
await fetchArchivedPackages();
|
|
309
|
-
renderPackagesPanel();
|
|
310
|
-
}
|
|
311
|
-
return result;
|
|
312
|
-
} catch (error) {
|
|
313
|
-
console.error('Failed to archive package:', error);
|
|
314
|
-
return { success: false, error: error.message };
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* View an archived package (read-only mode)
|
|
320
|
-
* REQ-d00099-B: Archived packages open in read-only mode
|
|
321
|
-
*/
|
|
322
|
-
async function viewArchivedPackage(packageId) {
|
|
323
|
-
try {
|
|
324
|
-
const response = await fetch(`/api/reviews/archive/${packageId}`);
|
|
325
|
-
if (!response.ok) {
|
|
326
|
-
throw new Error(`HTTP ${response.status}`);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const pkg = await response.json();
|
|
330
|
-
review.packages.isArchiveMode = true;
|
|
331
|
-
review.packages.viewingArchivedPackage = pkg;
|
|
332
|
-
|
|
333
|
-
// Trigger event for UI updates
|
|
334
|
-
document.dispatchEvent(new CustomEvent('traceview:archive-view-opened', {
|
|
335
|
-
detail: { package: pkg }
|
|
336
|
-
}));
|
|
337
|
-
|
|
338
|
-
// Render archive viewer
|
|
339
|
-
renderArchiveViewer(pkg);
|
|
340
|
-
return pkg;
|
|
341
|
-
} catch (error) {
|
|
342
|
-
console.error('Failed to view archived package:', error);
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Close archive viewer and exit read-only mode
|
|
349
|
-
*/
|
|
350
|
-
function closeArchiveViewer() {
|
|
351
|
-
review.packages.isArchiveMode = false;
|
|
352
|
-
review.packages.viewingArchivedPackage = null;
|
|
353
|
-
|
|
354
|
-
// Hide archive viewer
|
|
355
|
-
const viewer = document.getElementById('archiveViewer');
|
|
356
|
-
if (viewer) {
|
|
357
|
-
viewer.style.display = 'none';
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
document.dispatchEvent(new CustomEvent('traceview:archive-view-closed'));
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Set the active package and switch to its git branch
|
|
365
|
-
*/
|
|
366
|
-
async function setActivePackage(packageId) {
|
|
367
|
-
const user = review.state.currentUser || 'anonymous';
|
|
368
|
-
|
|
369
|
-
// Show toast when switching to a package (not when selecting None)
|
|
370
|
-
if (packageId) {
|
|
371
|
-
showToast('Syncing with GitHub...', true);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
try {
|
|
375
|
-
// 1. Set the active package in packages.json
|
|
376
|
-
const response = await fetch('/api/reviews/packages/active', {
|
|
377
|
-
method: 'PUT',
|
|
378
|
-
headers: { 'Content-Type': 'application/json' },
|
|
379
|
-
body: JSON.stringify({ packageId, user })
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
if (!response.ok) {
|
|
383
|
-
throw new Error(`HTTP ${response.status}`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const result = await response.json();
|
|
387
|
-
if (!result.success) {
|
|
388
|
-
hideToast();
|
|
389
|
-
return result;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
review.packages.activeId = packageId;
|
|
393
|
-
|
|
394
|
-
// 2. Switch to package branch (creates branch if needed)
|
|
395
|
-
if (packageId) {
|
|
396
|
-
await switchToPackageBranch(packageId, user);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// 3. Re-render panel to update radio buttons and highlights
|
|
400
|
-
renderPackagesPanel();
|
|
401
|
-
|
|
402
|
-
// 4. Apply context styling
|
|
403
|
-
applyPackageFilter();
|
|
404
|
-
|
|
405
|
-
// 5. Update git sync indicator to show new branch
|
|
406
|
-
if (review.updateGitSyncIndicator) {
|
|
407
|
-
review.updateGitSyncIndicator();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Toast is hidden in fetchConsolidatedPackageData after sync completes
|
|
411
|
-
// But if no packageId, hide it now
|
|
412
|
-
if (!packageId) {
|
|
413
|
-
hideToast();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return result;
|
|
417
|
-
} catch (error) {
|
|
418
|
-
console.error('Failed to set active package:', error);
|
|
419
|
-
hideToast();
|
|
420
|
-
return { success: false, error: error.message };
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Switch to a package branch for the current user
|
|
426
|
-
*/
|
|
427
|
-
async function switchToPackageBranch(packageId, user) {
|
|
428
|
-
try {
|
|
429
|
-
const response = await fetch('/api/reviews/packages/switch', {
|
|
430
|
-
method: 'POST',
|
|
431
|
-
headers: { 'Content-Type': 'application/json' },
|
|
432
|
-
body: JSON.stringify({ packageId, user })
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
if (!response.ok) {
|
|
436
|
-
throw new Error(`HTTP ${response.status}`);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const result = await response.json();
|
|
440
|
-
if (result.success) {
|
|
441
|
-
console.log(`Switched to branch: ${result.branch}`);
|
|
442
|
-
review.packages.currentBranch = result.branch;
|
|
443
|
-
|
|
444
|
-
// Re-fetch packages to get updated reqIds from new branch
|
|
445
|
-
await fetchPackages();
|
|
446
|
-
|
|
447
|
-
// Fetch consolidated data from all package branches
|
|
448
|
-
await fetchConsolidatedPackageData();
|
|
449
|
-
} else {
|
|
450
|
-
// Branch switch failed, hide the toast
|
|
451
|
-
hideToast();
|
|
452
|
-
}
|
|
453
|
-
return result;
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error('Failed to switch to package branch:', error);
|
|
456
|
-
hideToast();
|
|
457
|
-
return { success: false, error: error.message };
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Fetch consolidated review data from all users' branches for current package
|
|
463
|
-
*/
|
|
464
|
-
async function fetchConsolidatedPackageData() {
|
|
465
|
-
// Toast already shown by setActivePackage
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
const response = await fetch('/api/reviews/sync/fetch-all-package', {
|
|
469
|
-
method: 'POST'
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
if (!response.ok) {
|
|
473
|
-
throw new Error(`HTTP ${response.status}`);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const data = await response.json();
|
|
477
|
-
review.packages.contributors = data.contributors || [];
|
|
478
|
-
|
|
479
|
-
// If there's merged thread data, update the state
|
|
480
|
-
if (data.threads && Object.keys(data.threads).length > 0) {
|
|
481
|
-
console.log(`Loaded threads from ${data.contributors.length} contributor(s)`);
|
|
482
|
-
// Trigger refresh event so UI updates
|
|
483
|
-
document.dispatchEvent(new CustomEvent('traceview:data-fetched', {
|
|
484
|
-
detail: { data, timestamp: new Date() }
|
|
485
|
-
}));
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
hideToast();
|
|
489
|
-
return data;
|
|
490
|
-
} catch (error) {
|
|
491
|
-
console.error('Failed to fetch consolidated package data:', error);
|
|
492
|
-
hideToast();
|
|
493
|
-
return { threads: {}, flags: {}, contributors: [] };
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Get package contributors (users who have branches for this package)
|
|
499
|
-
*/
|
|
500
|
-
async function getPackageContributors(packageId) {
|
|
501
|
-
try {
|
|
502
|
-
const response = await fetch(`/api/reviews/packages/${packageId}/contributors`);
|
|
503
|
-
if (!response.ok) {
|
|
504
|
-
throw new Error(`HTTP ${response.status}`);
|
|
505
|
-
}
|
|
506
|
-
const data = await response.json();
|
|
507
|
-
return data.contributors || [];
|
|
508
|
-
} catch (error) {
|
|
509
|
-
console.error('Failed to get package contributors:', error);
|
|
510
|
-
return [];
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Get current package context from git branch
|
|
516
|
-
*/
|
|
517
|
-
async function getCurrentPackageContext() {
|
|
518
|
-
try {
|
|
519
|
-
const response = await fetch('/api/reviews/context');
|
|
520
|
-
if (!response.ok) {
|
|
521
|
-
throw new Error(`HTTP ${response.status}`);
|
|
522
|
-
}
|
|
523
|
-
const data = await response.json();
|
|
524
|
-
return data; // { packageId, user, branch } or null
|
|
525
|
-
} catch (error) {
|
|
526
|
-
console.error('Failed to get package context:', error);
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Add a REQ to a package
|
|
533
|
-
*/
|
|
534
|
-
async function addReqToPackage(packageId, reqId) {
|
|
535
|
-
try {
|
|
536
|
-
const response = await fetch(`/api/reviews/packages/${packageId}/reqs/${reqId}`, {
|
|
537
|
-
method: 'POST'
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
if (!response.ok) {
|
|
541
|
-
throw new Error(`HTTP ${response.status}`);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const result = await response.json();
|
|
545
|
-
if (result.success) {
|
|
546
|
-
// Update local state
|
|
547
|
-
const pkg = review.packages.items.find(p => p.packageId === packageId);
|
|
548
|
-
if (pkg && !pkg.reqIds.includes(reqId)) {
|
|
549
|
-
pkg.reqIds.push(reqId);
|
|
550
|
-
}
|
|
551
|
-
renderPackagesPanel();
|
|
552
|
-
}
|
|
553
|
-
return result;
|
|
554
|
-
} catch (error) {
|
|
555
|
-
console.error('Failed to add REQ to package:', error);
|
|
556
|
-
return { success: false, error: error.message };
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Remove a REQ from a package
|
|
562
|
-
*/
|
|
563
|
-
async function removeReqFromPackage(packageId, reqId) {
|
|
564
|
-
try {
|
|
565
|
-
const response = await fetch(`/api/reviews/packages/${packageId}/reqs/${reqId}`, {
|
|
566
|
-
method: 'DELETE'
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
if (!response.ok) {
|
|
570
|
-
throw new Error(`HTTP ${response.status}`);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const result = await response.json();
|
|
574
|
-
if (result.success) {
|
|
575
|
-
// Update local state
|
|
576
|
-
const pkg = review.packages.items.find(p => p.packageId === packageId);
|
|
577
|
-
if (pkg) {
|
|
578
|
-
pkg.reqIds = pkg.reqIds.filter(id => id !== reqId);
|
|
579
|
-
}
|
|
580
|
-
renderPackagesPanel();
|
|
581
|
-
}
|
|
582
|
-
return result;
|
|
583
|
-
} catch (error) {
|
|
584
|
-
console.error('Failed to remove REQ from package:', error);
|
|
585
|
-
return { success: false, error: error.message };
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Add REQ to active package
|
|
591
|
-
* REQ-d00095-B: Requires explicit package selection (no default)
|
|
592
|
-
*/
|
|
593
|
-
async function addReqToActivePackage(reqId) {
|
|
594
|
-
const packageId = review.packages.activeId;
|
|
595
|
-
if (!packageId) {
|
|
596
|
-
console.warn('addReqToActivePackage: No package selected. Select a package first.');
|
|
597
|
-
showToast('Please select a package first');
|
|
598
|
-
return { success: false, error: 'No package selected' };
|
|
599
|
-
}
|
|
600
|
-
return addReqToPackage(packageId, reqId);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// ==========================================================================
|
|
604
|
-
// UI Functions
|
|
605
|
-
// ==========================================================================
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Render the packages panel
|
|
609
|
-
* REQ-d00095-B: No default package concept
|
|
610
|
-
* REQ-d00099: Include archive viewer section
|
|
611
|
-
*/
|
|
612
|
-
function renderPackagesPanel() {
|
|
613
|
-
const panel = document.getElementById('reviewPackagesPanel');
|
|
614
|
-
if (!panel) return;
|
|
615
|
-
|
|
616
|
-
const packagesContent = panel.querySelector('.packages-content');
|
|
617
|
-
if (!packagesContent) return;
|
|
618
|
-
|
|
619
|
-
const items = review.packages.items;
|
|
620
|
-
const archivedItems = review.packages.archivedItems;
|
|
621
|
-
const activeId = review.packages.activeId;
|
|
622
|
-
|
|
623
|
-
// Build package list HTML
|
|
624
|
-
let html = '<div class="package-list">';
|
|
625
|
-
|
|
626
|
-
// "None" option (show all REQs)
|
|
627
|
-
html += `
|
|
628
|
-
<label class="package-item${!activeId ? ' active' : ''}">
|
|
629
|
-
<input type="radio" name="activePackage" value=""
|
|
630
|
-
${!activeId ? 'checked' : ''}
|
|
631
|
-
onchange="TraceView.review.setActivePackage(null)">
|
|
632
|
-
<span class="package-info">
|
|
633
|
-
<span class="package-name">None (Show All)</span>
|
|
634
|
-
<span class="package-desc">No package filter applied</span>
|
|
635
|
-
</span>
|
|
636
|
-
</label>
|
|
637
|
-
`;
|
|
638
|
-
|
|
639
|
-
// Package items - REQ-d00095-B: All packages are explicit (no default)
|
|
640
|
-
for (const pkg of items) {
|
|
641
|
-
const isActive = pkg.packageId === activeId;
|
|
642
|
-
const reqCount = pkg.reqIds ? pkg.reqIds.length : 0;
|
|
643
|
-
const reqIds = pkg.reqIds || [];
|
|
644
|
-
|
|
645
|
-
// Build REQ list HTML for active package
|
|
646
|
-
let reqListHtml = '';
|
|
647
|
-
if (isActive && reqIds.length > 0) {
|
|
648
|
-
reqListHtml = `
|
|
649
|
-
<div class="package-req-list">
|
|
650
|
-
${reqIds.map(reqId => `
|
|
651
|
-
<div class="package-req-item" data-req-id="${reqId}">
|
|
652
|
-
<span class="package-req-id" onclick="TraceView.panel.open('${reqId}')" title="View REQ-${reqId}">REQ-${reqId}</span>
|
|
653
|
-
<button class="rs-btn rs-btn-sm rs-btn-danger package-req-remove"
|
|
654
|
-
onclick="TraceView.review.removeReqFromPackage('${pkg.packageId}', '${reqId}'); event.stopPropagation();"
|
|
655
|
-
title="Remove from package">×</button>
|
|
656
|
-
</div>
|
|
657
|
-
`).join('')}
|
|
658
|
-
</div>
|
|
659
|
-
`;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
html += `
|
|
663
|
-
<div class="package-item-wrapper${isActive ? ' active' : ''}">
|
|
664
|
-
<label class="package-item${isActive ? ' active' : ''}">
|
|
665
|
-
<input type="radio" name="activePackage" value="${pkg.packageId}"
|
|
666
|
-
${isActive ? 'checked' : ''}
|
|
667
|
-
onchange="TraceView.review.setActivePackage('${pkg.packageId}')">
|
|
668
|
-
<span class="package-info">
|
|
669
|
-
<span class="package-name">${escapeHtml(pkg.name)}</span>
|
|
670
|
-
<span class="package-desc">${escapeHtml(pkg.description || '')}</span>
|
|
671
|
-
</span>
|
|
672
|
-
<span class="package-count">${reqCount}</span>
|
|
673
|
-
<span class="package-actions">
|
|
674
|
-
<button class="rs-btn rs-btn-sm" onclick="TraceView.review.editPackageDialog('${pkg.packageId}', event)" title="Edit">
|
|
675
|
-
✎
|
|
676
|
-
</button>
|
|
677
|
-
<button class="rs-btn rs-btn-sm" onclick="TraceView.review.confirmArchivePackage('${pkg.packageId}', event)" title="Archive">
|
|
678
|
-
🗃
|
|
679
|
-
</button>
|
|
680
|
-
<button class="rs-btn rs-btn-sm rs-btn-danger" onclick="TraceView.review.confirmDeletePackage('${pkg.packageId}', event)" title="Delete (archives)">
|
|
681
|
-
×
|
|
682
|
-
</button>
|
|
683
|
-
</span>
|
|
684
|
-
</label>
|
|
685
|
-
${reqListHtml}
|
|
686
|
-
</div>
|
|
687
|
-
`;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
html += '</div>';
|
|
691
|
-
|
|
692
|
-
// REQ-d00099: Archive viewer section
|
|
693
|
-
if (archivedItems.length > 0) {
|
|
694
|
-
const archiveExpanded = review.packages.archivePanelExpanded;
|
|
695
|
-
html += `
|
|
696
|
-
<div class="archive-section">
|
|
697
|
-
<div class="archive-header" onclick="TraceView.review.toggleArchivePanel()">
|
|
698
|
-
<span class="collapse-icon">${archiveExpanded ? '▼' : '▶'}</span>
|
|
699
|
-
<h4>Archived Packages (${archivedItems.length})</h4>
|
|
700
|
-
</div>
|
|
701
|
-
<div class="archive-list" style="${archiveExpanded ? '' : 'display: none;'}">
|
|
702
|
-
${archivedItems.map(pkg => `
|
|
703
|
-
<div class="archive-item" onclick="TraceView.review.viewArchivedPackage('${pkg.packageId}')">
|
|
704
|
-
<span class="archive-item-name">${escapeHtml(pkg.name)}</span>
|
|
705
|
-
<span class="archive-item-reason rs-badge rs-badge-${pkg.archiveReason || 'manual'}">${pkg.archiveReason || 'archived'}</span>
|
|
706
|
-
<span class="archive-item-date" title="${pkg.archivedAt || ''}">${formatArchiveDate(pkg.archivedAt)}</span>
|
|
707
|
-
</div>
|
|
708
|
-
`).join('')}
|
|
709
|
-
</div>
|
|
710
|
-
</div>
|
|
711
|
-
`;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
packagesContent.innerHTML = html;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Format archive date for display
|
|
719
|
-
*/
|
|
720
|
-
function formatArchiveDate(isoDate) {
|
|
721
|
-
if (!isoDate) return '';
|
|
722
|
-
try {
|
|
723
|
-
const date = new Date(isoDate);
|
|
724
|
-
return date.toLocaleDateString();
|
|
725
|
-
} catch (e) {
|
|
726
|
-
return '';
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* Toggle archive panel expansion
|
|
732
|
-
* REQ-d00099: Archive viewer
|
|
733
|
-
*/
|
|
734
|
-
function toggleArchivePanel() {
|
|
735
|
-
review.packages.archivePanelExpanded = !review.packages.archivePanelExpanded;
|
|
736
|
-
renderPackagesPanel();
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Render archive viewer for a specific archived package
|
|
741
|
-
* REQ-d00099: Read-only archive viewer
|
|
742
|
-
*/
|
|
743
|
-
function renderArchiveViewer(pkg) {
|
|
744
|
-
let viewer = document.getElementById('archiveViewer');
|
|
745
|
-
if (!viewer) {
|
|
746
|
-
viewer = document.createElement('div');
|
|
747
|
-
viewer.id = 'archiveViewer';
|
|
748
|
-
viewer.className = 'archive-viewer';
|
|
749
|
-
document.body.appendChild(viewer);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// REQ-d00099-D: Display archival metadata
|
|
753
|
-
// REQ-d00099-E: Display git audit trail information
|
|
754
|
-
viewer.innerHTML = `
|
|
755
|
-
<div class="archive-viewer-overlay" onclick="TraceView.review.closeArchiveViewer()"></div>
|
|
756
|
-
<div class="archive-viewer-content">
|
|
757
|
-
<div class="archive-viewer-header">
|
|
758
|
-
<h3>${escapeHtml(pkg.name)} <span class="archive-badge">ARCHIVED</span></h3>
|
|
759
|
-
<button class="rs-btn rs-btn-link" onclick="TraceView.review.closeArchiveViewer()">×</button>
|
|
760
|
-
</div>
|
|
761
|
-
<div class="archive-viewer-meta">
|
|
762
|
-
<div class="archive-meta-row">
|
|
763
|
-
<span class="archive-meta-label">Archive Reason:</span>
|
|
764
|
-
<span class="archive-meta-value rs-badge rs-badge-${pkg.archiveReason || 'manual'}">${pkg.archiveReason || 'archived'}</span>
|
|
765
|
-
</div>
|
|
766
|
-
<div class="archive-meta-row">
|
|
767
|
-
<span class="archive-meta-label">Archived At:</span>
|
|
768
|
-
<span class="archive-meta-value">${pkg.archivedAt || 'Unknown'}</span>
|
|
769
|
-
</div>
|
|
770
|
-
<div class="archive-meta-row">
|
|
771
|
-
<span class="archive-meta-label">Archived By:</span>
|
|
772
|
-
<span class="archive-meta-value">${escapeHtml(pkg.archivedBy || 'Unknown')}</span>
|
|
773
|
-
</div>
|
|
774
|
-
${pkg.branchName ? `
|
|
775
|
-
<div class="archive-meta-row">
|
|
776
|
-
<span class="archive-meta-label">Branch:</span>
|
|
777
|
-
<span class="archive-meta-value">${escapeHtml(pkg.branchName)}</span>
|
|
778
|
-
</div>
|
|
779
|
-
` : ''}
|
|
780
|
-
${pkg.creationCommitHash ? `
|
|
781
|
-
<div class="archive-meta-row">
|
|
782
|
-
<span class="archive-meta-label">Creation Commit:</span>
|
|
783
|
-
<span class="archive-meta-value commit-hash" title="May not exist if branch was squash-merged">${pkg.creationCommitHash.substring(0, 7)}</span>
|
|
784
|
-
</div>
|
|
785
|
-
` : ''}
|
|
786
|
-
${pkg.lastReviewedCommitHash ? `
|
|
787
|
-
<div class="archive-meta-row">
|
|
788
|
-
<span class="archive-meta-label">Last Reviewed Commit:</span>
|
|
789
|
-
<span class="archive-meta-value commit-hash" title="May not exist if branch was squash-merged">${pkg.lastReviewedCommitHash.substring(0, 7)}</span>
|
|
790
|
-
</div>
|
|
791
|
-
` : ''}
|
|
792
|
-
</div>
|
|
793
|
-
<div class="archive-viewer-body">
|
|
794
|
-
<p class="archive-readonly-notice">This package is archived and read-only. Comments cannot be added or modified.</p>
|
|
795
|
-
${pkg.description ? `<p class="archive-description">${escapeHtml(pkg.description)}</p>` : ''}
|
|
796
|
-
<div class="archive-reqs">
|
|
797
|
-
<h4>Requirements (${(pkg.reqIds || []).length})</h4>
|
|
798
|
-
<div class="archive-req-list">
|
|
799
|
-
${(pkg.reqIds || []).map(reqId => `
|
|
800
|
-
<span class="archive-req-badge">REQ-${reqId}</span>
|
|
801
|
-
`).join(' ')}
|
|
802
|
-
</div>
|
|
803
|
-
</div>
|
|
804
|
-
</div>
|
|
805
|
-
<div class="archive-viewer-footer">
|
|
806
|
-
<button class="rs-btn" onclick="TraceView.review.closeArchiveViewer()">Close</button>
|
|
807
|
-
</div>
|
|
808
|
-
</div>
|
|
809
|
-
`;
|
|
810
|
-
|
|
811
|
-
viewer.style.display = 'flex';
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
/**
|
|
815
|
-
* Toggle packages panel expansion
|
|
816
|
-
*/
|
|
817
|
-
function togglePackagesPanel() {
|
|
818
|
-
const panel = document.getElementById('reviewPackagesPanel');
|
|
819
|
-
if (!panel) return;
|
|
820
|
-
|
|
821
|
-
review.packages.panelExpanded = !review.packages.panelExpanded;
|
|
822
|
-
panel.classList.toggle('collapsed', !review.packages.panelExpanded);
|
|
823
|
-
|
|
824
|
-
const icon = panel.querySelector('.collapse-icon');
|
|
825
|
-
if (icon) {
|
|
826
|
-
icon.textContent = review.packages.panelExpanded ? '\u25BC' : '\u25B6';
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
/**
|
|
831
|
-
* Show create package dialog
|
|
832
|
-
*/
|
|
833
|
-
function showCreatePackageDialog(event) {
|
|
834
|
-
if (event) event.stopPropagation();
|
|
835
|
-
|
|
836
|
-
const name = prompt('Package name:');
|
|
837
|
-
if (!name || !name.trim()) return;
|
|
838
|
-
|
|
839
|
-
const description = prompt('Package description (optional):') || '';
|
|
840
|
-
createPackage(name.trim(), description.trim());
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
/**
|
|
844
|
-
* Show edit package dialog
|
|
845
|
-
*/
|
|
846
|
-
function editPackageDialog(packageId, event) {
|
|
847
|
-
if (event) event.stopPropagation();
|
|
848
|
-
|
|
849
|
-
const pkg = review.packages.items.find(p => p.packageId === packageId);
|
|
850
|
-
if (!pkg) return;
|
|
851
|
-
|
|
852
|
-
const name = prompt('Package name:', pkg.name);
|
|
853
|
-
if (!name || !name.trim()) return;
|
|
854
|
-
|
|
855
|
-
const description = prompt('Package description:', pkg.description || '');
|
|
856
|
-
updatePackage(packageId, {
|
|
857
|
-
name: name.trim(),
|
|
858
|
-
description: description ? description.trim() : ''
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
/**
|
|
863
|
-
* Confirm and delete package (archives it per REQ-d00095-F)
|
|
864
|
-
*/
|
|
865
|
-
function confirmDeletePackage(packageId, event) {
|
|
866
|
-
if (event) event.stopPropagation();
|
|
867
|
-
|
|
868
|
-
const pkg = review.packages.items.find(p => p.packageId === packageId);
|
|
869
|
-
if (!pkg) return;
|
|
870
|
-
|
|
871
|
-
if (confirm(`Delete package "${pkg.name}"?\n\nThis will archive the package (not destroy it). REQs will not be deleted.`)) {
|
|
872
|
-
deletePackage(packageId);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* Confirm and archive a package manually
|
|
878
|
-
* REQ-d00097-D: Manual archive action
|
|
879
|
-
*/
|
|
880
|
-
function confirmArchivePackage(packageId, event) {
|
|
881
|
-
if (event) event.stopPropagation();
|
|
882
|
-
|
|
883
|
-
const pkg = review.packages.items.find(p => p.packageId === packageId);
|
|
884
|
-
if (!pkg) return;
|
|
885
|
-
|
|
886
|
-
if (confirm(`Archive package "${pkg.name}"?\n\nArchived packages are read-only but preserved for audit purposes.`)) {
|
|
887
|
-
archivePackage(packageId, 'manual');
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* Apply package context and optional filtering to the requirement tree.
|
|
893
|
-
* Context (activeId) determines which package new REQs are added to.
|
|
894
|
-
* Filter (filterEnabled) determines whether to hide non-package REQs.
|
|
895
|
-
*
|
|
896
|
-
* NOTE: This function delegates to the unified applyAllFilters() orchestrator
|
|
897
|
-
* which handles all filtering (view filters, package filters, and hierarchy promotion)
|
|
898
|
-
* in a single, consistent pass. See generate_traceability.py for the implementation.
|
|
899
|
-
*/
|
|
900
|
-
function applyPackageContext() {
|
|
901
|
-
// Update context indicator UI
|
|
902
|
-
updateContextIndicator(review.packages.activeId);
|
|
903
|
-
|
|
904
|
-
// Delegate all filtering to the unified orchestrator
|
|
905
|
-
// This handles: view filters, package filters, hierarchy promotion, icon updates, and stats
|
|
906
|
-
if (typeof applyAllFilters === 'function') {
|
|
907
|
-
applyAllFilters();
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* Update context indicator in UI
|
|
913
|
-
*/
|
|
914
|
-
function updateContextIndicator(activeId) {
|
|
915
|
-
const indicator = document.getElementById('packageContextIndicator');
|
|
916
|
-
if (!indicator) return;
|
|
917
|
-
|
|
918
|
-
if (activeId) {
|
|
919
|
-
const pkg = review.packages.items.find(p => p.packageId === activeId);
|
|
920
|
-
const name = pkg ? pkg.name : 'Unknown';
|
|
921
|
-
indicator.textContent = `Context: ${name}`;
|
|
922
|
-
} else {
|
|
923
|
-
indicator.textContent = 'Context: Default';
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* Toggle the package filter on/off
|
|
929
|
-
*/
|
|
930
|
-
function togglePackageFilter(event) {
|
|
931
|
-
if (event) event.stopPropagation();
|
|
932
|
-
|
|
933
|
-
review.packages.filterEnabled = !review.packages.filterEnabled;
|
|
934
|
-
|
|
935
|
-
// Update toggle button styling
|
|
936
|
-
const toggle = document.getElementById('packageFilterToggle');
|
|
937
|
-
if (toggle) {
|
|
938
|
-
toggle.classList.toggle('active', review.packages.filterEnabled);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// Re-apply filtering
|
|
942
|
-
applyPackageContext();
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// Alias for backwards compatibility
|
|
946
|
-
function applyPackageFilter() {
|
|
947
|
-
applyPackageContext();
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
/**
|
|
951
|
-
* Escape HTML special characters
|
|
952
|
-
*/
|
|
953
|
-
function escapeHtml(text) {
|
|
954
|
-
const div = document.createElement('div');
|
|
955
|
-
div.textContent = text;
|
|
956
|
-
return div.innerHTML;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
/**
|
|
960
|
-
* Initialize packages panel when review mode is activated
|
|
961
|
-
* REQ-d00099: Also fetch archived packages
|
|
962
|
-
*/
|
|
963
|
-
async function initPackagesPanel() {
|
|
964
|
-
await Promise.all([
|
|
965
|
-
fetchPackages(),
|
|
966
|
-
fetchArchivedPackages() // REQ-d00099: Load archived packages
|
|
967
|
-
]);
|
|
968
|
-
renderPackagesPanel();
|
|
969
|
-
applyPackageFilter();
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// ==========================================================================
|
|
973
|
-
// Export Functions
|
|
974
|
-
// ==========================================================================
|
|
975
|
-
|
|
976
|
-
review.fetchPackages = fetchPackages;
|
|
977
|
-
review.fetchArchivedPackages = fetchArchivedPackages; // REQ-d00099
|
|
978
|
-
review.createPackage = createPackage;
|
|
979
|
-
review.updatePackage = updatePackage;
|
|
980
|
-
review.deletePackage = deletePackage;
|
|
981
|
-
review.archivePackage = archivePackage; // REQ-d00097
|
|
982
|
-
review.viewArchivedPackage = viewArchivedPackage; // REQ-d00099
|
|
983
|
-
review.closeArchiveViewer = closeArchiveViewer; // REQ-d00099
|
|
984
|
-
review.setActivePackage = setActivePackage;
|
|
985
|
-
review.switchToPackageBranch = switchToPackageBranch;
|
|
986
|
-
review.fetchConsolidatedPackageData = fetchConsolidatedPackageData;
|
|
987
|
-
review.getPackageContributors = getPackageContributors;
|
|
988
|
-
review.getCurrentPackageContext = getCurrentPackageContext;
|
|
989
|
-
review.addReqToPackage = addReqToPackage;
|
|
990
|
-
review.removeReqFromPackage = removeReqFromPackage;
|
|
991
|
-
review.addReqToActivePackage = addReqToActivePackage;
|
|
992
|
-
review.renderPackagesPanel = renderPackagesPanel;
|
|
993
|
-
review.togglePackagesPanel = togglePackagesPanel;
|
|
994
|
-
review.toggleArchivePanel = toggleArchivePanel; // REQ-d00099
|
|
995
|
-
review.togglePackageFilter = togglePackageFilter;
|
|
996
|
-
review.showCreatePackageDialog = showCreatePackageDialog;
|
|
997
|
-
review.editPackageDialog = editPackageDialog;
|
|
998
|
-
review.confirmDeletePackage = confirmDeletePackage;
|
|
999
|
-
review.confirmArchivePackage = confirmArchivePackage; // REQ-d00097
|
|
1000
|
-
review.initPackagesPanel = initPackagesPanel;
|
|
1001
|
-
review.applyPackageFilter = applyPackageFilter;
|
|
1002
|
-
review.applyPackageContext = applyPackageContext;
|
|
1003
|
-
|
|
1004
|
-
})();
|
|
1005
|
-
|
|
1006
|
-
// Export to ReviewSystem alias with RS. pattern (REQ-d00092)
|
|
1007
|
-
window.ReviewSystem = window.ReviewSystem || {};
|
|
1008
|
-
var RS = window.ReviewSystem;
|
|
1009
|
-
|
|
1010
|
-
// Initialize packages state on ReviewSystem (REQ-d00092)
|
|
1011
|
-
RS.packages = {
|
|
1012
|
-
items: [],
|
|
1013
|
-
activeId: null,
|
|
1014
|
-
filterEnabled: false
|
|
1015
|
-
};
|
|
1016
|
-
|
|
1017
|
-
// Export package functions
|
|
1018
|
-
RS.renderPackagesPanel = TraceView.review.renderPackagesPanel;
|
|
1019
|
-
RS.togglePackagesPanel = TraceView.review.togglePackagesPanel;
|
|
1020
|
-
RS.togglePackageFilter = TraceView.review.togglePackageFilter;
|
|
1021
|
-
RS.toggleArchivePanel = TraceView.review.toggleArchivePanel; // REQ-d00099
|
|
1022
|
-
RS.showCreatePackageDialog = TraceView.review.showCreatePackageDialog;
|
|
1023
|
-
RS.setActivePackage = TraceView.review.setActivePackage;
|
|
1024
|
-
RS.initPackagesPanel = TraceView.review.initPackagesPanel;
|
|
1025
|
-
RS.applyPackageFilter = TraceView.review.applyPackageFilter;
|
|
1026
|
-
RS.archivePackage = TraceView.review.archivePackage; // REQ-d00097
|
|
1027
|
-
RS.viewArchivedPackage = TraceView.review.viewArchivedPackage; // REQ-d00099
|
|
1028
|
-
RS.closeArchiveViewer = TraceView.review.closeArchiveViewer; // REQ-d00099
|
|
1029
|
-
RS.confirmArchivePackage = TraceView.review.confirmArchivePackage; // REQ-d00097
|