thevoidforge 21.0.0 → 21.0.1

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.
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Prophecy Visualizer — interactive SVG dependency graph for campaign missions.
3
+ *
4
+ * Reads campaign data from /api/war-room/campaign and renders a node/edge graph.
5
+ * Nodes = missions. Edges = dependency order. Color = status.
6
+ * Clickable nodes show mission details.
7
+ */
8
+ (function () {
9
+ 'use strict';
10
+
11
+ // ── Constants ─────────────────────────────────
12
+
13
+ var NODE_RADIUS = 24;
14
+ var NODE_SPACING_X = 120;
15
+ var NODE_SPACING_Y = 80;
16
+ var PADDING = 40;
17
+
18
+ var STATUS_COLORS = {
19
+ COMPLETE: '#34d399',
20
+ ACTIVE: '#fbbf24',
21
+ BLOCKED: '#ef4444',
22
+ PENDING: '#555',
23
+ STRUCTURAL: '#6366f1'
24
+ };
25
+
26
+ var STATUS_LABELS = {
27
+ COMPLETE: 'Complete',
28
+ ACTIVE: 'In Progress',
29
+ BLOCKED: 'Blocked',
30
+ PENDING: 'Pending',
31
+ STRUCTURAL: 'Structural'
32
+ };
33
+
34
+ // ── SVG helpers ───────────────────────────────
35
+
36
+ function svgEl(tag, attrs) {
37
+ var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
38
+ if (attrs) {
39
+ for (var key in attrs) {
40
+ if (Object.prototype.hasOwnProperty.call(attrs, key)) {
41
+ el.setAttribute(key, attrs[key]);
42
+ }
43
+ }
44
+ }
45
+ return el;
46
+ }
47
+
48
+ function escapeText(str) {
49
+ var div = document.createElement('div');
50
+ div.appendChild(document.createTextNode(str));
51
+ return div.innerHTML;
52
+ }
53
+
54
+ // ── Layout ────────────────────────────────────
55
+
56
+ function layoutNodes(missions) {
57
+ // Simple left-to-right grid layout
58
+ var cols = Math.ceil(Math.sqrt(missions.length));
59
+ return missions.map(function (m, i) {
60
+ var col = i % cols;
61
+ var row = Math.floor(i / cols);
62
+ return {
63
+ mission: m,
64
+ x: PADDING + col * NODE_SPACING_X + NODE_RADIUS,
65
+ y: PADDING + row * NODE_SPACING_Y + NODE_RADIUS
66
+ };
67
+ });
68
+ }
69
+
70
+ // ── Rendering ─────────────────────────────────
71
+
72
+ function renderGraph(container, campaignData) {
73
+ container.innerHTML = '';
74
+
75
+ if (!campaignData || !campaignData.missions || campaignData.missions.length === 0) {
76
+ container.innerHTML = '<div style="padding:20px;color:var(--text-dim);font-size:13px;">No campaign data — run /campaign to see the prophecy graph.</div>';
77
+ return;
78
+ }
79
+
80
+ var nodes = layoutNodes(campaignData.missions);
81
+ var cols = Math.ceil(Math.sqrt(campaignData.missions.length));
82
+ var rows = Math.ceil(campaignData.missions.length / cols);
83
+ var svgWidth = PADDING * 2 + cols * NODE_SPACING_X;
84
+ var svgHeight = PADDING * 2 + rows * NODE_SPACING_Y;
85
+
86
+ var svg = svgEl('svg', {
87
+ viewBox: '0 0 ' + svgWidth + ' ' + svgHeight,
88
+ width: '100%',
89
+ height: Math.min(svgHeight, 400) + 'px',
90
+ role: 'group',
91
+ 'aria-label': 'Campaign mission dependency graph'
92
+ });
93
+
94
+ // Draw edges (sequential dependency: mission N → mission N+1)
95
+ for (var i = 0; i < nodes.length - 1; i++) {
96
+ var from = nodes[i];
97
+ var to = nodes[i + 1];
98
+ var line = svgEl('line', {
99
+ x1: from.x,
100
+ y1: from.y,
101
+ x2: to.x,
102
+ y2: to.y,
103
+ stroke: '#444',
104
+ 'stroke-width': '2',
105
+ 'stroke-dasharray': '4,4'
106
+ });
107
+ svg.appendChild(line);
108
+ }
109
+
110
+ // Draw nodes
111
+ nodes.forEach(function (node) {
112
+ var m = node.mission;
113
+ var color = STATUS_COLORS[m.status] || STATUS_COLORS.PENDING;
114
+ var label = STATUS_LABELS[m.status] || m.status;
115
+
116
+ // Node group
117
+ var g = svgEl('g', {
118
+ 'data-mission': m.number,
119
+ style: 'cursor:pointer',
120
+ role: 'button',
121
+ tabindex: '0',
122
+ 'aria-label': 'Mission ' + m.number + ': ' + escapeText(m.name) + ' — ' + label
123
+ });
124
+
125
+ // Circle
126
+ var circle = svgEl('circle', {
127
+ cx: node.x,
128
+ cy: node.y,
129
+ r: NODE_RADIUS,
130
+ fill: color,
131
+ opacity: '0.2',
132
+ stroke: color,
133
+ 'stroke-width': '2'
134
+ });
135
+ g.appendChild(circle);
136
+
137
+ // Mission number
138
+ var text = svgEl('text', {
139
+ x: node.x,
140
+ y: node.y + 1,
141
+ 'text-anchor': 'middle',
142
+ 'dominant-baseline': 'central',
143
+ fill: color,
144
+ 'font-size': '14',
145
+ 'font-weight': '700'
146
+ });
147
+ text.textContent = m.number;
148
+ g.appendChild(text);
149
+
150
+ // Mission name label (below node)
151
+ var nameText = svgEl('text', {
152
+ x: node.x,
153
+ y: node.y + NODE_RADIUS + 14,
154
+ 'text-anchor': 'middle',
155
+ fill: '#999',
156
+ 'font-size': '9'
157
+ });
158
+ // Truncate long names
159
+ var displayName = m.name.length > 18 ? m.name.substring(0, 16) + '…' : m.name;
160
+ nameText.textContent = displayName;
161
+ g.appendChild(nameText);
162
+
163
+ // Status dot
164
+ var statusDot = svgEl('circle', {
165
+ cx: node.x + NODE_RADIUS - 4,
166
+ cy: node.y - NODE_RADIUS + 4,
167
+ r: '4',
168
+ fill: color
169
+ });
170
+ g.appendChild(statusDot);
171
+
172
+ // Focus indicator — highlight circle on keyboard focus
173
+ g.addEventListener('focus', function () { circle.setAttribute('stroke-width', '4'); });
174
+ g.addEventListener('blur', function () { circle.setAttribute('stroke-width', '2'); });
175
+
176
+ // Click handler — show details in the detail panel
177
+ g.addEventListener('click', function () { showDetail(m); });
178
+ g.addEventListener('keydown', function (e) {
179
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); showDetail(m); }
180
+ });
181
+
182
+ svg.appendChild(g);
183
+ });
184
+
185
+ container.appendChild(svg);
186
+
187
+ // Legend
188
+ var legend = document.createElement('div');
189
+ legend.style.cssText = 'display:flex;gap:12px;margin-top:8px;font-size:10px;color:var(--text-dim);flex-wrap:wrap;';
190
+ Object.keys(STATUS_COLORS).forEach(function (status) {
191
+ var item = document.createElement('span');
192
+ item.innerHTML = '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' +
193
+ STATUS_COLORS[status] + ';margin-right:4px;vertical-align:middle;"></span>' +
194
+ (STATUS_LABELS[status] || status);
195
+ legend.appendChild(item);
196
+ });
197
+ container.appendChild(legend);
198
+ }
199
+
200
+ // ── Detail panel ──────────────────────────────
201
+
202
+ function showDetail(mission) {
203
+ var panel = document.getElementById('prophecy-detail');
204
+ if (!panel) return;
205
+ var color = STATUS_COLORS[mission.status] || STATUS_COLORS.PENDING;
206
+ var label = STATUS_LABELS[mission.status] || escapeText(mission.status);
207
+ panel.innerHTML =
208
+ '<div style="font-weight:700;color:' + color + ';">Mission ' + escapeText(String(mission.number)) + '</div>' +
209
+ '<div style="margin:4px 0;font-size:13px;">' + escapeText(mission.name) + '</div>' +
210
+ '<div style="font-size:11px;color:var(--text-dim);">Status: ' + label + '</div>';
211
+ }
212
+
213
+ // ── Init ──────────────────────────────────────
214
+
215
+ // Expose render function for the main war-room.js to call
216
+ window.renderProphecyGraph = renderGraph;
217
+ })();
@@ -0,0 +1,219 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>War Room — VoidForge</title>
7
+ <link rel="icon" type="image/svg+xml" href="favicon.svg">
8
+ <link rel="stylesheet" href="styles.css">
9
+ <style>
10
+ body { min-height: 100vh; display: flex; flex-direction: column; background: var(--bg); color: var(--text); }
11
+
12
+ /* War Room Layout */
13
+ .war-room { display: grid; grid-template-columns: 1fr 280px; grid-template-rows: auto 1fr auto; height: 100vh; }
14
+ .war-room-header { grid-column: 1 / -1; padding: 12px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
15
+ .war-room-title { font-size: 16px; font-weight: 700; color: var(--accent); }
16
+ .war-room-version { font-size: 12px; color: var(--text-dim); }
17
+ .war-room-nav { display: flex; gap: 8px; }
18
+ .war-room-nav a { color: var(--text-dim); text-decoration: none; font-size: 13px; padding: 4px 10px; border-radius: 4px; }
19
+ .war-room-nav a:hover { background: var(--bg-card); color: var(--text); }
20
+
21
+ /* Main Panel Area */
22
+ .main-panels { padding: 16px; overflow-y: auto; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-content: start; }
23
+
24
+ /* Sidebar */
25
+ .sidebar { border-left: 1px solid var(--border); padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
26
+
27
+ /* Panel Card */
28
+ .panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; }
29
+ .panel-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); margin-bottom: 8px; }
30
+ .panel-full { grid-column: 1 / -1; }
31
+
32
+ /* Campaign Timeline */
33
+ .timeline { display: flex; gap: 4px; flex-wrap: wrap; }
34
+ .timeline-item { width: 28px; height: 28px; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; }
35
+ .timeline-complete { background: #065f46; color: #34d399; }
36
+ .timeline-active { background: #713f12; color: #fbbf24; animation: pulse 2s infinite; }
37
+ .timeline-pending { background: var(--bg); color: var(--text-dim); border: 1px solid var(--border); }
38
+ .timeline-blocked { background: #7f1d1d; color: #fca5a5; }
39
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
40
+
41
+ /* Phase Pipeline */
42
+ .pipeline { display: flex; flex-direction: column; gap: 3px; }
43
+ .pipeline-phase { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 3px 0; }
44
+ .pipeline-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
45
+ .pipeline-dot.complete { background: #34d399; }
46
+ .pipeline-dot.active { background: #fbbf24; animation: pulse 2s infinite; }
47
+ .pipeline-dot.pending { background: var(--border); }
48
+ .pipeline-dot.skipped { background: var(--text-dim); opacity: 0.5; }
49
+ .pipeline-label { color: var(--text-dim); }
50
+
51
+ /* Finding Scoreboard */
52
+ .scoreboard { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
53
+ .score-item { text-align: center; padding: 8px 4px; border-radius: 4px; }
54
+ .score-critical { background: #7f1d1d; }
55
+ .score-high { background: #713f12; }
56
+ .score-medium { background: #1e3a5f; }
57
+ .score-low { background: #1a2e1a; }
58
+ .score-count { font-size: 20px; font-weight: 700; }
59
+ .score-label { font-size: 9px; text-transform: uppercase; color: var(--text-dim); margin-top: 2px; }
60
+
61
+ /* Context Gauge */
62
+ .gauge { position: relative; width: 80px; height: 80px; margin: 0 auto; }
63
+ .gauge svg { width: 80px; height: 80px; transform: rotate(-90deg); }
64
+ .gauge-track { fill: none; stroke: var(--border); stroke-width: 6; }
65
+ .gauge-fill { fill: none; stroke-width: 6; stroke-linecap: round; transition: stroke-dashoffset 0.5s, stroke 0.5s; }
66
+ .gauge-text { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 700; }
67
+
68
+ /* Agent Activity Ticker */
69
+ .ticker { grid-column: 1 / -1; border-top: 1px solid var(--border); padding: 6px 20px; font-size: 11px; color: var(--text-dim); overflow: hidden; white-space: nowrap; }
70
+ .ticker-item { display: inline; margin-right: 24px; }
71
+ .ticker-agent { color: var(--accent); font-weight: 600; }
72
+
73
+ /* Deploy Status */
74
+ .deploy-badge { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
75
+ .deploy-dot { width: 8px; height: 8px; border-radius: 50%; }
76
+ .deploy-dot.live { background: #34d399; }
77
+ .deploy-dot.down { background: #ef4444; }
78
+ .deploy-dot.unknown { background: var(--border); }
79
+
80
+ /* Version Badge */
81
+ .version-badge { font-size: 13px; font-weight: 600; color: var(--accent); }
82
+ .branch-status { font-size: 11px; color: var(--text-dim); margin-top: 4px; }
83
+
84
+ /* Focus management */
85
+ a:focus-visible, .panel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
86
+
87
+ /* Responsive — stack on narrow viewports */
88
+ @media (max-width: 700px) {
89
+ .war-room { grid-template-columns: 1fr; }
90
+ .sidebar { border-left: none; border-top: 1px solid var(--border); }
91
+ .main-panels { grid-template-columns: 1fr; }
92
+ }
93
+
94
+ /* Reduced motion */
95
+ @media (prefers-reduced-motion: reduce) {
96
+ .timeline-active, .pipeline-dot.active { animation: none; }
97
+ }
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <a href="#war-room-grid" class="skip-nav">Skip to main content</a>
102
+ <noscript><div class="noscript-msg">VoidForge requires JavaScript to run.</div></noscript>
103
+ <div class="war-room">
104
+ <!-- Header -->
105
+ <header class="war-room-header">
106
+ <div>
107
+ <div class="war-room-title">War Room</div>
108
+ <div class="war-room-version" id="version-display">VoidForge v—</div>
109
+ </div>
110
+ <nav class="war-room-nav" aria-label="War Room navigation">
111
+ <a href="lobby.html">Lobby</a>
112
+ <a href="war-room.html" aria-current="page" style="background:var(--bg-card);color:var(--text);">War Room</a>
113
+ </nav>
114
+ </header>
115
+
116
+ <!-- Main Panels -->
117
+ <main class="main-panels" id="war-room-grid">
118
+ <!-- Campaign Timeline -->
119
+ <div class="panel panel-full" role="region" aria-label="Campaign Timeline">
120
+ <h2 class="panel-title" id="title-timeline">Campaign Timeline</h2>
121
+ <div class="timeline" id="campaign-timeline" aria-labelledby="title-timeline">
122
+ <div class="timeline-item timeline-pending">—</div>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Phase Pipeline -->
127
+ <div class="panel" role="region" aria-label="Phase Pipeline">
128
+ <h2 class="panel-title" id="title-pipeline">Phase Pipeline</h2>
129
+ <div class="pipeline" id="phase-pipeline">
130
+ <div class="pipeline-phase"><span class="pipeline-dot pending"></span><span class="pipeline-label">No active build</span></div>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- Finding Scoreboard -->
135
+ <div class="panel" role="region" aria-label="Finding Scoreboard">
136
+ <h2 class="panel-title" id="title-findings">Findings</h2>
137
+ <div class="scoreboard" id="finding-scoreboard" aria-labelledby="title-findings" role="group">
138
+ <div class="score-item score-critical"><div class="score-count" id="score-critical">0</div><div class="score-label">Critical</div></div>
139
+ <div class="score-item score-high"><div class="score-count" id="score-high">0</div><div class="score-label">High</div></div>
140
+ <div class="score-item score-medium"><div class="score-count" id="score-medium">0</div><div class="score-label">Medium</div></div>
141
+ <div class="score-item score-low"><div class="score-count" id="score-low">0</div><div class="score-label">Low</div></div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Experiment Dashboard -->
146
+ <div class="panel" role="region" aria-label="Experiments">
147
+ <h2 class="panel-title" id="title-experiments">Experiments</h2>
148
+ <div id="experiment-dashboard" aria-labelledby="title-experiments" style="font-size:13px;color:var(--text-dim);">No experiments</div>
149
+ </div>
150
+
151
+ <!-- PRD Coverage -->
152
+ <div class="panel panel-full" role="region" aria-label="PRD Coverage">
153
+ <h2 class="panel-title" id="title-prd">PRD Coverage</h2>
154
+ <div id="prd-coverage" style="font-size:13px;color:var(--text-dim);">No campaign active</div>
155
+ </div>
156
+
157
+ <!-- Prophecy Graph -->
158
+ <div class="panel panel-full" role="region" aria-label="Prophecy Graph">
159
+ <h2 class="panel-title" id="title-prophecy">Prophecy Graph</h2>
160
+ <div id="prophecy-graph" style="min-height:100px;"></div>
161
+ <div id="prophecy-detail" style="font-size:12px;margin-top:6px;min-height:40px;color:var(--text-dim);"></div>
162
+ </div>
163
+ </main>
164
+
165
+ <!-- Sidebar -->
166
+ <aside class="sidebar">
167
+ <!-- Context Gauge -->
168
+ <div class="panel">
169
+ <h2 class="panel-title">Context Usage</h2>
170
+ <div class="gauge" id="context-gauge" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-label="Context usage">
171
+ <svg viewBox="0 0 36 36" role="img" aria-hidden="true">
172
+ <circle class="gauge-track" cx="18" cy="18" r="14" />
173
+ <circle class="gauge-fill" cx="18" cy="18" r="14"
174
+ stroke-dasharray="88" stroke-dashoffset="88"
175
+ id="gauge-fill" stroke="#34d399" />
176
+ </svg>
177
+ <div class="gauge-text" id="gauge-text" aria-live="polite">—%</div>
178
+ </div>
179
+ </div>
180
+
181
+ <!-- Version & Branch -->
182
+ <div class="panel">
183
+ <h2 class="panel-title">Version</h2>
184
+ <div class="version-badge" id="version-badge">—</div>
185
+ <div class="branch-status" id="branch-status">—</div>
186
+ </div>
187
+
188
+ <!-- Deploy Status -->
189
+ <div class="panel">
190
+ <h2 class="panel-title">Deploy</h2>
191
+ <div class="deploy-badge" id="deploy-status">
192
+ <span class="deploy-dot unknown"></span>
193
+ <span>No deploy data</span>
194
+ </div>
195
+ </div>
196
+
197
+ <!-- Test Suite -->
198
+ <div class="panel">
199
+ <h2 class="panel-title">Tests</h2>
200
+ <div id="test-status" style="font-size:13px;color:var(--text-dim);">No test data</div>
201
+ </div>
202
+
203
+ <!-- Cost -->
204
+ <div class="panel">
205
+ <h2 class="panel-title">Cost</h2>
206
+ <div id="cost-display" style="font-size:13px;color:var(--text-dim);">No cost data</div>
207
+ </div>
208
+ </aside>
209
+
210
+ <!-- Agent Activity Ticker -->
211
+ <footer class="ticker" id="agent-ticker" aria-live="polite" aria-label="Agent activity feed">
212
+ <span class="ticker-item"><span class="ticker-agent">Sisko</span> standing by...</span>
213
+ </footer>
214
+ </div>
215
+
216
+ <script src="war-room-prophecy.js"></script>
217
+ <script src="war-room.js"></script>
218
+ </body>
219
+ </html>