tickmarkr 1.84.0 → 1.85.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.
@@ -53,33 +53,111 @@ function IdentityHeader({ data, columns, secondary = true, }) {
53
53
  {
54
54
  title: "",
55
55
  lines: [
56
- `${data.runId} · ${data.branch} ${data.status} · ${data.elapsed}`,
56
+ frameCaption(data),
57
57
  ],
58
58
  },
59
59
  ];
60
60
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(NarrowHeaderBand, { columns: columns, content: primary, rightStrong: true }), secondary && (_jsx(NarrowHeaderBand, { columns: columns, content: secondaryBand }))] }));
61
61
  }
62
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(BodyText, { emphasis: "strong", children: lockup[0] ?? "tickmarkr" }), _jsxs(BodyText, { emphasis: "strong", children: ["v", data.binaryVersion, " \u00B7 binary ", GLYPHS.pass] })] }), secondary && (_jsxs(Box, { justifyContent: "space-between", children: [_jsx(BodyText, { emphasis: "strong", children: lockup[1] ?? "tickmarkr" }), _jsxs(BodyText, { children: [data.runId, " \u00B7 ", data.branch, " ", data.status, " \u00B7 ", data.elapsed] })] }))] }));
62
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(BodyText, { emphasis: "strong", children: lockup[0] ?? "tickmarkr" }), _jsxs(BodyText, { emphasis: "strong", children: ["v", data.binaryVersion, " \u00B7 binary ", GLYPHS.pass] })] }), secondary && (_jsxs(Box, { justifyContent: "space-between", children: [_jsx(BodyText, { emphasis: "strong", children: lockup[1] ?? "tickmarkr" }), _jsx(BodyText, { children: frameCaption(data) })] }))] }));
63
63
  }
64
64
  function NarrowHeaderBand({ columns, content, rightStrong = false, }) {
65
65
  const widths = allocateBandColumns(columns, content, NARROW_HEADER_ALLOCATION);
66
66
  return (_jsxs(CockpitGrid, { columns: columns, columnContents: content, allocation: NARROW_HEADER_ALLOCATION, children: [_jsx(BandLines, { columns: Math.max(1, widths[0]), lines: content[0].lines, emphasis: "strong" }), _jsx(Box, { alignItems: "flex-end", flexDirection: "column", children: _jsx(BandLines, { columns: Math.max(1, widths[1]), lines: content[1].lines, emphasis: rightStrong ? "strong" : "normal" }) })] }));
67
67
  }
68
68
  const RUN_SIDE_RAIL_WIDTH = 16;
69
+ function runInstant(runId) {
70
+ const match = runId.match(/\brun-(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})/u);
71
+ return match === null
72
+ ? new Date()
73
+ : new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6]));
74
+ }
75
+ function utcClock(value) {
76
+ const match = value.match(/^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/u);
77
+ return match === null
78
+ ? undefined
79
+ : [Number(match[1]), Number(match[2]), Number(match[3])];
80
+ }
81
+ function localZoneLabel(data) {
82
+ const newestTimestamp = data.journalRows.find((row) => row.timestamp !== undefined)?.timestamp;
83
+ const recorded = newestTimestamp === undefined ? undefined : new Date(newestTimestamp);
84
+ const reference = recorded !== undefined && Number.isFinite(recorded.getTime())
85
+ ? recorded
86
+ : runInstant(data.runId);
87
+ const offset = -reference.getTimezoneOffset();
88
+ if (offset === 0)
89
+ return "UTC";
90
+ const sign = offset < 0 ? "-" : "+";
91
+ const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, "0");
92
+ const minutes = Math.abs(offset) % 60;
93
+ return minutes === 0
94
+ ? `${sign}${hours}`
95
+ : `${sign}${hours}:${String(minutes).padStart(2, "0")}`;
96
+ }
97
+ function localClockTime(value, timestamp) {
98
+ if (utcClock(value) === undefined)
99
+ return value;
100
+ if (timestamp === undefined)
101
+ return value;
102
+ const completeInstant = new Date(timestamp);
103
+ if (!Number.isFinite(completeInstant.getTime()))
104
+ return value;
105
+ const hours = String(completeInstant.getHours()).padStart(2, "0");
106
+ const minutes = String(completeInstant.getMinutes()).padStart(2, "0");
107
+ const seconds = String(completeInstant.getSeconds()).padStart(2, "0");
108
+ return `${hours}:${minutes}:${seconds}`;
109
+ }
110
+ /** Name the ruler an already-built caption's bare attempt number is counted on. */
111
+ function nameAttemptRuler(caption) {
112
+ return caption.replace(/(?<!engagement |run )\battempt (?=\d)/gu, "engagement attempt ");
113
+ }
114
+ /**
115
+ * The fold owns active-task identity and full event order. This surface only names the attempt
116
+ * ruler and fits that authoritative caption; TaskRows are never ranked into a competing answer.
117
+ */
118
+ function engagementProgressCaption(data, columns = Number.POSITIVE_INFINITY) {
119
+ const caption = nameAttemptRuler(data.progressCaption);
120
+ if (cellWidth(caption) <= columns)
121
+ return caption;
122
+ const actorSeparator = caption.lastIndexOf(" · ");
123
+ return actorSeparator < 0
124
+ ? caption
125
+ : `${caption.slice(0, actorSeparator)} ${caption.slice(actorSeparator + 3)}`;
126
+ }
127
+ function runAttemptLabel(attempts) {
128
+ return attempts === 1
129
+ ? "run attempt 1"
130
+ : `run attempts ${fieldReading(attempts)}`;
131
+ }
69
132
  /** The header caption: the run's identity line, planned as the header's refinable sub-region. */
