@beastmode-develeap/beastmode 0.1.301 → 0.1.303
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 +270 -1
- 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__ = "20260526-193724-b40efbc";</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">
|
|
@@ -11643,6 +11643,204 @@ function LearningsPage({ selectedProject }) {
|
|
|
11643
11643
|
`;
|
|
11644
11644
|
}
|
|
11645
11645
|
|
|
11646
|
+
// ================================================================
|
|
11647
|
+
// Investigator Status Bar + Activity Drawer (Story S2, item #660)
|
|
11648
|
+
// ================================================================
|
|
11649
|
+
|
|
11650
|
+
// Explicit terminal-state status selector for the investigator status bar.
|
|
11651
|
+
// Blocked and Escalated are STICKY — they win regardless of timestamps and can
|
|
11652
|
+
// only be displaced by a newer InvestigatorResolved. System-scan events
|
|
11653
|
+
// (InvestigatorObserved for item #0 / null) are ignored entirely.
|
|
11654
|
+
// Does NOT use Array.reduce() so there is no risk of a newer scan timestamp
|
|
11655
|
+
// silently winning a comparator-based fold.
|
|
11656
|
+
function _computeInvestigatorStatus(events) {
|
|
11657
|
+
if (!events || events.length === 0) return null;
|
|
11658
|
+
let bestTerminal = null; // most-recent Blocked or Escalated
|
|
11659
|
+
let bestActivity = null; // most-recent non-terminal, non-scan event
|
|
11660
|
+
for (const e of events) {
|
|
11661
|
+
const ts = e.timestamp || '';
|
|
11662
|
+
const isTerminal = e.type === 'InvestigatorBlocked' || e.type === 'InvestigatorEscalated';
|
|
11663
|
+
const isResolution = e.type === 'InvestigatorResolved';
|
|
11664
|
+
const isSystemScan = !e.item_id || e.item_id === '0' || e.item_id === 0
|
|
11665
|
+
|| (e.payload && e.payload.summary === 'Investigator system scan');
|
|
11666
|
+
if (isTerminal) {
|
|
11667
|
+
if (!bestTerminal || ts > (bestTerminal.timestamp || '')) bestTerminal = e;
|
|
11668
|
+
} else if (isResolution) {
|
|
11669
|
+
if (bestTerminal && ts > (bestTerminal.timestamp || '')) bestTerminal = null;
|
|
11670
|
+
if (!bestActivity || ts > (bestActivity.timestamp || '')) bestActivity = e;
|
|
11671
|
+
} else if (!isSystemScan) {
|
|
11672
|
+
if (!bestActivity || ts > (bestActivity.timestamp || '')) bestActivity = e;
|
|
11673
|
+
}
|
|
11674
|
+
}
|
|
11675
|
+
return bestTerminal || bestActivity;
|
|
11676
|
+
}
|
|
11677
|
+
|
|
11678
|
+
function InvestigatorStatusBar({ events, onOpenDrawer, drawerOpen }) {
|
|
11679
|
+
// events is pre-filtered (system scans excluded at accumulation time in App).
|
|
11680
|
+
// _computeInvestigatorStatus adds a second layer of defence.
|
|
11681
|
+
const latest = _computeInvestigatorStatus(events);
|
|
11682
|
+
|
|
11683
|
+
let text = 'Investigator: idle';
|
|
11684
|
+
let dotColor = 'var(--text-muted)';
|
|
11685
|
+
let textColor = 'var(--text)'; // --text-muted fails 3.0 WCAG ratio on --bg-card in light theme
|
|
11686
|
+
|
|
11687
|
+
if (latest) {
|
|
11688
|
+
const ts = latest.timestamp ? new Date(latest.timestamp).getTime() : 0;
|
|
11689
|
+
const ageMs = Date.now() - ts;
|
|
11690
|
+
const tooOld = !ts || ageMs > 600000;
|
|
11691
|
+
const item = latest.item_id || '';
|
|
11692
|
+
const resolvedFading = latest.type === 'InvestigatorResolved' && ageMs > 60000;
|
|
11693
|
+
if (tooOld || resolvedFading) {
|
|
11694
|
+
text = 'Investigator: idle';
|
|
11695
|
+
} else if (latest.type === 'InvestigatorObserved') {
|
|
11696
|
+
text = item ? `Investigator: observing #${item}` : 'Investigator: observing (system scan)';
|
|
11697
|
+
dotColor = 'var(--accent)';
|
|
11698
|
+
textColor = 'var(--text)';
|
|
11699
|
+
} else if (latest.type === 'InvestigatorActed' || latest.type === 'InvestigatorHypothesized') {
|
|
11700
|
+
text = `Investigator: acting on #${item}`;
|
|
11701
|
+
dotColor = 'var(--accent)';
|
|
11702
|
+
textColor = 'var(--text)';
|
|
11703
|
+
} else if (latest.type === 'InvestigatorEscalated') {
|
|
11704
|
+
text = `Investigator: escalated on #${item}`;
|
|
11705
|
+
dotColor = 'var(--danger)';
|
|
11706
|
+
textColor = 'var(--danger)';
|
|
11707
|
+
} else if (latest.type === 'InvestigatorBlocked') {
|
|
11708
|
+
const isHold = latest && latest.payload && latest.payload.reason === 'operator_hold';
|
|
11709
|
+
text = isHold ? `Investigator: held by operator on #${item}` : `Investigator: blocked on #${item}`;
|
|
11710
|
+
dotColor = 'var(--warning)';
|
|
11711
|
+
textColor = 'var(--warning)';
|
|
11712
|
+
} else if (latest.type === 'InvestigatorResolved') {
|
|
11713
|
+
text = `Investigator: resolved #${item}`;
|
|
11714
|
+
dotColor = 'var(--success)';
|
|
11715
|
+
textColor = 'var(--success)';
|
|
11716
|
+
}
|
|
11717
|
+
}
|
|
11718
|
+
|
|
11719
|
+
return html`
|
|
11720
|
+
<button
|
|
11721
|
+
data-testid="investigator-status"
|
|
11722
|
+
aria-label="Open investigator activity drawer"
|
|
11723
|
+
aria-expanded=${drawerOpen ? 'true' : 'false'}
|
|
11724
|
+
onClick=${onOpenDrawer}
|
|
11725
|
+
style="display:flex;align-items:center;gap:8px;padding:6px 10px;margin-bottom:12px;font-size:12px;color:${textColor};background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;font-family:inherit;">
|
|
11726
|
+
<span style="width:8px;height:8px;border-radius:50%;background:${dotColor};display:inline-block;" />
|
|
11727
|
+
${text}
|
|
11728
|
+
</button>
|
|
11729
|
+
`;
|
|
11730
|
+
}
|
|
11731
|
+
|
|
11732
|
+
function InvestigatorActivityDrawer({ open, onClose, events, severity, onSeverityChange }) {
|
|
11733
|
+
const order = { debug: 0, info: 1, notice: 2, warn: 3 };
|
|
11734
|
+
const threshold = severity === 'all' ? 0 : severity === 'info+' ? 1 : 2;
|
|
11735
|
+
const now = Date.now();
|
|
11736
|
+
|
|
11737
|
+
const filtered = (events || [])
|
|
11738
|
+
.filter(e => {
|
|
11739
|
+
const ts = e && e.timestamp ? new Date(e.timestamp).getTime() : 0;
|
|
11740
|
+
if (!ts || now - ts > 600000) return false;
|
|
11741
|
+
const raw = e && e.payload ? e.payload.severity : undefined;
|
|
11742
|
+
const lvl = (raw in order) ? order[raw] : 2;
|
|
11743
|
+
return lvl >= threshold;
|
|
11744
|
+
})
|
|
11745
|
+
.sort((a, b) => {
|
|
11746
|
+
const ta = a.timestamp || '';
|
|
11747
|
+
const tb = b.timestamp || '';
|
|
11748
|
+
if (ta !== tb) return ta < tb ? 1 : -1;
|
|
11749
|
+
// Same second: higher id comes first (later-inserted = newer)
|
|
11750
|
+
return (parseInt(b.id) || 0) - (parseInt(a.id) || 0);
|
|
11751
|
+
});
|
|
11752
|
+
|
|
11753
|
+
const [expanded, setExpanded] = useState({});
|
|
11754
|
+
|
|
11755
|
+
return html`
|
|
11756
|
+
${open ? html`
|
|
11757
|
+
<div
|
|
11758
|
+
data-testid="investigator-drawer-backdrop"
|
|
11759
|
+
onClick=${onClose}
|
|
11760
|
+
style="position:fixed;inset:0;background:rgba(0,0,0,0.3);z-index:199;" />
|
|
11761
|
+
` : null}
|
|
11762
|
+
<aside
|
|
11763
|
+
data-testid="investigator-drawer"
|
|
11764
|
+
style="position:fixed;top:0;right:0;height:100vh;width:min(480px,90vw);background:var(--bg-card);border-left:1px solid var(--border);box-shadow:var(--shadow-sm);transform:translateX(${open ? '0' : '100%'});transition:transform 200ms ease-out;z-index:200;display:flex;flex-direction:column;">
|
|
11765
|
+
<header style="padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;">
|
|
11766
|
+
<strong style="flex:1;color:var(--text);">Investigator activity</strong>
|
|
11767
|
+
<select
|
|
11768
|
+
data-testid="investigator-severity-filter"
|
|
11769
|
+
value=${severity}
|
|
11770
|
+
onChange=${(e) => onSeverityChange(e.target.value)}
|
|
11771
|
+
style="background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--radius-sm);padding:2px 6px;">
|
|
11772
|
+
<option value="notice+">notice+</option>
|
|
11773
|
+
<option value="info+">info+</option>
|
|
11774
|
+
<option value="all">all</option>
|
|
11775
|
+
</select>
|
|
11776
|
+
<button
|
|
11777
|
+
data-testid="investigator-drawer-close"
|
|
11778
|
+
aria-label="Close drawer"
|
|
11779
|
+
onClick=${onClose}
|
|
11780
|
+
style="background:transparent;border:none;color:var(--text-muted);cursor:pointer;font-size:18px;">${'×'}</button>
|
|
11781
|
+
</header>
|
|
11782
|
+
<div style="flex:1;overflow-y:auto;padding:8px 0;">
|
|
11783
|
+
${filtered.length === 0 ? html`
|
|
11784
|
+
<div
|
|
11785
|
+
data-testid="investigator-empty-state"
|
|
11786
|
+
style="padding:24px 16px;color:var(--text);font-size:13px;text-align:center;">
|
|
11787
|
+
No investigator activity in the last 10 minutes.
|
|
11788
|
+
</div>
|
|
11789
|
+
` : filtered.map(e => {
|
|
11790
|
+
const isExpanded = !!expanded[e.id];
|
|
11791
|
+
const shortType = (e.type || '').replace(/^Investigator/, '');
|
|
11792
|
+
const ts = (e.timestamp || '').slice(11, 19);
|
|
11793
|
+
const summary = e && e.payload && e.payload.summary ? e.payload.summary : '';
|
|
11794
|
+
return html`
|
|
11795
|
+
<div
|
|
11796
|
+
data-testid="investigator-event-row"
|
|
11797
|
+
data-event-id=${e.id}
|
|
11798
|
+
data-event-type=${e.type}
|
|
11799
|
+
style="padding:8px 16px;border-bottom:1px solid var(--border);font-size:12px;color:var(--text);">
|
|
11800
|
+
<div style="display:flex;align-items:center;gap:4px;">
|
|
11801
|
+
<span style="color:var(--text-muted);font-variant-numeric:tabular-nums;">${ts}</span>
|
|
11802
|
+
<span style="color:var(--text-muted);" aria-hidden="true"> · </span>
|
|
11803
|
+
<span style="font-weight:600;">${shortType}</span>
|
|
11804
|
+
<span style="color:var(--text-muted);" aria-hidden="true"> · </span>
|
|
11805
|
+
${e.item_id
|
|
11806
|
+
? html`<a href="#/item/${e.item_id}" style="color:var(--accent);text-decoration:none;">#${e.item_id}</a>`
|
|
11807
|
+
: html`<span style="color:var(--text-muted);">system</span>`}
|
|
11808
|
+
<span style="color:var(--text-muted);" aria-hidden="true"> · </span>
|
|
11809
|
+
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-muted);">${summary}</span>
|
|
11810
|
+
${e && e.payload && e.payload.details && e.payload.details.kb_hit ? html`
|
|
11811
|
+
<span
|
|
11812
|
+
data-testid="investigator-kb-badge"
|
|
11813
|
+
data-kb-signature=${(e.payload.details.kb_signature) || ''}
|
|
11814
|
+
style="flex-shrink:0;font-size:11px;padding:2px 6px;border-radius:var(--radius-sm);background:var(--accent);color:var(--bg);font-weight:600;white-space:nowrap;">
|
|
11815
|
+
known failure shape, fixed ${e.payload.details.kb_success_count || 0} times
|
|
11816
|
+
</span>
|
|
11817
|
+
` : null}
|
|
11818
|
+
<button
|
|
11819
|
+
data-testid="investigator-event-expand"
|
|
11820
|
+
aria-expanded=${isExpanded ? 'true' : 'false'}
|
|
11821
|
+
onClick=${() => setExpanded(prev => ({ ...prev, [e.id]: !prev[e.id] }))}
|
|
11822
|
+
style="flex-shrink:0;background:transparent;border:none;color:var(--text-muted);cursor:pointer;padding:0 4px;">
|
|
11823
|
+
${isExpanded ? '▾' : '▸'}
|
|
11824
|
+
</button>
|
|
11825
|
+
</div>
|
|
11826
|
+
${isExpanded ? html`
|
|
11827
|
+
<pre
|
|
11828
|
+
data-testid="investigator-event-details"
|
|
11829
|
+
style="margin:8px 0 0 0;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:11px;overflow-x:auto;">${`type: ${e.type}\n${JSON.stringify(e.payload, null, 2)}`}</pre>
|
|
11830
|
+
` : null}
|
|
11831
|
+
</div>
|
|
11832
|
+
`;
|
|
11833
|
+
})}
|
|
11834
|
+
</div>
|
|
11835
|
+
</aside>
|
|
11836
|
+
`;
|
|
11837
|
+
}
|
|
11838
|
+
// Expose so verifiers and future patches can assert exactly one live reference
|
|
11839
|
+
try { window.__InvestigatorActivityDrawer = InvestigatorActivityDrawer; } catch (_) {}
|
|
11840
|
+
|
|
11841
|
+
// Expose on window so the verifier can inspect/test without navigating component trees.
|
|
11842
|
+
try { window.__InvestigatorActivityDrawer = InvestigatorActivityDrawer; } catch (_) {}
|
|
11843
|
+
|
|
11646
11844
|
// ================================================================
|
|
11647
11845
|
// Factory Status Bar
|
|
11648
11846
|
// ================================================================
|
|
@@ -11954,6 +12152,67 @@ function App() {
|
|
|
11954
12152
|
setMobileMenuOpen(false);
|
|
11955
12153
|
}, [hash]);
|
|
11956
12154
|
|
|
12155
|
+
// Investigator status indicator + activity drawer (Story S2, item #660).
|
|
12156
|
+
const [investigatorDrawerOpen, setInvestigatorDrawerOpen] = useState(false);
|
|
12157
|
+
// All investigator events — fed to the activity drawer.
|
|
12158
|
+
const [investigatorEvents, setInvestigatorEvents] = useState([]);
|
|
12159
|
+
// Non-scan investigator events only — fed to the status bar.
|
|
12160
|
+
// System-scan events (InvestigatorObserved for item #0) are excluded here so
|
|
12161
|
+
// they can never appear in the status bar's input even before _computeInvestigatorStatus runs.
|
|
12162
|
+
const [investigatorStatusEvents, setInvestigatorStatusEvents] = useState([]);
|
|
12163
|
+
const [investigatorSeverity, setInvestigatorSeverity] = useState('notice+');
|
|
12164
|
+
|
|
12165
|
+
useEffect(() => {
|
|
12166
|
+
let since = new Date(Date.now() - 600000).toISOString();
|
|
12167
|
+
const fetchEvents = async () => {
|
|
12168
|
+
try {
|
|
12169
|
+
// Same precedence as api() lines 4004-4015 and ItemDetailSidebar line 5649:
|
|
12170
|
+
// URL ?board= first, then localStorage (excluding the literal 'all'),
|
|
12171
|
+
// else no scope (default board). Without this scoping the UI showed
|
|
12172
|
+
// events from the project board while the verifier was scoped to a
|
|
12173
|
+
// verify-board (bug #698 blocking #660).
|
|
12174
|
+
let board = '';
|
|
12175
|
+
try {
|
|
12176
|
+
board = new URLSearchParams(window.location.search).get('board') || '';
|
|
12177
|
+
if (!board) {
|
|
12178
|
+
const ls = localStorage.getItem('beastmode-selected-project') || '';
|
|
12179
|
+
board = (ls && ls !== 'all') ? ls : '';
|
|
12180
|
+
}
|
|
12181
|
+
} catch (_) { board = ''; }
|
|
12182
|
+
const boardParam = board ? `&board=${encodeURIComponent(board)}` : '';
|
|
12183
|
+
const rows = await api('GET', `/api/events?since=${encodeURIComponent(since)}&limit=500${boardParam}`);
|
|
12184
|
+
const incoming = (rows || []).filter(e => typeof (e && e.type) === 'string' && e.type.startsWith('Investigator'));
|
|
12185
|
+
if (incoming.length > 0) {
|
|
12186
|
+
const latest = incoming.reduce((a, b) => ((a.timestamp || '') > (b.timestamp || '') ? a : b));
|
|
12187
|
+
if (latest.timestamp) since = latest.timestamp;
|
|
12188
|
+
// All events → drawer
|
|
12189
|
+
setInvestigatorEvents(prev => {
|
|
12190
|
+
const seen = new Set(prev.map(e => e.id));
|
|
12191
|
+
const novel = incoming.filter(e => !seen.has(e.id));
|
|
12192
|
+
return novel.length ? [...prev, ...novel] : prev;
|
|
12193
|
+
});
|
|
12194
|
+
// Non-scan events only → status bar (system scan = InvestigatorObserved for item #0)
|
|
12195
|
+
const statusIncoming = incoming.filter(e => {
|
|
12196
|
+
if (!e || e.type !== 'InvestigatorObserved') return true;
|
|
12197
|
+
if (!e.item_id || e.item_id === '0' || e.item_id === 0) return false;
|
|
12198
|
+
if (e.payload && e.payload.summary === 'Investigator system scan') return false;
|
|
12199
|
+
return true;
|
|
12200
|
+
});
|
|
12201
|
+
if (statusIncoming.length > 0) {
|
|
12202
|
+
setInvestigatorStatusEvents(prev => {
|
|
12203
|
+
const seen = new Set(prev.map(e => e.id));
|
|
12204
|
+
const novel = statusIncoming.filter(e => !seen.has(e.id));
|
|
12205
|
+
return novel.length ? [...prev, ...novel] : prev;
|
|
12206
|
+
});
|
|
12207
|
+
}
|
|
12208
|
+
}
|
|
12209
|
+
} catch (_) { /* fail-open: keep prior state */ }
|
|
12210
|
+
};
|
|
12211
|
+
fetchEvents();
|
|
12212
|
+
const id = setInterval(fetchEvents, 3000);
|
|
12213
|
+
return () => clearInterval(id);
|
|
12214
|
+
}, []);
|
|
12215
|
+
|
|
11957
12216
|
return html`
|
|
11958
12217
|
<div class="layout">
|
|
11959
12218
|
<button class="mobile-menu-btn" onClick=${() => setMobileMenuOpen(!mobileMenuOpen)} aria-label="Menu">
|
|
@@ -11965,8 +12224,18 @@ function App() {
|
|
|
11965
12224
|
mobileOpen=${mobileMenuOpen} />
|
|
11966
12225
|
<main class="main">
|
|
11967
12226
|
<${FactoryStatusBar} />
|
|
12227
|
+
<${InvestigatorStatusBar}
|
|
12228
|
+
events=${investigatorStatusEvents}
|
|
12229
|
+
onOpenDrawer=${() => setInvestigatorDrawerOpen(true)}
|
|
12230
|
+
drawerOpen=${investigatorDrawerOpen} />
|
|
11968
12231
|
${page}
|
|
11969
12232
|
</main>
|
|
12233
|
+
<${InvestigatorActivityDrawer}
|
|
12234
|
+
open=${investigatorDrawerOpen}
|
|
12235
|
+
onClose=${() => setInvestigatorDrawerOpen(false)}
|
|
12236
|
+
events=${investigatorEvents}
|
|
12237
|
+
severity=${investigatorSeverity}
|
|
12238
|
+
onSeverityChange=${setInvestigatorSeverity} />
|
|
11970
12239
|
<${OnboardingTour} />
|
|
11971
12240
|
</div>
|
|
11972
12241
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
b40efbc02b862c2611477f0a4c8f116e13f5e17b
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260526-193724-b40efbc
|