work-kit-cli 0.2.7 → 0.2.8
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/cli/src/observer/renderer.ts +79 -14
- package/package.json +1 -1
|
@@ -171,10 +171,10 @@ function subStageIndicator(status: string, tick: number): string {
|
|
|
171
171
|
|
|
172
172
|
function phaseName(name: string, status: string, tick: number): string {
|
|
173
173
|
switch (status) {
|
|
174
|
-
case "completed": return
|
|
175
|
-
case "in-progress": return tick % 2 === 0 ? boldCyan(name) : cyan(name);
|
|
176
|
-
case "waiting": return yellow(name);
|
|
177
|
-
case "failed": return red(name);
|
|
174
|
+
case "completed": return boldGreen(name);
|
|
175
|
+
case "in-progress": return tick % 2 === 0 ? boldCyan(name) : bold(cyan(name));
|
|
176
|
+
case "waiting": return bold(yellow(name));
|
|
177
|
+
case "failed": return bold(red(name));
|
|
178
178
|
default: return dim(name);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -201,17 +201,79 @@ function renderGatedBadge(): string {
|
|
|
201
201
|
|
|
202
202
|
// ── Phase Pipeline ──────────────────────────────────────────────────
|
|
203
203
|
|
|
204
|
+
function phaseDuration(p: { status: string; startedAt?: string; completedAt?: string }): string {
|
|
205
|
+
if (p.status === "completed" && p.startedAt && p.completedAt) {
|
|
206
|
+
const ms = new Date(p.completedAt).getTime() - new Date(p.startedAt).getTime();
|
|
207
|
+
const sec = Math.floor(ms / 1000);
|
|
208
|
+
if (sec < 60) return `${sec}s`;
|
|
209
|
+
const min = Math.floor(sec / 60);
|
|
210
|
+
if (min < 60) return `${min}m`;
|
|
211
|
+
const hr = Math.floor(min / 60);
|
|
212
|
+
const remMin = min % 60;
|
|
213
|
+
return remMin > 0 ? `${hr}h${remMin}m` : `${hr}h`;
|
|
214
|
+
}
|
|
215
|
+
if (p.status === "in-progress" && p.startedAt) {
|
|
216
|
+
return formatDuration(p.startedAt);
|
|
217
|
+
}
|
|
218
|
+
if (p.status === "waiting" && p.startedAt) {
|
|
219
|
+
return formatDuration(p.startedAt);
|
|
220
|
+
}
|
|
221
|
+
return "";
|
|
222
|
+
}
|
|
223
|
+
|
|
204
224
|
function renderPhasePipeline(
|
|
205
225
|
phases: WorkItemView["phases"],
|
|
206
226
|
tick: number
|
|
207
|
-
): string {
|
|
208
|
-
const
|
|
209
|
-
|
|
227
|
+
): string[] {
|
|
228
|
+
const connector = dim(" ── ");
|
|
229
|
+
const connectorLen = 4; // " ── "
|
|
230
|
+
|
|
231
|
+
// Build top line (icons + names) and bottom line (timing, aligned)
|
|
232
|
+
const topParts: string[] = [];
|
|
233
|
+
const bottomParts: string[] = [];
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < phases.length; i++) {
|
|
236
|
+
const p = phases[i];
|
|
210
237
|
const icon = phaseIndicator(p.status, tick);
|
|
211
238
|
const name = phaseName(p.name, p.status, tick);
|
|
212
|
-
|
|
239
|
+
const segment = `${icon} ${name}`;
|
|
240
|
+
topParts.push(segment);
|
|
241
|
+
|
|
242
|
+
// Calculate plain width of this segment for alignment
|
|
243
|
+
const segPlainLen = stripAnsi(segment).length;
|
|
244
|
+
const dur = phaseDuration(p);
|
|
245
|
+
|
|
246
|
+
if (dur) {
|
|
247
|
+
// Color the duration based on status
|
|
248
|
+
let durStyled: string;
|
|
249
|
+
if (p.status === "completed") durStyled = dim(dur);
|
|
250
|
+
else if (p.status === "in-progress") durStyled = cyan(dur);
|
|
251
|
+
else if (p.status === "waiting") durStyled = yellow(dur);
|
|
252
|
+
else durStyled = dim(dur);
|
|
253
|
+
|
|
254
|
+
// Center the duration under the segment
|
|
255
|
+
const durPlainLen = stripAnsi(durStyled).length;
|
|
256
|
+
const pad = Math.max(0, Math.floor((segPlainLen - durPlainLen) / 2));
|
|
257
|
+
bottomParts.push(" ".repeat(pad) + durStyled + " ".repeat(Math.max(0, segPlainLen - durPlainLen - pad)));
|
|
258
|
+
} else {
|
|
259
|
+
bottomParts.push(" ".repeat(segPlainLen));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Add connector spacing to bottom line too
|
|
263
|
+
if (i < phases.length - 1) {
|
|
264
|
+
bottomParts.push(" ".repeat(connectorLen));
|
|
265
|
+
}
|
|
213
266
|
}
|
|
214
|
-
|
|
267
|
+
|
|
268
|
+
const topLine = topParts.join(connector);
|
|
269
|
+
const bottomLine = bottomParts.join("");
|
|
270
|
+
|
|
271
|
+
// Only show bottom line if there's at least one duration
|
|
272
|
+
const hasAnyDuration = phases.some(p => phaseDuration(p) !== "");
|
|
273
|
+
if (hasAnyDuration) {
|
|
274
|
+
return [topLine, bottomLine];
|
|
275
|
+
}
|
|
276
|
+
return [topLine];
|
|
215
277
|
}
|
|
216
278
|
|
|
217
279
|
// ── Sub-Stage Detail Box ────────────────────────────────────────────
|
|
@@ -304,7 +366,7 @@ function renderWorkItem(item: WorkItemView, innerWidth: number, tick: number): s
|
|
|
304
366
|
lines.push(slugText + " ".repeat(gap1) + elapsedText);
|
|
305
367
|
|
|
306
368
|
// Line 2: branch + mode badge + gated badge + classification
|
|
307
|
-
const branchText = dim(item.branch);
|
|
369
|
+
const branchText = dim("⎇ " + item.branch);
|
|
308
370
|
let badges = " " + renderModeBadge(item.mode);
|
|
309
371
|
if (item.gated) badges += " " + renderGatedBadge();
|
|
310
372
|
if (item.status === "paused") badges += " " + bgYellow(" PAUSED ");
|
|
@@ -334,8 +396,11 @@ function renderWorkItem(item: WorkItemView, innerWidth: number, tick: number): s
|
|
|
334
396
|
tick
|
|
335
397
|
));
|
|
336
398
|
|
|
337
|
-
// Line 5: phase pipeline with connectors and
|
|
338
|
-
|
|
399
|
+
// Line 5-6: phase pipeline with connectors, spinner, and timing row
|
|
400
|
+
const pipelineLines = renderPhasePipeline(item.phases, tick);
|
|
401
|
+
for (const pl of pipelineLines) {
|
|
402
|
+
lines.push(" " + pl);
|
|
403
|
+
}
|
|
339
404
|
|
|
340
405
|
// Line 6: sub-stage detail box (all sub-stages of current phase)
|
|
341
406
|
const subStageBox = renderSubStageBox(item, innerWidth, tick);
|
|
@@ -361,11 +426,11 @@ function renderWorkItem(item: WorkItemView, innerWidth: number, tick: number): s
|
|
|
361
426
|
// Worktree path
|
|
362
427
|
if (item.worktreePath) {
|
|
363
428
|
let displayPath = item.worktreePath;
|
|
364
|
-
const maxPathLen = innerWidth -
|
|
429
|
+
const maxPathLen = innerWidth - 8;
|
|
365
430
|
if (displayPath.length > maxPathLen) {
|
|
366
431
|
displayPath = "…" + displayPath.slice(displayPath.length - maxPathLen + 1);
|
|
367
432
|
}
|
|
368
|
-
lines.push(" " + dim(displayPath));
|
|
433
|
+
lines.push(" " + dim("⌂ " + displayPath));
|
|
369
434
|
}
|
|
370
435
|
|
|
371
436
|
return lines;
|