@worca/ui 0.3.0 → 0.3.1-rc.2

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
@@ -528,6 +528,13 @@ h1, h2, h3, h4, h5, h6 {
528
528
  font-size: 13px;
529
529
  }
530
530
 
531
+ .run-template {
532
+ display: flex;
533
+ align-items: center;
534
+ gap: 6px;
535
+ font-size: 13px;
536
+ }
537
+
531
538
  .pipeline-cost-strip {
532
539
  display: flex;
533
540
  flex-wrap: wrap;
@@ -1247,6 +1254,15 @@ sl-details.log-history-panel::part(content) {
1247
1254
  font-variant-numeric: tabular-nums;
1248
1255
  }
1249
1256
 
1257
+ .run-card-template {
1258
+ display: flex;
1259
+ align-items: center;
1260
+ gap: 4px;
1261
+ padding-left: 26px;
1262
+ font-size: 12px;
1263
+ color: var(--muted);
1264
+ }
1265
+
1250
1266
  .run-card-stages {
1251
1267
  display: flex;
1252
1268
  flex-wrap: wrap;
@@ -2249,6 +2265,43 @@ sl-details.live-output-panel::part(content) {
2249
2265
  font-weight: 500;
2250
2266
  }
2251
2267
 
2268
+ .new-run-section sl-select {
2269
+ width: 100%;
2270
+ }
2271
+
2272
+ /* Template group labels */
2273
+ .template-group-label {
2274
+ display: block;
2275
+ font-size: 10px;
2276
+ font-weight: 700;
2277
+ letter-spacing: 0.05em;
2278
+ text-transform: uppercase;
2279
+ color: var(--muted);
2280
+ padding: 6px 12px 2px;
2281
+ }
2282
+
2283
+ /* Indent grouped template options and bold the name */
2284
+ sl-option.template-grouped::part(base) {
2285
+ padding-left: 24px;
2286
+ flex-wrap: wrap;
2287
+ }
2288
+
2289
+ sl-option.template-grouped::part(label) {
2290
+ font-weight: 500;
2291
+ }
2292
+
2293
+ /* Description wraps below name, aligned with label */
2294
+ sl-option.template-grouped::part(suffix) {
2295
+ flex-basis: 100%;
2296
+ font-size: 11px;
2297
+ line-height: 1.4;
2298
+ color: var(--muted);
2299
+ white-space: nowrap;
2300
+ overflow: hidden;
2301
+ text-overflow: ellipsis;
2302
+ padding: 0 0 4px;
2303
+ }
2304
+
2252
2305
  /* Plan file autocomplete */
2253
2306
  .plan-autocomplete {
2254
2307
  position: relative;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.3.0",
3
+ "version": "0.3.1-rc.2",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -210,6 +210,9 @@ export class ProcessManager {
210
210
  if (opts.branch) {
211
211
  args.push('--branch', opts.branch);
212
212
  }
213
+ if (opts.template) {
214
+ args.push('--template', opts.template);
215
+ }
213
216
 
214
217
  const env = { ...process.env };
215
218
  delete env.CLAUDECODE;
@@ -73,6 +73,9 @@ export function findRunStatusPath(worcaDir, runId) {
73
73
 
74
74
  /** Validate a branch name — alphanumeric, dots, hyphens, underscores, slashes */
75
75
  const BRANCH_RE = /^[\w.\-/]+$/;
76
+
77
+ /** Validate a template identifier — lowercase alphanumeric and hyphens, 1-64 chars */
78
+ const TEMPLATE_RE = /^[a-z0-9-]{1,64}$/;
76
79
  function validateBranch(branch) {
77
80
  return (
78
81
  typeof branch === 'string' && branch.length <= 200 && BRANCH_RE.test(branch)
@@ -472,8 +475,16 @@ export function createProjectScopedRoutes() {
472
475
  router.post('/runs', requireWorcaDir, async (req, res) => {
473
476
  const body = req.body || {};
474
477
 
475
- let { sourceType, sourceValue, prompt, planFile, msize, mloops, branch } =
476
- body;
478
+ let {
479
+ sourceType,
480
+ sourceValue,
481
+ prompt,
482
+ planFile,
483
+ msize,
484
+ mloops,
485
+ branch,
486
+ template,
487
+ } = body;
477
488
  if (body.inputType && sourceType === undefined) {
478
489
  if (body.inputType === 'prompt') {
479
490
  sourceType = 'none';
@@ -534,6 +545,15 @@ export function createProjectScopedRoutes() {
534
545
  }
535
546
  }
536
547
 
548
+ if (template !== undefined && template !== null) {
549
+ if (typeof template !== 'string' || !TEMPLATE_RE.test(template)) {
550
+ return res.status(400).json({
551
+ ok: false,
552
+ error: 'template must match ^[a-z0-9-]{1,64}$',
553
+ });
554
+ }
555
+ }
556
+
537
557
  const hasSource = sourceType !== 'none' && sourceValue;
538
558
  const hasPlan = typeof planFile === 'string' && planFile.trim().length > 0;
539
559
  const hasPrompt = typeof prompt === 'string' && prompt.length > 0;
@@ -561,6 +581,7 @@ export function createProjectScopedRoutes() {
561
581
  mloops: mloopsVal,
562
582
  planFile: hasPlan ? planFile.trim() : undefined,
563
583
  branch: branch || undefined,
584
+ template: template || undefined,
564
585
  });
565
586
  const { broadcast } = req.app.locals;
566
587
  if (broadcast) broadcast('run-started', { pid: result.pid });
@@ -1142,6 +1163,45 @@ export function createProjectScopedRoutes() {
1142
1163
  }
1143
1164
  });
1144
1165
 
1166
+ // GET /api/projects/:projectId/templates — list available pipeline templates
1167
+ router.get('/templates', (req, res) => {
1168
+ const root = req.project.projectRoot;
1169
+ const tiers = [
1170
+ { tier: 'worca', dir: join(root, '.claude', 'worca', 'templates') },
1171
+ { tier: 'project', dir: join(root, '.claude', 'templates') },
1172
+ { tier: 'user', dir: join(homedir(), '.worca', 'templates') },
1173
+ ];
1174
+
1175
+ const templates = [];
1176
+ for (const { tier, dir } of tiers) {
1177
+ if (!existsSync(dir)) continue;
1178
+ let entries;
1179
+ try {
1180
+ entries = readdirSync(dir, { withFileTypes: true });
1181
+ } catch {
1182
+ continue;
1183
+ }
1184
+ for (const entry of entries) {
1185
+ if (!entry.isDirectory()) continue;
1186
+ const manifestPath = join(dir, entry.name, 'template.json');
1187
+ if (!existsSync(manifestPath)) continue;
1188
+ try {
1189
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
1190
+ templates.push({
1191
+ id: manifest.id || entry.name,
1192
+ name: manifest.name || entry.name,
1193
+ description: manifest.description || '',
1194
+ tier,
1195
+ });
1196
+ } catch {
1197
+ /* skip malformed manifests */
1198
+ }
1199
+ }
1200
+ }
1201
+
1202
+ res.json({ ok: true, templates });
1203
+ });
1204
+
1145
1205
  // GET /api/projects/:projectId/worca-status — check worca installation state
1146
1206
  router.get('/worca-status', (req, res) => {
1147
1207
  const installed = checkWorcaInstalled(req.project.projectRoot);