70
133
  function frameCaption(data) {
71
- return `${data.runId} · ${data.branch} · ${data.status} · ${data.elapsed}`;
134
+ return `${data.runId} · ${data.branch} · ${data.status} · ${data.elapsed} · zone ${localZoneLabel(data)}`;
72
135
  }
73
136
  /** The header's fixed left part: brand, version and the two tab labels. */
74
- function headerLead(data, plan) {
137
+ function headerLead(data, plan, columns) {
75
138
  const tabs = plan.tab === "watch"
76
139
  ? `[ WATCH ]${FRAME_HEADER_GAP}${DECISIONS_TAB_TITLE}`
77
140
  : `WATCH${FRAME_HEADER_GAP}[ ${DECISIONS_TAB_TITLE} ]`;
78
- return `tickmarkr ${data.binaryVersion}${FRAME_HEADER_GAP}${tabs}`;
141
+ if (plan.regions.some((region) => region.id === "caption")) {
142
+ return `tickmarkr ${data.binaryVersion}${FRAME_HEADER_GAP}${tabs}`;
143
+ }
144
+ const withVersion = `tickmarkr ${data.binaryVersion}${FRAME_HEADER_GAP}${tabs} · zone ${localZoneLabel(data)}`;
145
+ const compactTabs = plan.tab === "watch"
146
+ ? `[WATCH]${DECISIONS_TAB_TITLE}`
147
+ : `WATCH[${DECISIONS_TAB_TITLE}]`;
148
+ const compact = `tickmarkr ${data.binaryVersion}${compactTabs} zone ${localZoneLabel(data)}`;
149
+ if (cellWidth(withVersion) <= columns)
150
+ return withVersion;
151
+ if (cellWidth(compact) <= columns)
152
+ return compact;
153
+ const markedTabs = plan.tab === "watch"
154
+ ? `>WATCH${DECISIONS_TAB_TITLE}`
155
+ : `WATCH>${DECISIONS_TAB_TITLE}`;
156
+ return `tickmarkr ${data.binaryVersion}${markedTabs} zone ${localZoneLabel(data)}`;
79
157
  }
80
158
  /** The one plain header line: brand, version and the two tab labels, the active one marked. */
81
159
  function PlannedHeader({ data, plan, columns, }) {
82
- return (_jsx(BodyText, { emphasis: "strong", children: fitCells(headerLead(data, plan), Math.max(1, columns)) }));
160
+ return (_jsx(BodyText, { emphasis: "strong", children: fitCells(headerLead(data, plan, columns), Math.max(1, columns)) }));
83
161
  }
84
162
  /** The header's caption sub-region: the run identity, right-aligned in it. */
85
163
  function PlannedCaption({ data, columns, }) {
@@ -117,7 +195,7 @@ function setupSectionLines(section, data) {
117
195
  const parked = data.taskRows.filter((row) => row.state === "human");
118
196
  if (parked.length === 0)
119
197
  return ["No parked decisions in this engagement"];
120
- return parked.map((row) => `${row.taskId} · parked · attempt ${row.attempts ?? 0}`);
198
+ return parked.map((row) => `${row.taskId} · parked · ${runAttemptLabel(row.attempts)}`);
121
199
  }
122
200
  /**
123
201
  * The plan's item rows, and the ref the frame registers their painted node
@@ -189,20 +267,26 @@ export function deriveRunViewRows(data, viewId, filterQuery = "") {
189
267
  * drew. The overview owns no list of its own, and the journal is a tail.
190
268
  */
191
269
  function promotedViewRows(data, viewId) {
270
+ // Surrendered by identity, never rebuilt: the journal view shows the fold's own records, and
271
+ // rewriting their text here — even to name the ruler its attempt is counted on — would be this
272
+ // surface authoring a row. The counters the surface DOES compose (the caption, the task rows)
273
+ // name their rulers below.
192
274
  if (viewId === "journal")
193
275
  return data.journalRows;
194
276
  if (viewId === "tasks") {
195
- return data.taskRows.map((row) => ({
277
+ return data.taskRows.filter(recordedTaskRow).map((row) => ({
196
278
  id: row.id,
197
279
  time: fieldReading(row.lastEventTime),
198
- state: taskRowState(row.state),
199
- text: `${row.taskId} · ${fieldReading(row.state)} · attempt ${fieldReading(row.attempts)} · ${fieldReading(row.actor)}${row.title === undefined ? "" : ` · ${row.title}`}`,
280
+ ...(row.lastEventTimestamp === undefined ? {} : { timestamp: row.lastEventTimestamp }),
281
+ state: taskRowState(row),
282
+ text: `${row.taskId} · ${fieldReading(row.state)} · ${runAttemptLabel(row.attempts)} · ${fieldReading(row.actor)}${row.title === undefined ? "" : ` · ${row.title}`}`,
200
283
  }));
201
284
  }
202
285
  if (viewId === "gates") {
203
286
  return data.gateRows.map((row) => ({
204
287
  id: row.id,
205
288
  time: row.time,
289
+ ...(row.timestamp === undefined ? {} : { timestamp: row.timestamp }),
206
290
  state: row.state,
207
291
  text: `${fieldReading(row.taskId)} · ${fieldReading(row.gate)} · ${row.pass === undefined ? fieldReading(undefined) : row.pass ? "pass" : "fail"}${row.details === undefined ? "" : ` · ${row.details}`}`,
208
292
  }));
@@ -211,19 +295,29 @@ function promotedViewRows(data, viewId) {
211
295
  return data.fleetRows.map((row) => ({
212
296
  id: row.id,
213
297
  time: fieldReading(row.lastEventTime),
298
+ ...(row.lastEventTimestamp === undefined ? {} : { timestamp: row.lastEventTimestamp }),
214
299
  state: "neutral",
215
300
  text: `${row.adapter} · ${row.model} · dispatches ${fieldReading(row.dispatches)}`,
216
301
  }));
217
302
  }
218
303
  return [];
219
304
  }
305
+ /** A task row exists on an operator surface only when the journal fold recorded a task fact. */
306
+ function recordedTaskRow(row) {
307
+ return row.state !== undefined
308
+ || row.lastEventTime !== undefined
309
+ || row.attempts !== undefined
310
+ || row.actor !== undefined
311
+ || row.merged === true
312
+ || row.parkKind !== undefined;
313
+ }
220
314
  /** A task's recorded state read as the component vocabulary the rows draw in. */
221
- function taskRowState(state) {
222
- if (state === "done")
315
+ function taskRowState(row) {
316
+ if (row.state === "done" && row.merged === true)
223
317
  return "pass";
224
- if (state === "failed")
318
+ if (row.state === "failed")
225
319
  return "fail";
226
- if (state === "running" || state === "human")
320
+ if (row.state === "running" || row.state === "human")
227
321
  return "active";
228
322
  return "neutral";
229
323
  }
@@ -270,7 +364,10 @@ function SizedJournalPanel({ rows, bodyRows, title, selection, focused, }) {
270
364
  : window.rows.findIndex((row) => row.id === hovered);
271
365
  const emptyRows = Math.max(0, bodyRows - window.rows.length);
272
366
  const journal = JournalRowPanel({
273
- rows: window.rows,
367
+ rows: window.rows.map((row) => ({
368
+ ...row,
369
+ time: localClockTime(row.time, row.timestamp),
370
+ })),
274
371
  title,
275
372
  selection: window.selection,
276
373
  focused,
@@ -294,7 +391,7 @@ function helpLines(entries) {
294
391
  }
295
392
  /** The lines the opened-event view draws for one journal row. */
296
393
  function eventLines(row) {
297
- return [`${row.time} · ${row.state}`, row.text];
394
+ return [`${localClockTime(row.time, row.timestamp)} · ${row.state}`, row.text];
298
395
  }
299
396
  /**
300
397
  * A view drawn from the rows its own derivation produces: its detail while the
@@ -419,15 +516,15 @@ function TailBand({ data, rows, columns, pendingWrites, }) {
419
516
  ? ["PENDING WRITES · none"]
420
517
  : deriveRunViewRows(data, "journal")
421
518
  .slice(-rows)
422
- .map((row) => `${row.time} ${row.text}`);
519
+ .map((row) => `${localClockTime(row.time, row.timestamp)} ${row.text}`);
423
520
  // A styled blank cell differs with colour on and off; these rows carry no dim style.
424
521
  return (_jsx(Box, { flexDirection: "column", height: rows, overflow: "hidden", children: Array.from({ length: rows }, (_, index) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsx(BodyText, { children: fitCells(lines[index] ?? "", columns) }) }, index))) }));
425
522
  }
426
- function ApprovedProgress({ data, caption, }) {
427
- return (_jsxs(Panel, { title: "PROGRESS", children: [_jsx(ProgressMeter, { value: data.progress, width: 28 }), caption && _jsx(BodyText, { emphasis: "dim", children: data.progressCaption })] }));
523
+ function ApprovedProgress({ data, caption, columns, }) {
524
+ return (_jsxs(Panel, { title: "PROGRESS", children: [_jsx(ProgressMeter, { value: data.progress, width: 28 }), caption && _jsx(BodyText, { emphasis: "dim", children: engagementProgressCaption(data, columns) })] }));
428
525
  }
429
- function CompactProgress({ data, caption, meterWidth = 28, }) {
430
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", children: [_jsx(BodyText, { emphasis: "strong", children: "PROGRESS " }), _jsx(ProgressMeter, { value: data.progress, width: meterWidth })] }), caption && _jsx(BodyText, { emphasis: "dim", children: data.progressCaption })] }));
526
+ function CompactProgress({ data, caption, meterWidth = 28, columns, }) {
527
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", children: [_jsx(BodyText, { emphasis: "strong", children: "PROGRESS " }), _jsx(ProgressMeter, { value: data.progress, width: meterWidth })] }), caption && _jsx(BodyText, { emphasis: "dim", children: engagementProgressCaption(data, columns) })] }));
431
528
  }
432
529
  /** The RUN view's interior, allocated out of the planned body span, each element granted whole or not at all. */
433
530
  function resolveRunViewContent(bodySpan, bodyColumns, data) {
@@ -450,8 +547,9 @@ function resolveRunViewContent(bodySpan, bodyColumns, data) {
450
547
  : 0;
451
548
  journal += tail;
452
549
  available -= tail;
550
+ const progressCaptionColumns = Math.max(1, bodyColumns - RUN_PANEL_HORIZONTAL_CHROME);
453
551
  const captionRows = progressBar
454
- ? wrapCells(data.progressCaption, Math.max(1, bodyColumns)).length
552
+ ? wrapCells(engagementProgressCaption(data, progressCaptionColumns), progressCaptionColumns).length
455
553
  : 0;
456
554
  const progressCaption = captionRows > 0 && available >= captionRows;
457
555
  if (progressCaption)
@@ -511,12 +609,12 @@ function LadderRunPanel({ data, content, compactMeterWidth, interaction, keyEntr
511
609
  return (_jsx(SizedLinesPanel, { title: "HELP", lines: helpLines(keyEntries), bodyRows: content.items.rows, wrapColumns: content.items.columns, focused: focused, flexGrow: 1 }));
512
610
  }
513
611
  return (_jsx(Panel, { title: "RUN", focused: focused, flexGrow: 1, children: _jsxs(ItemRows, { children: [_jsx(RunStats, { data: data, mode: content.stats }), content.progressBar && (content.approvedProgress
514
- ? _jsx(ApprovedProgress, { data: data, caption: content.progressCaption })
515
- : (_jsx(CompactProgress, { data: data, caption: content.progressCaption, meterWidth: compactMeterWidth }))), content.journal > 0 && (_jsx(JournalBody, { data: data, interaction: interaction, bodyRows: content.journal, keyEntries: keyEntries }))] }) }));
612
+ ? (_jsx(ApprovedProgress, { data: data, caption: content.progressCaption, columns: Math.max(1, content.items.columns - RUN_PANEL_HORIZONTAL_CHROME) }))
613
+ : (_jsx(CompactProgress, { data: data, caption: content.progressCaption, meterWidth: compactMeterWidth, columns: Math.max(1, content.items.columns - RUN_PANEL_HORIZONTAL_CHROME) }))), content.journal > 0 && (_jsx(JournalBody, { data: data, interaction: interaction, bodyRows: content.journal, keyEntries: keyEntries }))] }) }));
516
614
  }
517
615
  function LegacyRunPanel({ data, interaction, keyEntries, focused = true, }) {
518
616
  const rows = deriveRunViewRows(data, "journal");
519
- return (_jsxs(Panel, { title: "RUN", focused: focused, flexGrow: 1, children: [_jsx(RunStats, { data: data, mode: "tiles" }), _jsxs(Panel, { title: "PROGRESS", children: [_jsx(ProgressMeter, { value: data.progress, width: 28 }), _jsx(BodyText, { emphasis: "dim", children: data.progressCaption })] }), _jsx(JournalBody, { data: data, interaction: interaction, bodyRows: rows.length, keyEntries: keyEntries })] }));
617
+ return (_jsxs(Panel, { title: "RUN", focused: focused, flexGrow: 1, children: [_jsx(RunStats, { data: data, mode: "tiles" }), _jsxs(Panel, { title: "PROGRESS", children: [_jsx(ProgressMeter, { value: data.progress, width: 28 }), _jsx(BodyText, { emphasis: "dim", children: engagementProgressCaption(data) })] }), _jsx(JournalBody, { data: data, interaction: interaction, bodyRows: rows.length, keyEntries: keyEntries })] }));
520
618
  }
521
619
  /**
522
620
  * One planned band, drawn at exactly the span the plan gives it. Unsized (the
@@ -528,8 +626,11 @@ function FrameBand({ rows, row = false, gap, regionRef, children, }) {
528
626
  /** A sized sub-floor plan draws the plain fallback, clipped to the measured size. */
529
627
  function PlainFallbackPaint({ data, size, }) {
530
628
  const columns = Math.max(1, size.columns);
629
+ // This is an operator surface, so it names its zone once like every other one.
630
+ // The run.ci/run.non-tty machine anchors are drawn by capture.ts's
631
+ // defaultPlainRenderer, not by this component, so the label does not move them.
531
632
  const lines = [
532
- `tickmarkr ${data.binaryVersion}`,
633
+ `tickmarkr ${data.binaryVersion} · zone ${localZoneLabel(data)}`,
533
634
  `${data.runId} ${data.status} ${data.elapsed}`,
534
635
  ...data.statusItems.map((item) => item.text),
535
636
  ];
@@ -588,7 +689,7 @@ function RunCockpitPaint({ data, columns, plan, content, interaction, keyProject
588
689
  : (_jsx(RailBand, { data: data, focused: railFocused, selection: railSelection, columns: rail.columns, rows: rail.rows, sidebar: plan.sidebar })) })), _jsx(Box, { ref: regionRef("body"), width: body.columns, height: body.rows, flexShrink: 0, flexDirection: "column", overflow: "hidden", children: _jsx(ItemRowsBand.Provider, { value: { region: content.items, regionRef: regionRef("items") }, children: setup
589
690
  ? (_jsx(SetupTabPanel, { data: data, selection: railSelection, focused: contentFocused, keyEntries: keyEntries, help: currentInteraction.help, bodyRows: content.items.rows, bodyColumns: content.items.columns }))
590
691
  : activeView === "run"
591
- ? (_jsx(LadderRunPanel, { data: data, content: content, compactMeterWidth: content.sideRails ? undefined : 24, interaction: currentInteraction, keyEntries: keyEntries, focused: contentFocused }))
692
+ ? (_jsx(LadderRunPanel, { data: data, content: content, compactMeterWidth: 24, interaction: currentInteraction, keyEntries: keyEntries, focused: contentFocused }))
592
693
  : (_jsx(RunViewPanel, { data: data, viewId: activeView, interaction: currentInteraction, focused: contentFocused, keyEntries: keyEntries, bodyRows: content.items.rows, bodyColumns: content.items.columns })) }) })] }), _jsx(FrameBand, { rows: requiredRegion(plan, "rule2").rows, regionRef: regionRef("rule2"), children: _jsx(RuleBand, { columns: plan.size.columns }) }), tail !== undefined && (_jsx(FrameBand, { rows: tail.rows, regionRef: regionRef("tail"), children: _jsx(TailBand, { data: data, rows: tail.rows, columns: tail.columns, pendingWrites: plan.content.tail === "pending-writes" }) })), _jsx(FrameBand, { rows: requiredRegion(plan, "status").rows, regionRef: regionRef("status"), children: _jsx(StatusStrip, { items: reportedStatusItems, width: plan.size.columns }) }), _jsx(FrameBand, { rows: requiredRegion(plan, "keybar").rows, regionRef: regionRef("keybar"), children: _jsx(RunKeybar, { entries: keyEntries, width: plan.size.columns, help: currentInteraction.help }) })] }));
593
694
  }
594
695
  /** Resolve every draw-time input; the plan is planFrame's own return, consumed unmodified. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "1.84.0",
3
+ "version": "1.85.0",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",