@worca/ui 0.15.3 → 0.17.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.15.3",
3
+ "version": "0.17.0",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -50,6 +50,7 @@
50
50
  "prepublishOnly": "npm run build && npm test",
51
51
  "test": "vitest run",
52
52
  "test:watch": "vitest",
53
+ "test:coverage": "vitest run --coverage",
53
54
  "test:browser": "playwright test",
54
55
  "test:browser:ui": "playwright test --ui",
55
56
  "lint": "biome check",
@@ -70,6 +71,7 @@
70
71
  "devDependencies": {
71
72
  "@biomejs/biome": "^2.2.4",
72
73
  "@playwright/test": "^1.58.2",
74
+ "@vitest/coverage-v8": "^4.0.15",
73
75
  "esbuild": "^0.27.1",
74
76
  "jsdom": "^29.0.1",
75
77
  "vitest": "^4.0.15"
@@ -431,6 +431,16 @@ export class ProcessManager {
431
431
  const statusDir = resumeCtx ? resumeCtx.worcaDir : this.worcaDir;
432
432
  args.push('--status-dir', statusDir);
433
433
 
434
+ // Worktree runs: registry lives in the parent project's .worca, not
435
+ // the worktree's. run_worktree.py passes --registry-base on initial
436
+ // launch; resume must do the same so update_pipeline() lands on the
437
+ // right registry entry. Without this, the runner's terminal /
438
+ // resume-flip-to-running registry updates silently no-op against a
439
+ // non-existent <worktree>/.worca/multi/pipelines.d/<id>.json.
440
+ if (resumeCtx && resumeCtx.worcaDir !== this.worcaDir) {
441
+ args.push('--registry-base', this.worcaDir);
442
+ }
443
+
434
444
  // _find_active_runs filters out runs whose pipeline_status is in
435
445
  // {completed, interrupted}. To resume an interrupted/failed run, flip
436
446
  // the top-level status to "resuming" so the runner can pick it up;
@@ -71,7 +71,11 @@ function validateRunId(runId) {
71
71
  // Re-exported from run-dir-resolver so callers (including older tests) can
72
72
  // continue importing from project-routes. The implementation now overlays
73
73
  // worktree runs registered in <worcaDir>/multi/pipelines.d/.
74
- import { findRunStatusPath, readPipelineOverlay } from './run-dir-resolver.js';
74
+ import {
75
+ findRunStatusPath,
76
+ readPipelineOverlay,
77
+ updatePipelineStatus,
78
+ } from './run-dir-resolver.js';
75
79
  export { findRunStatusPath };
76
80
 
77
81
  /** Validate a branch name — alphanumeric, dots, hyphens, underscores, slashes */
@@ -1086,6 +1090,11 @@ export function createProjectScopedRoutes({
1086
1090
  st.completed_at = new Date().toISOString();
1087
1091
  writeFileSync(statusPath, `${JSON.stringify(st, null, 2)}\n`, 'utf8');
1088
1092
 
1093
+ // Mirror into the multi-pipeline registry so global-mode views don't
1094
+ // keep reporting the run as "running". Best-effort — the registry entry
1095
+ // only exists for worktree runs.
1096
+ updatePipelineStatus(worcaDir, runId, 'cancelled');
1097
+
1089
1098
  const { broadcast, scheduleRefresh } = req.app.locals;
1090
1099
  if (broadcast) broadcast('run-cancelled', { runId });
1091
1100
  if (scheduleRefresh) scheduleRefresh(req.project?.name);
@@ -13,7 +13,7 @@
13
13
  * 3. `<worcaDir>/multi/pipelines.d/<runId>.json` → `<worktree_path>/.worca/runs/<runId>/`
14
14
  */
15
15
 
16
- import { existsSync, readFileSync } from 'node:fs';
16
+ import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
18
 
19
19
  /**
@@ -77,3 +77,37 @@ export function readPipelineOverlay(worcaDir, runId) {
77
77
  return null;
78
78
  }
79
79
  }
80
+
81
+ /**
82
+ * Update the multi-pipeline registry entry's status field.
83
+ *
84
+ * Mirrors src/worca/orchestrator/registry.py:update_pipeline() — terminal
85
+ * status (cancelled/completed/failed/interrupted) only. The Python runner
86
+ * updates the registry on its own terminal paths; this helper exists for
87
+ * callers (the UI cancel route) that write status.json directly without
88
+ * going through the runner.
89
+ *
90
+ * Returns true when the registry entry existed and was updated, false when
91
+ * there's no registry entry for this runId (e.g. local in-place run).
92
+ *
93
+ * @param {string} worcaDir - the parent project's .worca directory
94
+ * @param {string} runId
95
+ * @param {string} status - new status value (e.g. 'cancelled')
96
+ */
97
+ export function updatePipelineStatus(worcaDir, runId, status) {
98
+ const regPath = join(worcaDir, 'multi', 'pipelines.d', `${runId}.json`);
99
+ if (!existsSync(regPath)) return false;
100
+ try {
101
+ const data = JSON.parse(readFileSync(regPath, 'utf8'));
102
+ data.status = status;
103
+ data.updated_at = new Date().toISOString();
104
+ // Match Python's atomic write: write to .tmp + rename. Avoids partial
105
+ // writes if the process crashes mid-write.
106
+ const tmpPath = `${regPath}.tmp`;
107
+ writeFileSync(tmpPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
108
+ renameSync(tmpPath, regPath);
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }