@beastmode-develeap/beastmode 0.1.133 → 0.1.135
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/index.js +73 -10
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +305 -12
- 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__ = "
|
|
18
|
+
<script>window.__BUILD_STAMP__ = "20260420-073821-483e679";</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">
|
|
@@ -609,11 +609,12 @@ body {
|
|
|
609
609
|
content: '';
|
|
610
610
|
position: absolute;
|
|
611
611
|
top: -50%;
|
|
612
|
-
right: -
|
|
613
|
-
width:
|
|
614
|
-
height:
|
|
615
|
-
background: radial-gradient(circle, var(--accent-glow) 0%, transparent
|
|
612
|
+
right: -20%;
|
|
613
|
+
width: 250px;
|
|
614
|
+
height: 250px;
|
|
615
|
+
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 60%);
|
|
616
616
|
pointer-events: none;
|
|
617
|
+
opacity: 0.6;
|
|
617
618
|
}
|
|
618
619
|
|
|
619
620
|
.welcome-title {
|
|
@@ -1070,6 +1071,39 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
1070
1071
|
padding: 8px;
|
|
1071
1072
|
flex: 1;
|
|
1072
1073
|
min-height: 60px;
|
|
1074
|
+
max-height: calc(100vh - 300px);
|
|
1075
|
+
overflow-y: auto;
|
|
1076
|
+
scrollbar-width: thin;
|
|
1077
|
+
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.kanban-items::-webkit-scrollbar {
|
|
1081
|
+
width: 6px;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.kanban-items::-webkit-scrollbar-track {
|
|
1085
|
+
background: transparent;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.kanban-items::-webkit-scrollbar-thumb {
|
|
1089
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1090
|
+
border-radius: 3px;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.kanban-items::-webkit-scrollbar-thumb:hover {
|
|
1094
|
+
background: rgba(255, 255, 255, 0.25);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
[data-theme="light"] .kanban-items {
|
|
1098
|
+
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
[data-theme="light"] .kanban-items::-webkit-scrollbar-thumb {
|
|
1102
|
+
background: rgba(0, 0, 0, 0.15);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
[data-theme="light"] .kanban-items::-webkit-scrollbar-thumb:hover {
|
|
1106
|
+
background: rgba(0, 0, 0, 0.25);
|
|
1073
1107
|
}
|
|
1074
1108
|
|
|
1075
1109
|
.kanban-card {
|
|
@@ -1176,6 +1210,23 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
1176
1210
|
color: var(--text-muted);
|
|
1177
1211
|
font-family: var(--font-mono);
|
|
1178
1212
|
}
|
|
1213
|
+
/* Status-age badge — time spent in the CURRENT status. Color-coded so
|
|
1214
|
+
operators can scan which items have been waiting/working too long.
|
|
1215
|
+
"ok" fresh, "info" 1-6h, "warn" 6-24h, "stale" 24h+. Designed for
|
|
1216
|
+
the 2026-04-19 "not sure why waiting so long" complaint. */
|
|
1217
|
+
.kanban-card .card-status-age {
|
|
1218
|
+
margin-left: auto;
|
|
1219
|
+
font-size: 11px;
|
|
1220
|
+
font-family: var(--font-mono);
|
|
1221
|
+
padding: 1px 6px;
|
|
1222
|
+
border-radius: 3px;
|
|
1223
|
+
font-weight: 500;
|
|
1224
|
+
letter-spacing: 0.2px;
|
|
1225
|
+
}
|
|
1226
|
+
.card-status-age.status-age-ok { color: var(--text-muted); }
|
|
1227
|
+
.card-status-age.status-age-info { color: var(--accent); background: var(--accent-subtle); }
|
|
1228
|
+
.card-status-age.status-age-warn { color: #fb923c; background: rgba(249, 115, 22, 0.15); }
|
|
1229
|
+
.card-status-age.status-age-stale { color: #f87171; background: rgba(239, 68, 68, 0.15); }
|
|
1179
1230
|
.card-badge {
|
|
1180
1231
|
display: inline-block;
|
|
1181
1232
|
padding: 2px 8px;
|
|
@@ -1674,6 +1725,43 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
1674
1725
|
}
|
|
1675
1726
|
.filter-toggle:hover { border-color: var(--text-muted); color: var(--text); }
|
|
1676
1727
|
.filter-toggle.active { border-color: var(--accent); color: var(--accent); background: var(--accent-subtle); }
|
|
1728
|
+
|
|
1729
|
+
.view-toggle {
|
|
1730
|
+
display: inline-flex;
|
|
1731
|
+
border: 1px solid var(--border);
|
|
1732
|
+
border-radius: var(--radius-sm);
|
|
1733
|
+
overflow: hidden;
|
|
1734
|
+
flex-shrink: 0;
|
|
1735
|
+
}
|
|
1736
|
+
.view-toggle-btn {
|
|
1737
|
+
display: inline-flex;
|
|
1738
|
+
align-items: center;
|
|
1739
|
+
padding: 0 12px;
|
|
1740
|
+
height: 36px;
|
|
1741
|
+
font-size: 13px;
|
|
1742
|
+
font-family: var(--font-sans);
|
|
1743
|
+
font-weight: 500;
|
|
1744
|
+
background: var(--bg-card);
|
|
1745
|
+
color: var(--text-secondary);
|
|
1746
|
+
border: none;
|
|
1747
|
+
cursor: pointer;
|
|
1748
|
+
transition: all 0.15s ease;
|
|
1749
|
+
white-space: nowrap;
|
|
1750
|
+
user-select: none;
|
|
1751
|
+
}
|
|
1752
|
+
.view-toggle-btn:hover {
|
|
1753
|
+
color: var(--text);
|
|
1754
|
+
background: var(--bg-input);
|
|
1755
|
+
}
|
|
1756
|
+
.view-toggle-btn.active {
|
|
1757
|
+
background: var(--accent-subtle);
|
|
1758
|
+
color: var(--accent);
|
|
1759
|
+
cursor: default;
|
|
1760
|
+
}
|
|
1761
|
+
.view-toggle-btn + .view-toggle-btn {
|
|
1762
|
+
border-left: 1px solid var(--border);
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1677
1765
|
.filter-active-count {
|
|
1678
1766
|
display: inline-flex;
|
|
1679
1767
|
align-items: center;
|
|
@@ -2400,7 +2488,44 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
2400
2488
|
ACTIVITY SIDEBAR (Dashboard)
|
|
2401
2489
|
================================================================ */
|
|
2402
2490
|
|
|
2403
|
-
.activity-list {
|
|
2491
|
+
.activity-list {
|
|
2492
|
+
display: flex;
|
|
2493
|
+
flex-direction: column;
|
|
2494
|
+
gap: 2px;
|
|
2495
|
+
max-height: 400px;
|
|
2496
|
+
overflow-y: auto;
|
|
2497
|
+
scrollbar-width: thin;
|
|
2498
|
+
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
.activity-list::-webkit-scrollbar {
|
|
2502
|
+
width: 6px;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
.activity-list::-webkit-scrollbar-track {
|
|
2506
|
+
background: transparent;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
.activity-list::-webkit-scrollbar-thumb {
|
|
2510
|
+
background: rgba(255, 255, 255, 0.15);
|
|
2511
|
+
border-radius: 3px;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
.activity-list::-webkit-scrollbar-thumb:hover {
|
|
2515
|
+
background: rgba(255, 255, 255, 0.25);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
[data-theme="light"] .activity-list {
|
|
2519
|
+
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
[data-theme="light"] .activity-list::-webkit-scrollbar-thumb {
|
|
2523
|
+
background: rgba(0, 0, 0, 0.15);
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
[data-theme="light"] .activity-list::-webkit-scrollbar-thumb:hover {
|
|
2527
|
+
background: rgba(0, 0, 0, 0.25);
|
|
2528
|
+
}
|
|
2404
2529
|
.activity-item {
|
|
2405
2530
|
padding: 10px 12px;
|
|
2406
2531
|
border-radius: var(--radius-sm);
|
|
@@ -3580,6 +3705,35 @@ function timeAgo(dateString) {
|
|
|
3580
3705
|
return months + 'mo ago';
|
|
3581
3706
|
}
|
|
3582
3707
|
|
|
3708
|
+
// "In status" age — shorter render (no "ago"), with a severity class
|
|
3709
|
+
// so the UI can color-code long waits. Designed for inline card badges:
|
|
3710
|
+
// "3h", "2d 5h", "1w". Returns {text, severity} where severity is
|
|
3711
|
+
// "ok" (<1h), "info" (<6h), "warn" (<24h), or "stale" (>=24h). Empty
|
|
3712
|
+
// dateString returns {text: '', severity: 'ok'} — caller suppresses
|
|
3713
|
+
// the badge entirely.
|
|
3714
|
+
function statusAge(dateString) {
|
|
3715
|
+
if (!dateString) return { text: '', severity: 'ok' };
|
|
3716
|
+
const now = new Date();
|
|
3717
|
+
const date = new Date(dateString);
|
|
3718
|
+
const totalMin = Math.max(0, Math.floor((now - date) / 60000));
|
|
3719
|
+
let text;
|
|
3720
|
+
if (totalMin < 1) text = 'just now';
|
|
3721
|
+
else if (totalMin < 60) text = totalMin + 'm';
|
|
3722
|
+
else if (totalMin < 1440) text = Math.floor(totalMin / 60) + 'h';
|
|
3723
|
+
else if (totalMin < 10080) {
|
|
3724
|
+
const d = Math.floor(totalMin / 1440);
|
|
3725
|
+
const h = Math.floor((totalMin % 1440) / 60);
|
|
3726
|
+
text = h ? d + 'd ' + h + 'h' : d + 'd';
|
|
3727
|
+
} else {
|
|
3728
|
+
text = Math.floor(totalMin / 10080) + 'w';
|
|
3729
|
+
}
|
|
3730
|
+
let severity = 'ok';
|
|
3731
|
+
if (totalMin >= 60 && totalMin < 360) severity = 'info';
|
|
3732
|
+
else if (totalMin >= 360 && totalMin < 1440) severity = 'warn';
|
|
3733
|
+
else if (totalMin >= 1440) severity = 'stale';
|
|
3734
|
+
return { text, severity };
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3583
3737
|
function priorityBadgeClass(priority) {
|
|
3584
3738
|
if (!priority) return '';
|
|
3585
3739
|
const p = priority.toLowerCase();
|
|
@@ -4591,7 +4745,16 @@ function PipelineView({
|
|
|
4591
4745
|
const label = cost ? formatCost(cost.total_cost_usd) : null;
|
|
4592
4746
|
return label ? html`<span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span>` : null;
|
|
4593
4747
|
})()}
|
|
4594
|
-
|
|
4748
|
+
${(() => {
|
|
4749
|
+
// Time-in-status badge (2026-04-20): answers "how long has
|
|
4750
|
+
// this been waiting?" without the user having to click into
|
|
4751
|
+
// the task. Falls back to updated_at for pre-v5 rows.
|
|
4752
|
+
const src = item.status_changed_at || item.updated_at || item.created_at;
|
|
4753
|
+
const age = statusAge(src);
|
|
4754
|
+
if (!age.text) return null;
|
|
4755
|
+
return html`<span class=${'card-status-age status-age-' + age.severity}
|
|
4756
|
+
title=${'In status since ' + (src || 'unknown') + ' (updated ' + timeAgo(item.updated_at || item.created_at) + ')'}>${age.text}</span>`;
|
|
4757
|
+
})()}
|
|
4595
4758
|
</div>
|
|
4596
4759
|
</div>
|
|
4597
4760
|
`;
|
|
@@ -4826,6 +4989,35 @@ function CollapseAllButton({ allCollapsed, onToggle }) {
|
|
|
4826
4989
|
`;
|
|
4827
4990
|
}
|
|
4828
4991
|
|
|
4992
|
+
function ViewToggle({ viewMode, onToggle }) {
|
|
4993
|
+
const isPipeline = viewMode === 'pipeline';
|
|
4994
|
+
return html`
|
|
4995
|
+
<div class="view-toggle" data-testid="view-toggle"
|
|
4996
|
+
role="radiogroup" aria-label="View mode">
|
|
4997
|
+
<button
|
|
4998
|
+
class=${'view-toggle-btn' + (!isPipeline ? ' active' : '')}
|
|
4999
|
+
onClick=${() => { if (isPipeline) onToggle(); }}
|
|
5000
|
+
role="radio"
|
|
5001
|
+
aria-checked=${String(!isPipeline)}
|
|
5002
|
+
aria-pressed=${String(!isPipeline)}
|
|
5003
|
+
data-testid="view-toggle-board"
|
|
5004
|
+
title="Board view">
|
|
5005
|
+
Board
|
|
5006
|
+
</button>
|
|
5007
|
+
<button
|
|
5008
|
+
class=${'view-toggle-btn' + (isPipeline ? ' active' : '')}
|
|
5009
|
+
onClick=${() => { if (!isPipeline) onToggle(); }}
|
|
5010
|
+
role="radio"
|
|
5011
|
+
aria-checked=${String(isPipeline)}
|
|
5012
|
+
aria-pressed=${String(isPipeline)}
|
|
5013
|
+
data-testid="view-toggle-pipeline"
|
|
5014
|
+
title="Pipeline view">
|
|
5015
|
+
Pipeline
|
|
5016
|
+
</button>
|
|
5017
|
+
</div>
|
|
5018
|
+
`;
|
|
5019
|
+
}
|
|
5020
|
+
|
|
4829
5021
|
// ── Board Page ──
|
|
4830
5022
|
|
|
4831
5023
|
function BoardPage({ selectedProject }) {
|
|
@@ -4852,13 +5044,61 @@ function BoardPage({ selectedProject }) {
|
|
|
4852
5044
|
const [columnSorts, setColumnSorts] = useState({});
|
|
4853
5045
|
const [epicCollapseKey, setEpicCollapseKey] = useState(0);
|
|
4854
5046
|
const [costsByItem, setCostsByItem] = useState({});
|
|
4855
|
-
const viewMode = (() => {
|
|
5047
|
+
const [viewMode, setViewMode] = useState(() => {
|
|
5048
|
+
try {
|
|
5049
|
+
const saved = localStorage.getItem('beastmode-view-mode');
|
|
5050
|
+
if (saved === 'pipeline' || saved === 'board') return saved;
|
|
5051
|
+
} catch {}
|
|
4856
5052
|
try {
|
|
4857
5053
|
const params = new URLSearchParams(window.location.search);
|
|
4858
5054
|
if (params.get('view') === 'pipeline') return 'pipeline';
|
|
4859
5055
|
} catch {}
|
|
4860
5056
|
return 'board';
|
|
4861
|
-
})
|
|
5057
|
+
});
|
|
5058
|
+
|
|
5059
|
+
useEffect(() => {
|
|
5060
|
+
try {
|
|
5061
|
+
localStorage.setItem('beastmode-view-mode', viewMode);
|
|
5062
|
+
} catch {}
|
|
5063
|
+
}, [viewMode]);
|
|
5064
|
+
|
|
5065
|
+
useEffect(() => {
|
|
5066
|
+
try {
|
|
5067
|
+
const url = new URL(window.location.href);
|
|
5068
|
+
if (viewMode === 'pipeline') {
|
|
5069
|
+
url.searchParams.set('view', 'pipeline');
|
|
5070
|
+
} else {
|
|
5071
|
+
url.searchParams.delete('view');
|
|
5072
|
+
}
|
|
5073
|
+
if (url.href !== window.location.href) {
|
|
5074
|
+
window.history.pushState({ viewMode }, '', url.href);
|
|
5075
|
+
}
|
|
5076
|
+
} catch {}
|
|
5077
|
+
}, [viewMode]);
|
|
5078
|
+
|
|
5079
|
+
useEffect(() => {
|
|
5080
|
+
const onPop = (e) => {
|
|
5081
|
+
try {
|
|
5082
|
+
if (e.state && (e.state.viewMode === 'pipeline' || e.state.viewMode === 'board')) {
|
|
5083
|
+
setViewMode(e.state.viewMode);
|
|
5084
|
+
return;
|
|
5085
|
+
}
|
|
5086
|
+
const saved = localStorage.getItem('beastmode-view-mode');
|
|
5087
|
+
if (saved === 'pipeline' || saved === 'board') {
|
|
5088
|
+
setViewMode(saved);
|
|
5089
|
+
return;
|
|
5090
|
+
}
|
|
5091
|
+
const params = new URLSearchParams(window.location.search);
|
|
5092
|
+
setViewMode(params.get('view') === 'pipeline' ? 'pipeline' : 'board');
|
|
5093
|
+
} catch {}
|
|
5094
|
+
};
|
|
5095
|
+
window.addEventListener('popstate', onPop);
|
|
5096
|
+
return () => window.removeEventListener('popstate', onPop);
|
|
5097
|
+
}, []);
|
|
5098
|
+
|
|
5099
|
+
const toggleView = useCallback(() => {
|
|
5100
|
+
setViewMode(prev => prev === 'board' ? 'pipeline' : 'board');
|
|
5101
|
+
}, []);
|
|
4862
5102
|
|
|
4863
5103
|
const fetchItems = useCallback(() => {
|
|
4864
5104
|
setLoading(true);
|
|
@@ -5024,6 +5264,19 @@ function BoardPage({ selectedProject }) {
|
|
|
5024
5264
|
}, []);
|
|
5025
5265
|
|
|
5026
5266
|
const deleteItem = async (id) => {
|
|
5267
|
+
// Confirmation guard (2026-04-20, Agent C finding #10). The card
|
|
5268
|
+
// delete button was a bare onClick — one accidental click and a
|
|
5269
|
+
// task is gone. Add a native confirm() dialog so accidental
|
|
5270
|
+
// mid-drag clicks have an escape hatch. Kept simple (no custom
|
|
5271
|
+
// modal) to avoid touching the existing Preact component tree.
|
|
5272
|
+
const item = items.find(i => String(i.id) === String(id));
|
|
5273
|
+
const title = item ? (item.name || item.title || '(untitled)') : 'this task';
|
|
5274
|
+
const truncated = title.length > 60 ? title.slice(0, 60) + '…' : title;
|
|
5275
|
+
if (!window.confirm('Delete task #' + id + '?\n\n' + truncated +
|
|
5276
|
+
'\n\nThis cannot be undone. Updates, costs, and attachments ' +
|
|
5277
|
+
'will be lost.')) {
|
|
5278
|
+
return;
|
|
5279
|
+
}
|
|
5027
5280
|
try {
|
|
5028
5281
|
setError(null);
|
|
5029
5282
|
await api('DELETE', '/api/board/items/' + id);
|
|
@@ -5139,6 +5392,35 @@ function BoardPage({ selectedProject }) {
|
|
|
5139
5392
|
};
|
|
5140
5393
|
}, [activeSwimlanesSet]);
|
|
5141
5394
|
|
|
5395
|
+
// Layout test surface: expose column metrics on window for scenario
|
|
5396
|
+
// verification. Guards on !loading and items.length > 0 so metrics are
|
|
5397
|
+
// only exposed once the board has actually rendered content.
|
|
5398
|
+
// requestAnimationFrame defers measurement until after the browser has
|
|
5399
|
+
// painted, ensuring scrollHeight/clientHeight reflect the real layout.
|
|
5400
|
+
useEffect(() => {
|
|
5401
|
+
if (loading || items.length === 0) return;
|
|
5402
|
+
const raf = requestAnimationFrame(() => {
|
|
5403
|
+
const columns = document.querySelectorAll('.kanban-items');
|
|
5404
|
+
if (columns.length === 0) return;
|
|
5405
|
+
const metrics = Array.from(columns).map(col => {
|
|
5406
|
+
const cs = getComputedStyle(col);
|
|
5407
|
+
return {
|
|
5408
|
+
scrollHeight: col.scrollHeight,
|
|
5409
|
+
clientHeight: col.clientHeight,
|
|
5410
|
+
isScrollable: col.scrollHeight > col.clientHeight,
|
|
5411
|
+
hasOverflowAuto: cs.overflowY === 'auto',
|
|
5412
|
+
maxHeight: cs.maxHeight,
|
|
5413
|
+
};
|
|
5414
|
+
});
|
|
5415
|
+
window.__BEASTMODE_BOARD_LAYOUT__ = {
|
|
5416
|
+
columnCount: columns.length,
|
|
5417
|
+
columns: metrics,
|
|
5418
|
+
timestamp: Date.now(),
|
|
5419
|
+
};
|
|
5420
|
+
});
|
|
5421
|
+
return () => cancelAnimationFrame(raf);
|
|
5422
|
+
}, [items, loading]);
|
|
5423
|
+
|
|
5142
5424
|
const toggleSwimlane = (key) => {
|
|
5143
5425
|
setActiveSwimlanesSet(prev => {
|
|
5144
5426
|
const next = new Set(prev);
|
|
@@ -5257,7 +5539,7 @@ function BoardPage({ selectedProject }) {
|
|
|
5257
5539
|
|
|
5258
5540
|
<div class="board-header-row">
|
|
5259
5541
|
<div class="board-stats-bar">
|
|
5260
|
-
<span class="stat-label"
|
|
5542
|
+
<span class="stat-label">${viewMode === 'pipeline' ? 'Pipeline' : 'Board'}</span>
|
|
5261
5543
|
<span class="stat-item"><strong>${totalItems}</strong>${isFiltered ? '/' + items.length : ''} total</span>
|
|
5262
5544
|
<span class="stat-item"><strong>${activeItems}</strong> active</span>
|
|
5263
5545
|
<span class="stat-item" style="color: var(--success);"><strong style="color: var(--success);">${doneItems}</strong> done</span>
|
|
@@ -5265,6 +5547,8 @@ function BoardPage({ selectedProject }) {
|
|
|
5265
5547
|
<span class="stat-item" style="color: var(--accent);"><strong style="color: var(--accent); font-family: var(--font-mono);">${progressPct}%</strong> complete</span>
|
|
5266
5548
|
</div>
|
|
5267
5549
|
|
|
5550
|
+
<${ViewToggle} viewMode=${viewMode} onToggle=${toggleView} />
|
|
5551
|
+
|
|
5268
5552
|
<div class="board-search-wrap">
|
|
5269
5553
|
<input type="text" placeholder="Search tasks..." value=${searchTerm} onInput=${(e) => setSearchTerm(e.target.value)} />
|
|
5270
5554
|
${searchTerm && html`<button class="board-search-clear" onClick=${() => setSearchTerm('')}>\u00d7</button>`}
|
|
@@ -5438,7 +5722,16 @@ function BoardPage({ selectedProject }) {
|
|
|
5438
5722
|
const label = cost ? formatCost(cost.total_cost_usd) : null;
|
|
5439
5723
|
return label ? html`<span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span>` : null;
|
|
5440
5724
|
})()}
|
|
5441
|
-
|
|
5725
|
+
${(() => {
|
|
5726
|
+
// Time-in-status badge (2026-04-20): answers "how long has
|
|
5727
|
+
// this been waiting?" without the user having to click into
|
|
5728
|
+
// the task. Falls back to updated_at for pre-v5 rows.
|
|
5729
|
+
const src = item.status_changed_at || item.updated_at || item.created_at;
|
|
5730
|
+
const age = statusAge(src);
|
|
5731
|
+
if (!age.text) return null;
|
|
5732
|
+
return html`<span class=${'card-status-age status-age-' + age.severity}
|
|
5733
|
+
title=${'In status since ' + (src || 'unknown') + ' (updated ' + timeAgo(item.updated_at || item.created_at) + ')'}>${age.text}</span>`;
|
|
5734
|
+
})()}
|
|
5442
5735
|
</div>
|
|
5443
5736
|
</div>
|
|
5444
5737
|
`;
|
|
@@ -8486,7 +8779,7 @@ function App() {
|
|
|
8486
8779
|
|
|
8487
8780
|
render(html`<${App} />`, document.getElementById('app'));
|
|
8488
8781
|
</script>
|
|
8489
|
-
<div id="build-stamp-footer" style="position:fixed;bottom:4px;right:8px;font-size:10px;color:rgba(255,255,255,0.2);font-family:'JetBrains Mono',monospace;pointer-events:none;z-index:1;user-select:all"></div>
|
|
8782
|
+
<div id="build-stamp-footer" style="position:fixed;bottom:4px;right:8px;font-size:10px;color:rgba(255,255,255,0.2);font-family:'JetBrains Mono',monospace;pointer-events:none;z-index:1;user-select:all;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></div>
|
|
8490
8783
|
<script>if(window.__BUILD_STAMP__){document.getElementById('build-stamp-footer').textContent=window.__BUILD_STAMP__}</script>
|
|
8491
8784
|
</body>
|
|
8492
8785
|
</html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
483e679664e425769ab39366cf74c472bf72c938
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260420-073821-483e679
|