@worca/ui 0.3.1-rc.5 → 0.5.0-rc.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.
package/app/styles.css CHANGED
@@ -25,9 +25,9 @@
25
25
  --status-resuming: #3b82f6;
26
26
  --status-skipped: #94a3b8;
27
27
  /* legacy */
28
- --status-in-progress: #f59e0b;
28
+ --status-in-progress: #3b82f6;
29
29
  --status-error: #ef4444;
30
- --status-blocked: #3b82f6;
30
+ --status-blocked: #f59e0b;
31
31
 
32
32
  /* Log viewer */
33
33
  --log-bg: #0f172a;
@@ -47,7 +47,7 @@
47
47
  /* Shoelace token mapping */
48
48
  --sl-color-primary-600: var(--accent);
49
49
  --sl-color-success-600: var(--status-completed);
50
- --sl-color-warning-600: var(--status-in-progress);
50
+ --sl-color-warning-600: var(--status-blocked);
51
51
  --sl-color-danger-600: var(--status-error);
52
52
  --sl-color-neutral-600: var(--status-pending);
53
53
  --sl-border-radius-medium: var(--radius);
@@ -577,11 +577,11 @@ h1, h2, h3, h4, h5, h6 {
577
577
  }
578
578
 
579
579
  .timing-bar-thinking {
580
- background: var(--status-blocked, #3b82f6);
580
+ background: var(--status-blocked, #f59e0b);
581
581
  }
582
582
 
583
583
  .timing-bar-tools {
584
- background: var(--status-in-progress, #f59e0b);
584
+ background: var(--status-in-progress, #3b82f6);
585
585
  }
586
586
 
587
587
  .timing-bar-rest {
@@ -2323,6 +2323,21 @@ sl-option.template-grouped::part(suffix) {
2323
2323
  padding: 0 0 4px;
2324
2324
  }
2325
2325
 
2326
+ /* Fix contrast on selected/current option description */
2327
+ sl-option.template-grouped[aria-selected="true"]::part(suffix),
2328
+ sl-option.template-grouped:focus::part(suffix) {
2329
+ color: inherit;
2330
+ opacity: 0.85;
2331
+ }
2332
+
2333
+ /* Selected template description below dropdown */
2334
+ .template-description {
2335
+ font-size: 12px;
2336
+ color: var(--muted);
2337
+ margin-top: 6px;
2338
+ line-height: 1.5;
2339
+ }
2340
+
2326
2341
  /* Plan file autocomplete */
2327
2342
  .plan-autocomplete {
2328
2343
  position: relative;
@@ -2598,6 +2613,10 @@ sl-option.template-grouped::part(suffix) {
2598
2613
  border-style: dashed;
2599
2614
  }
2600
2615
 
2616
+ .beads-kanban-card--in_progress {
2617
+ border-color: var(--status-in-progress);
2618
+ }
2619
+
2601
2620
  .beads-kanban-card-header {
2602
2621
  display: flex;
2603
2622
  align-items: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.3.1-rc.5",
3
+ "version": "0.5.0-rc.1",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -26,6 +26,7 @@
26
26
  "server/*.js",
27
27
  "!server/*.test.js",
28
28
  "!server/test/",
29
+ "app/favicon-*.svg",
29
30
  "app/index.html",
30
31
  "app/main.bundle.js",
31
32
  "app/main.bundle.js.map",
package/server/app.js CHANGED
@@ -427,6 +427,16 @@ export function createApp(options = {}) {
427
427
  );
428
428
  }
429
429
 
430
+ // ─── Dynamic favicon ──────────────────────────────────────────────────
431
+ // Serve mode-specific favicon before express.static so it takes precedence.
432
+ app.get('/favicon.svg', (_req, res) => {
433
+ const faviconFile = projectRoot
434
+ ? 'favicon-project.svg'
435
+ : 'favicon-global.svg';
436
+ res.setHeader('Content-Type', 'image/svg+xml');
437
+ res.sendFile(faviconFile, { root: appDir });
438
+ });
439
+
430
440
  app.use(express.static(appDir));
431
441
  app.get('/{*splat}', (_req, res) => {
432
442
  res.sendFile('index.html', { root: appDir });
@@ -8,7 +8,7 @@ import { existsSync, watch } from 'node:fs';
8
8
  import { join, resolve } from 'node:path';
9
9
  import { listIssues } from './beads-reader.js';
10
10
 
11
- const BEADS_DEBOUNCE_MS = 300;
11
+ const BEADS_DEBOUNCE_MS = 500;
12
12
 
13
13
  /**
14
14
  * @param {{ worcaDir: string, broadcaster: { broadcast: Function }, projectId?: string }} deps
@@ -35,24 +35,6 @@ import {
35
35
  import { readSettings } from './settings-reader.js';
36
36
  import { discoverRuns } from './watcher.js';
37
37
 
38
- // Legacy fallback prefixes — only used when status.json lacks a stored prompt
39
- const STAGE_PROMPT_PREFIX = {
40
- plan: 'Create a detailed implementation plan for the following work request. Write the plan to the designated plan file.\n\nWork request: ',
41
- coordinate:
42
- 'Decompose the following work request into Beads tasks with dependencies. Do NOT implement anything — only create tasks using `bd create`.\n\nWork request: ',
43
- implement:
44
- 'Implement the code changes described in the work request. Follow the plan and complete the tasks assigned to you.\n\nWork request: ',
45
- test: 'Review and test the implementation for the following work request. Run tests and report results. Do NOT modify code.\n\nWork request: ',
46
- review:
47
- 'Review the code changes for the following work request. Check for correctness, style, and adherence to the plan. Do NOT modify code.\n\nWork request: ',
48
- pr: 'Create a pull request for the following work request. Summarize the changes and ensure the commit history is clean.\n\nWork request: ',
49
- };
50
-
51
- function _buildStagePrompt(stage, rawPrompt) {
52
- const prefix = STAGE_PROMPT_PREFIX[stage];
53
- return prefix ? prefix + rawPrompt : rawPrompt;
54
- }
55
-
56
38
  /**
57
39
  * @param {{
58
40
  * watcherSets: Map<string, import('./watcher-set.js').WatcherSet>,
@@ -174,6 +156,7 @@ export function createMessageRouter({
174
156
  return;
175
157
  }
176
158
  const agentName = run.stages?.[stage]?.agent || stage;
159
+ const effectiveRunId = run.run_id || runId;
177
160
 
178
161
  const iterations = run.stages?.[stage]?.iterations || [];
179
162
  const iterationPrompts = iterations.map((iter, idx) => {
@@ -181,62 +164,116 @@ export function createMessageRouter({
181
164
  return { iteration: iter.number ?? idx, prompt };
182
165
  });
183
166
 
184
- const storedPrompt = run.stages?.[stage]?.prompt;
185
- let fallbackPrompt;
186
- let promptSource;
187
- if (storedPrompt) {
188
- fallbackPrompt = storedPrompt;
189
- promptSource = 'actual';
190
- } else {
191
- const rawPrompt =
192
- run.work_request?.description || run.work_request?.title || '';
193
- fallbackPrompt = _buildStagePrompt(stage, rawPrompt);
194
- promptSource = 'reconstructed';
195
- }
196
-
167
+ // User message (-p) — stored in status.json
168
+ const userPrompt = run.stages?.[stage]?.prompt || null;
197
169
  const hasIterationPrompts = iterationPrompts.some(
198
170
  (ip) => ip.prompt != null,
199
171
  );
200
- if (!hasIterationPrompts) {
172
+ if (!hasIterationPrompts && userPrompt) {
201
173
  for (const ip of iterationPrompts) {
202
- ip.prompt = fallbackPrompt;
174
+ ip.prompt = userPrompt;
203
175
  }
204
176
  }
205
177
 
206
- let agentInstructions = null;
207
- const candidates = [
208
- join(
209
- proj.worcaDir,
210
- 'runs',
211
- run.run_id || runId,
212
- 'agents',
213
- `${agentName}.md`,
214
- ),
215
- join(
216
- proj.worcaDir,
217
- 'results',
218
- run.run_id || runId,
219
- 'agents',
220
- `${agentName}.md`,
221
- ),
222
- ];
223
- for (const p of candidates) {
224
- if (existsSync(p)) {
225
- try {
226
- agentInstructions = readFileSync(p, 'utf8');
227
- } catch {
228
- /* ignore */
178
+ // Resolved agent prompt — the full document the agent actually received.
179
+ // Prefer per-iteration resolved files (W-037+), fall back to the
180
+ // unresolved agent template for pre-W-037 runs.
181
+ let resolvedPrompt = null;
182
+ const resolvedIterationPrompts = [];
183
+
184
+ // Collect per-iteration resolved files
185
+ // Filename format: {stage}-{agent}-iter-{N}.md (W-037+)
186
+ // Fallback: {agent}-iter-{N}.md (early W-037 runs)
187
+ for (const iter of iterations) {
188
+ const iterNum = iter.number ?? 0;
189
+ const resolvedCandidates = [
190
+ join(
191
+ proj.worcaDir,
192
+ 'runs',
193
+ effectiveRunId,
194
+ 'agents',
195
+ 'resolved',
196
+ `${stage}-${agentName}-iter-${iterNum}.md`,
197
+ ),
198
+ join(
199
+ proj.worcaDir,
200
+ 'results',
201
+ effectiveRunId,
202
+ 'agents',
203
+ 'resolved',
204
+ `${stage}-${agentName}-iter-${iterNum}.md`,
205
+ ),
206
+ // Fallback for early W-037 runs without stage prefix
207
+ join(
208
+ proj.worcaDir,
209
+ 'runs',
210
+ effectiveRunId,
211
+ 'agents',
212
+ 'resolved',
213
+ `${agentName}-iter-${iterNum}.md`,
214
+ ),
215
+ join(
216
+ proj.worcaDir,
217
+ 'results',
218
+ effectiveRunId,
219
+ 'agents',
220
+ 'resolved',
221
+ `${agentName}-iter-${iterNum}.md`,
222
+ ),
223
+ ];
224
+ let content = null;
225
+ for (const p of resolvedCandidates) {
226
+ if (existsSync(p)) {
227
+ try {
228
+ content = readFileSync(p, 'utf8');
229
+ } catch {
230
+ /* ignore */
231
+ }
232
+ break;
233
+ }
234
+ }
235
+ resolvedIterationPrompts.push({ iteration: iterNum, prompt: content });
236
+ if (!resolvedPrompt && content) resolvedPrompt = content;
237
+ }
238
+
239
+ // Fall back to unresolved agent template (pre-W-037 runs)
240
+ if (!resolvedPrompt) {
241
+ const templateCandidates = [
242
+ join(
243
+ proj.worcaDir,
244
+ 'runs',
245
+ effectiveRunId,
246
+ 'agents',
247
+ `${agentName}.md`,
248
+ ),
249
+ join(
250
+ proj.worcaDir,
251
+ 'results',
252
+ effectiveRunId,
253
+ 'agents',
254
+ `${agentName}.md`,
255
+ ),
256
+ ];
257
+ for (const p of templateCandidates) {
258
+ if (existsSync(p)) {
259
+ try {
260
+ resolvedPrompt = readFileSync(p, 'utf8');
261
+ } catch {
262
+ /* ignore */
263
+ }
264
+ break;
229
265
  }
230
- break;
231
266
  }
232
267
  }
268
+
233
269
  ws.send(
234
270
  JSON.stringify(
235
271
  makeOk(req, {
236
- agentInstructions,
237
- userPrompt: fallbackPrompt,
272
+ agentInstructions: resolvedPrompt,
273
+ userPrompt,
238
274
  iterationPrompts,
239
- promptSource,
275
+ resolvedIterationPrompts,
276
+ promptSource: userPrompt ? 'actual' : 'none',
240
277
  agent: agentName,
241
278
  }),
242
279
  ),
@@ -614,6 +651,12 @@ export function createMessageRouter({
614
651
  return;
615
652
  }
616
653
  const issues = await listIssuesByLabel(beadsDbPath, `run:${runId}`);
654
+ console.log(
655
+ '[list-beads-by-run] runId=%s count=%d statuses=%o',
656
+ runId,
657
+ issues.length,
658
+ issues.map((i) => i.status),
659
+ );
617
660
  ws.send(JSON.stringify(makeOk(req, { issues, runId })));
618
661
  return;
619
662
  }