@beastmode-develeap/beastmode 0.1.205 → 0.1.207
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/board.html +190 -6
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/web/board.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
17
|
<!--BOARD_DATA-->
|
|
18
|
-
<script>window.__BUILD_STAMP__ = "20260509-
|
|
18
|
+
<script>window.__BUILD_STAMP__ = "20260509-122800-7350789";</script>
|
|
19
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
21
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -1522,6 +1522,37 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
1522
1522
|
margin: 0 -8px;
|
|
1523
1523
|
padding: 8px;
|
|
1524
1524
|
}
|
|
1525
|
+
/* Audit Trail — Superseded transition history (Story 6) */
|
|
1526
|
+
.audit-timeline { display: flex; flex-direction: column; gap: 8px; }
|
|
1527
|
+
.audit-entry {
|
|
1528
|
+
background: var(--bg-input);
|
|
1529
|
+
border-radius: 8px;
|
|
1530
|
+
padding: 10px 12px;
|
|
1531
|
+
border-left: 3px solid var(--accent);
|
|
1532
|
+
}
|
|
1533
|
+
.audit-entry-header {
|
|
1534
|
+
display: flex;
|
|
1535
|
+
justify-content: space-between;
|
|
1536
|
+
align-items: center;
|
|
1537
|
+
margin-bottom: 4px;
|
|
1538
|
+
}
|
|
1539
|
+
.audit-actor { font-weight: 600; font-size: 13px; }
|
|
1540
|
+
.audit-time { color: var(--text-muted); font-size: 12px; }
|
|
1541
|
+
.audit-entry-body { font-size: 13px; display: flex; flex-direction: column; gap: 4px; }
|
|
1542
|
+
.audit-from { color: var(--text-muted); }
|
|
1543
|
+
.audit-successor a, .audit-cascade a {
|
|
1544
|
+
color: var(--accent);
|
|
1545
|
+
text-decoration: none;
|
|
1546
|
+
}
|
|
1547
|
+
.audit-successor a:hover, .audit-cascade a:hover { text-decoration: underline; }
|
|
1548
|
+
.audit-reason {
|
|
1549
|
+
margin-top: 4px;
|
|
1550
|
+
padding: 6px 8px;
|
|
1551
|
+
background: var(--bg-secondary);
|
|
1552
|
+
border-radius: 4px;
|
|
1553
|
+
font-style: italic;
|
|
1554
|
+
color: var(--text-secondary);
|
|
1555
|
+
}
|
|
1525
1556
|
.btn-env-deploy {
|
|
1526
1557
|
display: inline-flex;
|
|
1527
1558
|
align-items: center;
|
|
@@ -4260,7 +4291,7 @@ const STATUSES = [
|
|
|
4260
4291
|
'Waiting for Infra Approval', 'Provisioning Infra', 'Infra Ready',
|
|
4261
4292
|
'Approved & Merge to Main', 'Ready For Review',
|
|
4262
4293
|
'Verifying Prod with Tests', 'Verifying Epic', 'Waiting for Epic Bugs', 'Awaiting Input',
|
|
4263
|
-
'Epic Breakdown Posted', 'Stuck', 'Done',
|
|
4294
|
+
'Epic Breakdown Posted', 'Stuck', 'Done', 'Superseded',
|
|
4264
4295
|
];
|
|
4265
4296
|
|
|
4266
4297
|
const KANBAN_COLUMNS = [
|
|
@@ -5095,12 +5126,88 @@ function DeployModal({ envName, refs, loading, item, selectedProject, onClose })
|
|
|
5095
5126
|
`;
|
|
5096
5127
|
}
|
|
5097
5128
|
|
|
5129
|
+
// ── Superseded Modal ──
|
|
5130
|
+
|
|
5131
|
+
function SupersededModal({ item, onConfirm, onCancel }) {
|
|
5132
|
+
const [supersededBy, setSupersededBy] = useState('');
|
|
5133
|
+
const [reason, setReason] = useState('');
|
|
5134
|
+
const [error, setError] = useState('');
|
|
5135
|
+
const [loading, setLoading] = useState(false);
|
|
5136
|
+
|
|
5137
|
+
const isEpic = (item.task_type || '').toLowerCase() === 'epic';
|
|
5138
|
+
|
|
5139
|
+
useEffect(() => {
|
|
5140
|
+
const handleEsc = (e) => { if (e.key === 'Escape') onCancel(); };
|
|
5141
|
+
document.addEventListener('keydown', handleEsc);
|
|
5142
|
+
return () => document.removeEventListener('keydown', handleEsc);
|
|
5143
|
+
}, []);
|
|
5144
|
+
|
|
5145
|
+
const handleSubmit = async () => {
|
|
5146
|
+
if (isEpic && !supersededBy.trim()) {
|
|
5147
|
+
setError('Epics require a successor item ID (superseded_by).');
|
|
5148
|
+
return;
|
|
5149
|
+
}
|
|
5150
|
+
if (isEpic && !reason.trim()) {
|
|
5151
|
+
setError('Epics require a supersede reason.');
|
|
5152
|
+
return;
|
|
5153
|
+
}
|
|
5154
|
+
setLoading(true);
|
|
5155
|
+
setError('');
|
|
5156
|
+
try {
|
|
5157
|
+
const payload = { status: 'Superseded' };
|
|
5158
|
+
const extra = {};
|
|
5159
|
+
if (supersededBy.trim()) extra.superseded_by = supersededBy.trim();
|
|
5160
|
+
if (reason.trim()) extra.superseded_reason = reason.trim();
|
|
5161
|
+
if (Object.keys(extra).length > 0) payload.extra_columns = extra;
|
|
5162
|
+
await api('PATCH', '/api/board/items/' + item.id, payload);
|
|
5163
|
+
onConfirm();
|
|
5164
|
+
} catch (err) {
|
|
5165
|
+
setError(err.message || 'Failed to update status');
|
|
5166
|
+
} finally {
|
|
5167
|
+
setLoading(false);
|
|
5168
|
+
}
|
|
5169
|
+
};
|
|
5170
|
+
|
|
5171
|
+
return html`
|
|
5172
|
+
<div class="deploy-modal-overlay" data-testid="superseded-modal-overlay"
|
|
5173
|
+
onClick=${(e) => { if (e.target === e.currentTarget) onCancel(); }}>
|
|
5174
|
+
<div class="deploy-modal" data-testid="superseded-modal">
|
|
5175
|
+
<h3>Mark as Superseded</h3>
|
|
5176
|
+
<p style="font-size:13px;color:var(--text-muted);margin:0 0 12px;">
|
|
5177
|
+
This action is permanent. Superseded items cannot be reactivated.
|
|
5178
|
+
</p>
|
|
5179
|
+
${error && html`<div class="error-banner" style="margin-bottom:12px;">${error}</div>`}
|
|
5180
|
+
<label>Successor item ID ${isEpic ? html`<span style="color:var(--danger);">*</span>` : '(optional)'}</label>
|
|
5181
|
+
<input type="text" value=${supersededBy}
|
|
5182
|
+
data-testid="superseded-by-input"
|
|
5183
|
+
placeholder="e.g. 42"
|
|
5184
|
+
onInput=${(e) => setSupersededBy(e.target.value)}
|
|
5185
|
+
style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;box-sizing:border-box;" />
|
|
5186
|
+
<label>Reason ${isEpic ? html`<span style="color:var(--danger);">*</span>` : '(optional)'}</label>
|
|
5187
|
+
<textarea value=${reason}
|
|
5188
|
+
data-testid="superseded-reason-input"
|
|
5189
|
+
placeholder="Why is this item being superseded?"
|
|
5190
|
+
onInput=${(e) => setReason(e.target.value)}
|
|
5191
|
+
style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;box-sizing:border-box;resize:vertical;min-height:60px;" />
|
|
5192
|
+
<div class="deploy-modal-actions">
|
|
5193
|
+
<button onClick=${onCancel} disabled=${loading}>Cancel</button>
|
|
5194
|
+
<button class="btn-primary" onClick=${handleSubmit} disabled=${loading}
|
|
5195
|
+
data-testid="superseded-confirm-btn">
|
|
5196
|
+
${loading ? 'Updating...' : 'Mark Superseded'}
|
|
5197
|
+
</button>
|
|
5198
|
+
</div>
|
|
5199
|
+
</div>
|
|
5200
|
+
</div>
|
|
5201
|
+
`;
|
|
5202
|
+
}
|
|
5203
|
+
|
|
5098
5204
|
// ── Item Detail Sidebar ──
|
|
5099
5205
|
|
|
5100
|
-
function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
5206
|
+
function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject, allItems, onSelectItem }) {
|
|
5101
5207
|
const [updates, setUpdates] = useState([]);
|
|
5102
5208
|
const [loadingUpdates, setLoadingUpdates] = useState(true);
|
|
5103
5209
|
const [sortNewest, setSortNewest] = useState(true);
|
|
5210
|
+
const [showSupersededModal, setShowSupersededModal] = useState(false);
|
|
5104
5211
|
// Attachments (Gap 8a — 2026-04-15): list every attachment the
|
|
5105
5212
|
// board service has for this item, render image types as thumbnails
|
|
5106
5213
|
// that open in a new tab when clicked.
|
|
@@ -5111,6 +5218,7 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5111
5218
|
const [phaseData, setPhaseData] = useState([]);
|
|
5112
5219
|
const [envTimeline, setEnvTimeline] = useState(null);
|
|
5113
5220
|
const [deployModal, setDeployModal] = useState(null);
|
|
5221
|
+
const [auditEvents, setAuditEvents] = useState([]);
|
|
5114
5222
|
const sidebarRef = useRef(null);
|
|
5115
5223
|
const topCommentRef = useRef(null);
|
|
5116
5224
|
|
|
@@ -5132,6 +5240,25 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5132
5240
|
.catch(() => setEnvTimeline(null));
|
|
5133
5241
|
}, [item && item.id, item && item.extra && item.extra.current_env]);
|
|
5134
5242
|
|
|
5243
|
+
// FR-4: Fetch SupersededTransition audit events for the selected item.
|
|
5244
|
+
// Only fires when the item is in Superseded status — non-Superseded items
|
|
5245
|
+
// never have audit entries and the section is hidden anyway (NFR-4).
|
|
5246
|
+
useEffect(() => {
|
|
5247
|
+
if (!item || item.status !== 'Superseded') {
|
|
5248
|
+
setAuditEvents([]);
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
const proj = selectedProject && selectedProject !== 'all' ? selectedProject : null;
|
|
5252
|
+
const qs = proj ? '?board=' + encodeURIComponent(proj) : '';
|
|
5253
|
+
const sep = qs ? '&' : '?';
|
|
5254
|
+
const url = '/api/events' + qs + sep + 'item_id=' + encodeURIComponent(item.id) +
|
|
5255
|
+
'&type=SupersededTransition';
|
|
5256
|
+
fetch(url)
|
|
5257
|
+
.then(r => r.ok ? r.json() : [])
|
|
5258
|
+
.then(events => setAuditEvents(Array.isArray(events) ? events : []))
|
|
5259
|
+
.catch(() => setAuditEvents([]));
|
|
5260
|
+
}, [item && item.id, item && item.status]);
|
|
5261
|
+
|
|
5135
5262
|
// Cost summary fetch — keyed on item.id, refreshed alongside the
|
|
5136
5263
|
// 10-second updates/attachments poll below. The api() helper only
|
|
5137
5264
|
// auto-scopes /api/board/*, so append ?board=<proj> manually.
|
|
@@ -5300,9 +5427,17 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5300
5427
|
</div>
|
|
5301
5428
|
<div class="detail-fields">
|
|
5302
5429
|
<label>Status</label>
|
|
5303
|
-
<select value=${item.status || 'New'}
|
|
5430
|
+
<select value=${item.status || 'New'}
|
|
5431
|
+
disabled=${item.status === 'Superseded'}
|
|
5432
|
+
onChange=${async (e) => {
|
|
5433
|
+
const newStatus = e.target.value;
|
|
5434
|
+
if (newStatus === 'Superseded') {
|
|
5435
|
+
e.target.value = item.status || 'New';
|
|
5436
|
+
setShowSupersededModal(true);
|
|
5437
|
+
return;
|
|
5438
|
+
}
|
|
5304
5439
|
try {
|
|
5305
|
-
await api('PATCH', '/api/board/items/' + item.id, { status:
|
|
5440
|
+
await api('PATCH', '/api/board/items/' + item.id, { status: newStatus });
|
|
5306
5441
|
onStatusChange();
|
|
5307
5442
|
} catch (err) { /* ignore */ }
|
|
5308
5443
|
}}>
|
|
@@ -5451,6 +5586,49 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5451
5586
|
</div>
|
|
5452
5587
|
</div>
|
|
5453
5588
|
`}
|
|
5589
|
+
${item && item.status === 'Superseded' && auditEvents.length > 0 && html`
|
|
5590
|
+
<div class="detail-section" style="padding:12px 24px 0;" data-testid="audit-trail-section">
|
|
5591
|
+
<h4 class="detail-section-title" style="margin:0 0 8px 0;font-size:13px;font-weight:600;">Audit Trail</h4>
|
|
5592
|
+
<div class="audit-timeline">
|
|
5593
|
+
${auditEvents.map(ev => {
|
|
5594
|
+
const payload = (ev && ev.payload) || {};
|
|
5595
|
+
const isCascade = ev.actor === 'cascade';
|
|
5596
|
+
return html`
|
|
5597
|
+
<div class="audit-entry" key=${ev.id} data-testid="audit-entry">
|
|
5598
|
+
<div class="audit-entry-header">
|
|
5599
|
+
<span class="audit-actor" data-testid="audit-actor">${isCascade ? 'Auto-cascaded' : 'Status changed'}</span>
|
|
5600
|
+
<span class="audit-time" data-testid="audit-time">${timeAgo(ev.timestamp)}</span>
|
|
5601
|
+
</div>
|
|
5602
|
+
<div class="audit-entry-body">
|
|
5603
|
+
<span class="audit-from" data-testid="audit-from-status">From: <strong>${payload.from_status || ''}</strong></span>
|
|
5604
|
+
${payload.superseded_by && html`
|
|
5605
|
+
<span class="audit-successor">
|
|
5606
|
+
Successor: <a href="#" data-testid="audit-successor-link" onClick=${(e) => {
|
|
5607
|
+
e.preventDefault();
|
|
5608
|
+
const target = allItems && allItems.find ? allItems.find(i => String(i.id) === String(payload.superseded_by)) : null;
|
|
5609
|
+
if (target && onSelectItem) onSelectItem(target);
|
|
5610
|
+
}}>#${payload.superseded_by}</a>
|
|
5611
|
+
</span>
|
|
5612
|
+
`}
|
|
5613
|
+
${payload.cascade_source && html`
|
|
5614
|
+
<span class="audit-cascade">
|
|
5615
|
+
Cascaded from parent: <a href="#" data-testid="audit-cascade-source" onClick=${(e) => {
|
|
5616
|
+
e.preventDefault();
|
|
5617
|
+
const target = allItems && allItems.find ? allItems.find(i => String(i.id) === String(payload.cascade_source)) : null;
|
|
5618
|
+
if (target && onSelectItem) onSelectItem(target);
|
|
5619
|
+
}}>#${payload.cascade_source}</a>
|
|
5620
|
+
</span>
|
|
5621
|
+
`}
|
|
5622
|
+
${payload.superseded_reason && html`
|
|
5623
|
+
<div class="audit-reason" data-testid="audit-reason">${payload.superseded_reason}</div>
|
|
5624
|
+
`}
|
|
5625
|
+
</div>
|
|
5626
|
+
</div>
|
|
5627
|
+
`;
|
|
5628
|
+
})}
|
|
5629
|
+
</div>
|
|
5630
|
+
</div>
|
|
5631
|
+
`}
|
|
5454
5632
|
${(loadingAttachments || attachments.length > 0) && html`
|
|
5455
5633
|
<div style="padding:12px 24px 0;">
|
|
5456
5634
|
<h4 style="margin:0 0 8px 0;font-size:13px;font-weight:600;">
|
|
@@ -5507,6 +5685,11 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5507
5685
|
selectedProject=${selectedProject}
|
|
5508
5686
|
onClose=${() => setDeployModal(null)}
|
|
5509
5687
|
/>`}
|
|
5688
|
+
${showSupersededModal && html`<${SupersededModal}
|
|
5689
|
+
item=${item}
|
|
5690
|
+
onConfirm=${() => { setShowSupersededModal(false); onStatusChange(); }}
|
|
5691
|
+
onCancel=${() => setShowSupersededModal(false)}
|
|
5692
|
+
/>`}
|
|
5510
5693
|
`;
|
|
5511
5694
|
}
|
|
5512
5695
|
|
|
@@ -6359,6 +6542,7 @@ function BoardPage({ selectedProject }) {
|
|
|
6359
6542
|
e.preventDefault();
|
|
6360
6543
|
e.currentTarget.classList.remove('drag-over', 'drag-over-invalid');
|
|
6361
6544
|
e.currentTarget.removeAttribute('title');
|
|
6545
|
+
if (status === 'Superseded') return;
|
|
6362
6546
|
const di = dragInfoRef.current;
|
|
6363
6547
|
if (!di) return;
|
|
6364
6548
|
const validStages = (typeof getStagesForType === 'function')
|
|
@@ -6841,7 +7025,7 @@ function BoardPage({ selectedProject }) {
|
|
|
6841
7025
|
`}
|
|
6842
7026
|
|
|
6843
7027
|
${showCreateDialog && html`<${CreateTaskDialog} onClose=${() => setShowCreateDialog(false)} onCreated=${fetchItems} />`}
|
|
6844
|
-
${selectedItem && html`<${ItemDetailSidebar} item=${selectedItem} selectedProject=${selectedProject} onClose=${() => setSelectedItem(null)} onStatusChange=${() => { fetchItems(); setSelectedItem(null); }} />`}
|
|
7028
|
+
${selectedItem && html`<${ItemDetailSidebar} item=${selectedItem} selectedProject=${selectedProject} allItems=${items} onSelectItem=${setSelectedItem} onClose=${() => setSelectedItem(null)} onStatusChange=${() => { fetchItems(); setSelectedItem(null); }} />`}
|
|
6845
7029
|
</div>
|
|
6846
7030
|
`;
|
|
6847
7031
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7350789bf2ce5bca2ca226ac0e7e8f656012caf4
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
20260509-
|
|
1
|
+
20260509-122800-7350789
|