tickmarkr 1.83.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.
- package/dist/adapters/claude-code.d.ts +1 -0
- package/dist/adapters/claude-code.js +57 -1
- package/dist/adapters/fake.js +9 -0
- package/dist/adapters/types.d.ts +3 -0
- package/dist/adapters/types.js +21 -0
- package/dist/cli/commands/status.js +160 -30
- package/dist/compile/collateral.d.ts +86 -2
- package/dist/compile/collateral.js +294 -3
- package/dist/config/config.d.ts +62 -0
- package/dist/config/config.js +157 -2
- package/dist/drivers/herdr.d.ts +20 -3
- package/dist/drivers/herdr.js +288 -105
- package/dist/gates/baseline.d.ts +1 -0
- package/dist/gates/baseline.js +91 -13
- package/dist/gates/review.d.ts +7 -0
- package/dist/gates/review.js +99 -6
- package/dist/gates/run-gates.d.ts +9 -0
- package/dist/gates/run-gates.js +285 -41
- package/dist/run/daemon.d.ts +48 -2
- package/dist/run/daemon.js +1417 -315
- package/dist/run/journal.d.ts +56 -3
- package/dist/run/journal.js +275 -1
- package/dist/run/stall.d.ts +35 -1
- package/dist/run/stall.js +118 -8
- package/dist/tui/cockpit/components.d.ts +30 -1
- package/dist/tui/cockpit/components.js +19 -3
- package/dist/tui/cockpit/derive.d.ts +29 -2
- package/dist/tui/cockpit/derive.js +219 -23
- package/dist/tui/cockpit/layout.d.ts +75 -6
- package/dist/tui/cockpit/layout.js +97 -19
- package/dist/tui/cockpit/live.d.ts +14 -1
- package/dist/tui/cockpit/live.js +223 -29
- package/dist/tui/cockpit/pointer.d.ts +261 -0
- package/dist/tui/cockpit/pointer.js +610 -0
- package/dist/tui/cockpit/run-cockpit.d.ts +36 -5
- package/dist/tui/cockpit/run-cockpit.js +270 -51
- package/package.json +1 -1
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, useApp, useStdout } from "ink";
|
|
3
|
-
import { cloneElement, useInsertionEffect, useMemo, useRef, } from "react";
|
|
3
|
+
import { cloneElement, createContext, useContext, useEffect, useInsertionEffect, useMemo, useRef, useSyncExternalStore, } from "react";
|
|
4
4
|
import { GLYPHS, PLAIN_COMPACT_LOCKUP, } from "../../brand.js";
|
|
5
5
|
import { allocateBandColumns, BandLines, BodyText, CockpitGrid, composeBandLine, JournalRowPanel, keyRosterLines, Panel, PANEL_CHROME_ROWS, ProgressMeter, Sparkline, StatTile, StatusStrip, } from "./components.js";
|
|
6
6
|
import { fieldReading } from "./derive.js";
|
|
7
7
|
import { initialRunInteractionState, projectRunKeyEntries, reconcileRunInteraction, RUN_SIDE_RAIL_COLUMN_FLOOR, runPanelFocusOrder, runSideRailVisible, selectableRunViewRowIds, } from "./keys.js";
|
|
8
8
|
import { DECISIONS_TAB_TITLE, RUN_VIEWS, runViewById, setupSectionAt, SETUP_SECTIONS, } from "./views.js";
|
|
9
|
-
import { FRAME_HEADER_GAP, FULL_JOURNAL_ROWS, planFrame, SIDEBAR_COLUMN_FLOOR, } from "./layout.js";
|
|
9
|
+
import { FRAME_HEADER_GAP, FRAME_NESTED_ROW_BANDS, FULL_JOURNAL_ROWS, planFrame, RUN_PANEL_HORIZONTAL_CHROME, SIDEBAR_COLUMN_FLOOR, } from "./layout.js";
|
|
10
|
+
import { pointerHoverRow, pointerRestingCell, sessionRailOverride, watchPointerRest, watchSessionRailOverride, } from "./pointer.js";
|
|
10
11
|
import { cellWidth, fitCells, wrapCells } from "./width.js";
|
|
11
12
|
export { deriveRunCockpitData } from "./derive.js";
|
|
12
13
|
export { PANEL_CHROME_ROWS } from "./components.js";
|
|
13
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The width focus and key routing resolve at, from a measured width — the form
|
|
16
|
+
* used before a plan exists. Once one does, `plannedKeyColumns` (pointer.ts)
|
|
17
|
+
* reads the same answer off the bands the plan actually drew.
|
|
18
|
+
*/
|
|
14
19
|
export function runKeyColumns(columns) {
|
|
15
20
|
return columns >= SIDEBAR_COLUMN_FLOOR
|
|
16
21
|
? Math.max(columns, RUN_SIDE_RAIL_COLUMN_FLOOR)
|
|
@@ -48,35 +53,111 @@ function IdentityHeader({ data, columns, secondary = true, }) {
|
|
|
48
53
|
{
|
|
49
54
|
title: "",
|
|
50
55
|
lines: [
|
|
51
|
-
|
|
56
|
+
frameCaption(data),
|
|
52
57
|
],
|
|
53
58
|
},
|
|
54
59
|
];
|
|
55
60
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(NarrowHeaderBand, { columns: columns, content: primary, rightStrong: true }), secondary && (_jsx(NarrowHeaderBand, { columns: columns, content: secondaryBand }))] }));
|
|
56
61
|
}
|
|
57
|
-
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" }),
|
|
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) })] }))] }));
|
|
58
63
|
}
|
|
59
64
|
function NarrowHeaderBand({ columns, content, rightStrong = false, }) {
|
|
60
65
|
const widths = allocateBandColumns(columns, content, NARROW_HEADER_ALLOCATION);
|
|
61
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" }) })] }));
|
|
62
67
|
}
|
|
63
68
|
const RUN_SIDE_RAIL_WIDTH = 16;
|
|
64
|
-
|
|
65
|
-
const
|
|
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
|
+
}
|
|
66
132
|
/** The header caption: the run's identity line, planned as the header's refinable sub-region. */
|
|
67
133
|
function frameCaption(data) {
|
|
68
|
-
return `${data.runId} · ${data.branch} · ${data.status} · ${data.elapsed}`;
|
|
134
|
+
return `${data.runId} · ${data.branch} · ${data.status} · ${data.elapsed} · zone ${localZoneLabel(data)}`;
|
|
69
135
|
}
|
|
70
136
|
/** The header's fixed left part: brand, version and the two tab labels. */
|
|
71
|
-
function headerLead(data, plan) {
|
|
137
|
+
function headerLead(data, plan, columns) {
|
|
72
138
|
const tabs = plan.tab === "watch"
|
|
73
139
|
? `[ WATCH ]${FRAME_HEADER_GAP}${DECISIONS_TAB_TITLE}`
|
|
74
140
|
: `WATCH${FRAME_HEADER_GAP}[ ${DECISIONS_TAB_TITLE} ]`;
|
|
75
|
-
|
|
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)}`;
|
|
76
157
|
}
|
|
77
158
|
/** The one plain header line: brand, version and the two tab labels, the active one marked. */
|
|
78
159
|
function PlannedHeader({ data, plan, columns, }) {
|
|
79
|
-
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)) }));
|
|
80
161
|
}
|
|
81
162
|
/** The header's caption sub-region: the run identity, right-aligned in it. */
|
|
82
163
|
function PlannedCaption({ data, columns, }) {
|
|
@@ -114,7 +195,37 @@ function setupSectionLines(section, data) {
|
|
|
114
195
|
const parked = data.taskRows.filter((row) => row.state === "human");
|
|
115
196
|
if (parked.length === 0)
|
|
116
197
|
return ["No parked decisions in this engagement"];
|
|
117
|
-
return parked.map((row) => `${row.taskId} · parked ·
|
|
198
|
+
return parked.map((row) => `${row.taskId} · parked · ${runAttemptLabel(row.attempts)}`);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The plan's item rows, and the ref the frame registers their painted node
|
|
202
|
+
* through. Every body panel draws its list into this one box, so the rectangle
|
|
203
|
+
* the conformance oracle measures off the paint is the rectangle planFrame
|
|
204
|
+
* planned — which is exactly the pin a field standing beside the plan could
|
|
205
|
+
* never carry. Absent on the unsized legacy surface, which has no plan.
|
|
206
|
+
*/
|
|
207
|
+
const ItemRowsBand = createContext(undefined);
|
|
208
|
+
/**
|
|
209
|
+
* The identity of the row a live pointer is resting on, or none. It is drawn
|
|
210
|
+
* state and only drawn state: nothing reads it back, no interaction state
|
|
211
|
+
* carries it, and the one provider is fed by a `hover` prop the frame's own
|
|
212
|
+
* callers supply. A capture supplies none — `capture.ts` builds this frame from
|
|
213
|
+
* data, size and interaction alone — so every golden, anchor and colour capture
|
|
214
|
+
* records the pointerless appearance by construction rather than by a guard
|
|
215
|
+
* stripping a highlight back out afterwards.
|
|
216
|
+
*/
|
|
217
|
+
const HoveredRow = createContext(null);
|
|
218
|
+
/**
|
|
219
|
+
* A body panel's list, drawn into the plan's own item rows and nowhere else.
|
|
220
|
+
* The band is consumed by the outermost panel that asks for it — a panel nested
|
|
221
|
+
* inside that one (the RUN view's own journal) is part of the list, not a second
|
|
222
|
+
* claim on the region, so the band is cleared for everything below.
|
|
223
|
+
*/
|
|
224
|
+
function ItemRows({ children }) {
|
|
225
|
+
const band = useContext(ItemRowsBand);
|
|
226
|
+
if (band === undefined)
|
|
227
|
+
return _jsx(_Fragment, { children: children });
|
|
228
|
+
return (_jsx(Box, { ref: band.regionRef, flexDirection: "column", height: band.region.rows, width: band.region.columns, flexShrink: 0, children: _jsx(ItemRowsBand.Provider, { value: undefined, children: children }) }));
|
|
118
229
|
}
|
|
119
230
|
/**
|
|
120
231
|
* The setup tab's body, drawn inside the same row budget the watch tab's views
|
|
@@ -156,20 +267,26 @@ export function deriveRunViewRows(data, viewId, filterQuery = "") {
|
|
|
156
267
|
* drew. The overview owns no list of its own, and the journal is a tail.
|
|
157
268
|
*/
|
|
158
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.
|
|
159
274
|
if (viewId === "journal")
|
|
160
275
|
return data.journalRows;
|
|
161
276
|
if (viewId === "tasks") {
|
|
162
|
-
return data.taskRows.map((row) => ({
|
|
277
|
+
return data.taskRows.filter(recordedTaskRow).map((row) => ({
|
|
163
278
|
id: row.id,
|
|
164
279
|
time: fieldReading(row.lastEventTime),
|
|
165
|
-
|
|
166
|
-
|
|
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}`}`,
|
|
167
283
|
}));
|
|
168
284
|
}
|
|
169
285
|
if (viewId === "gates") {
|
|
170
286
|
return data.gateRows.map((row) => ({
|
|
171
287
|
id: row.id,
|
|
172
288
|
time: row.time,
|
|
289
|
+
...(row.timestamp === undefined ? {} : { timestamp: row.timestamp }),
|
|
173
290
|
state: row.state,
|
|
174
291
|
text: `${fieldReading(row.taskId)} · ${fieldReading(row.gate)} · ${row.pass === undefined ? fieldReading(undefined) : row.pass ? "pass" : "fail"}${row.details === undefined ? "" : ` · ${row.details}`}`,
|
|
175
292
|
}));
|
|
@@ -178,19 +295,29 @@ function promotedViewRows(data, viewId) {
|
|
|
178
295
|
return data.fleetRows.map((row) => ({
|
|
179
296
|
id: row.id,
|
|
180
297
|
time: fieldReading(row.lastEventTime),
|
|
298
|
+
...(row.lastEventTimestamp === undefined ? {} : { timestamp: row.lastEventTimestamp }),
|
|
181
299
|
state: "neutral",
|
|
182
300
|
text: `${row.adapter} · ${row.model} · dispatches ${fieldReading(row.dispatches)}`,
|
|
183
301
|
}));
|
|
184
302
|
}
|
|
185
303
|
return [];
|
|
186
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
|
+
}
|
|
187
314
|
/** A task's recorded state read as the component vocabulary the rows draw in. */
|
|
188
|
-
function taskRowState(
|
|
189
|
-
if (state === "done")
|
|
315
|
+
function taskRowState(row) {
|
|
316
|
+
if (row.state === "done" && row.merged === true)
|
|
190
317
|
return "pass";
|
|
191
|
-
if (state === "failed")
|
|
318
|
+
if (row.state === "failed")
|
|
192
319
|
return "fail";
|
|
193
|
-
if (state === "running" || state === "human")
|
|
320
|
+
if (row.state === "running" || row.state === "human")
|
|
194
321
|
return "active";
|
|
195
322
|
return "neutral";
|
|
196
323
|
}
|
|
@@ -207,17 +334,47 @@ function windowedRows(rows, bodyRows, selection) {
|
|
|
207
334
|
...(selectedIndex < 0 ? {} : { selection: selectedIndex - offset }),
|
|
208
335
|
};
|
|
209
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* The identities the body's rows carry in the order the body draws them — the
|
|
339
|
+
* same window `SizedJournalPanel` paints, so a hit on a body row resolves to
|
|
340
|
+
* the row an operator is looking at rather than to a position in a collection
|
|
341
|
+
* the frame scrolled past. A body that draws no selectable list (the overview,
|
|
342
|
+
* the journal tail, an open detail, the help overlay, the decisions tab) owns
|
|
343
|
+
* no rows here.
|
|
344
|
+
*/
|
|
345
|
+
export function drawnRunViewRowIds(data, interaction, bodyRows) {
|
|
346
|
+
if (interaction.tab === "setup"
|
|
347
|
+
|| interaction.activeView === "run"
|
|
348
|
+
|| interaction.opened !== null
|
|
349
|
+
|| interaction.help)
|
|
350
|
+
return [];
|
|
351
|
+
const rows = deriveRunViewRows(data, interaction.activeView, interaction.filterQuery);
|
|
352
|
+
return selectableRunViewRowIds(interaction.activeView, windowedRows(rows, bodyRows, interaction.selection).rows.map((row) => row.id));
|
|
353
|
+
}
|
|
210
354
|
function SizedJournalPanel({ rows, bodyRows, title, selection, focused, }) {
|
|
211
355
|
const window = windowedRows(rows, bodyRows, selection);
|
|
356
|
+
// The hovered row is carried as its identity, never as a screen row: it was
|
|
357
|
+
// resolved through the plan once, and this panel finds it in the very window
|
|
358
|
+
// it is about to paint or does not draw it at all. A list this pointer cell
|
|
359
|
+
// acts on nothing in — the passive overview tail, the journal's unselectable
|
|
360
|
+
// rows — finds nothing here, so no row is decorated for being passed over.
|
|
361
|
+
const hovered = useContext(HoveredRow);
|
|
362
|
+
const hover = hovered === null
|
|
363
|
+
? -1
|
|
364
|
+
: window.rows.findIndex((row) => row.id === hovered);
|
|
212
365
|
const emptyRows = Math.max(0, bodyRows - window.rows.length);
|
|
213
366
|
const journal = JournalRowPanel({
|
|
214
|
-
rows: window.rows
|
|
367
|
+
rows: window.rows.map((row) => ({
|
|
368
|
+
...row,
|
|
369
|
+
time: localClockTime(row.time, row.timestamp),
|
|
370
|
+
})),
|
|
215
371
|
title,
|
|
216
372
|
selection: window.selection,
|
|
217
373
|
focused,
|
|
374
|
+
...(hover < 0 ? {} : { hover }),
|
|
218
375
|
});
|
|
219
376
|
const journalProps = journal.props;
|
|
220
|
-
return cloneElement(journal, {}, journalProps.children, emptyRows > 0 ? _jsx(Box, { height: emptyRows }, "journal-space") : null);
|
|
377
|
+
return cloneElement(journal, {}, _jsxs(ItemRows, { children: [journalProps.children, emptyRows > 0 ? _jsx(Box, { height: emptyRows }, "journal-space") : null] }, "items"));
|
|
221
378
|
}
|
|
222
379
|
/** A body panel (help overlay, opened event) padded to the journal's rows. */
|
|
223
380
|
function SizedLinesPanel({ title, lines, bodyRows, wrapColumns, focused, flexGrow, }) {
|
|
@@ -226,7 +383,7 @@ function SizedLinesPanel({ title, lines, bodyRows, wrapColumns, focused, flexGro
|
|
|
226
383
|
: lines.flatMap((line) => composeBandLine(line, wrapColumns));
|
|
227
384
|
const visible = drawnLines.slice(0, bodyRows);
|
|
228
385
|
const emptyRows = Math.max(0, bodyRows - visible.length);
|
|
229
|
-
return (
|
|
386
|
+
return (_jsx(Panel, { title: title, focused: focused, flexGrow: flexGrow, children: _jsxs(ItemRows, { children: [visible.map((line, index) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsx(BodyText, { children: line }) }, `${index}:${line}`))), emptyRows > 0 && _jsx(Box, { height: emptyRows })] }) }));
|
|
230
387
|
}
|
|
231
388
|
/** The lines the help overlay draws: every advertised key and its promise. */
|
|
232
389
|
function helpLines(entries) {
|
|
@@ -234,7 +391,7 @@ function helpLines(entries) {
|
|
|
234
391
|
}
|
|
235
392
|
/** The lines the opened-event view draws for one journal row. */
|
|
236
393
|
function eventLines(row) {
|
|
237
|
-
return [`${row.time} · ${row.state}`, row.text];
|
|
394
|
+
return [`${localClockTime(row.time, row.timestamp)} · ${row.state}`, row.text];
|
|
238
395
|
}
|
|
239
396
|
/**
|
|
240
397
|
* A view drawn from the rows its own derivation produces: its detail while the
|
|
@@ -246,7 +403,7 @@ function RunViewPanel({ data, viewId, interaction, focused, keyEntries, bodyRows
|
|
|
246
403
|
const view = runViewById(viewId);
|
|
247
404
|
const rows = deriveRunViewRows(data, viewId, interaction.filterQuery);
|
|
248
405
|
if (interaction.help) {
|
|
249
|
-
return (_jsx(Panel, { title: "HELP", focused: focused, flexGrow: 1, children: helpLines(keyEntries).map((line) => _jsx(BodyText, { children: line }, line)) }));
|
|
406
|
+
return (_jsx(Panel, { title: "HELP", focused: focused, flexGrow: 1, children: _jsx(ItemRows, { children: helpLines(keyEntries).map((line) => _jsx(BodyText, { children: line }, line)) }) }));
|
|
250
407
|
}
|
|
251
408
|
const opened = interaction.opened === null
|
|
252
409
|
? undefined
|
|
@@ -255,7 +412,7 @@ function RunViewPanel({ data, viewId, interaction, focused, keyEntries, bodyRows
|
|
|
255
412
|
return (_jsx(SizedLinesPanel, { title: view.detailTitle, lines: eventLines(opened), bodyRows: bodyRows, wrapColumns: bodyColumns, focused: focused, flexGrow: 1 }));
|
|
256
413
|
}
|
|
257
414
|
if (rows.length === 0) {
|
|
258
|
-
return (_jsx(Panel, { title: view.title, focused: focused, flexGrow: 1, children: _jsxs(BodyText, { emphasis: "dim", children: ["No ", view.id, " in this engagement"] }) }));
|
|
415
|
+
return (_jsx(Panel, { title: view.title, focused: focused, flexGrow: 1, children: _jsx(ItemRows, { children: _jsxs(BodyText, { emphasis: "dim", children: ["No ", view.id, " in this engagement"] }) }) }));
|
|
259
416
|
}
|
|
260
417
|
return (_jsx(SizedJournalPanel, { rows: rows, bodyRows: bodyRows, title: interaction.filterPrompt
|
|
261
418
|
? `${view.title} /${interaction.filterQuery}`
|
|
@@ -359,15 +516,15 @@ function TailBand({ data, rows, columns, pendingWrites, }) {
|
|
|
359
516
|
? ["PENDING WRITES · none"]
|
|
360
517
|
: deriveRunViewRows(data, "journal")
|
|
361
518
|
.slice(-rows)
|
|
362
|
-
.map((row) => `${row.time} ${row.text}`);
|
|
519
|
+
.map((row) => `${localClockTime(row.time, row.timestamp)} ${row.text}`);
|
|
363
520
|
// A styled blank cell differs with colour on and off; these rows carry no dim style.
|
|
364
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))) }));
|
|
365
522
|
}
|
|
366
|
-
function ApprovedProgress({ data, caption, }) {
|
|
367
|
-
return (_jsxs(Panel, { title: "PROGRESS", children: [_jsx(ProgressMeter, { value: data.progress, width: 28 }), caption && _jsx(BodyText, { emphasis: "dim", children: data
|
|
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) })] }));
|
|
368
525
|
}
|
|
369
|
-
function CompactProgress({ data, caption, meterWidth = 28, }) {
|
|
370
|
-
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
|
|
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) })] }));
|
|
371
528
|
}
|
|
372
529
|
/** The RUN view's interior, allocated out of the planned body span, each element granted whole or not at all. */
|
|
373
530
|
function resolveRunViewContent(bodySpan, bodyColumns, data) {
|
|
@@ -390,8 +547,9 @@ function resolveRunViewContent(bodySpan, bodyColumns, data) {
|
|
|
390
547
|
: 0;
|
|
391
548
|
journal += tail;
|
|
392
549
|
available -= tail;
|
|
550
|
+
const progressCaptionColumns = Math.max(1, bodyColumns - RUN_PANEL_HORIZONTAL_CHROME);
|
|
393
551
|
const captionRows = progressBar
|
|
394
|
-
? wrapCells(data
|
|
552
|
+
? wrapCells(engagementProgressCaption(data, progressCaptionColumns), progressCaptionColumns).length
|
|
395
553
|
: 0;
|
|
396
554
|
const progressCaption = captionRows > 0 && available >= captionRows;
|
|
397
555
|
if (progressCaption)
|
|
@@ -414,12 +572,13 @@ function resolveRunViewContent(bodySpan, bodyColumns, data) {
|
|
|
414
572
|
}
|
|
415
573
|
/** What fills the regions this paint draws, resolved from the plan's own spans. */
|
|
416
574
|
function resolveRegionContent(plan, data, interaction) {
|
|
417
|
-
const
|
|
418
|
-
const bodySpan = bodyRegion.rows;
|
|
575
|
+
const bodySpan = requiredRegion(plan, "body").rows;
|
|
419
576
|
const sideRails = plan.band === "sidebar";
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
577
|
+
// The plan's own item rows, consumed unmodified: the paint draws its list at
|
|
578
|
+
// this region and the pointer resolves a hit through it — one geometry.
|
|
579
|
+
const items = requiredRegion(plan, "items");
|
|
580
|
+
const bodyColumns = items.columns;
|
|
581
|
+
const common = { items, sideRails };
|
|
423
582
|
if (interaction.tab !== "setup" && interaction.activeView === "run") {
|
|
424
583
|
return {
|
|
425
584
|
...common,
|
|
@@ -447,15 +606,15 @@ function resolveRegionContent(plan, data, interaction) {
|
|
|
447
606
|
/** The RUN view drawn at its planned spans: every row budget arrives resolved. */
|
|
448
607
|
function LadderRunPanel({ data, content, compactMeterWidth, interaction, keyEntries, focused = true, }) {
|
|
449
608
|
if (interaction?.help === true) {
|
|
450
|
-
return (_jsx(SizedLinesPanel, { title: "HELP", lines: helpLines(keyEntries), bodyRows: content.
|
|
609
|
+
return (_jsx(SizedLinesPanel, { title: "HELP", lines: helpLines(keyEntries), bodyRows: content.items.rows, wrapColumns: content.items.columns, focused: focused, flexGrow: 1 }));
|
|
451
610
|
}
|
|
452
|
-
return (
|
|
453
|
-
|
|
454
|
-
|
|
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
|
|
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 }))] }) }));
|
|
455
614
|
}
|
|
456
615
|
function LegacyRunPanel({ data, interaction, keyEntries, focused = true, }) {
|
|
457
616
|
const rows = deriveRunViewRows(data, "journal");
|
|
458
|
-
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
|
|
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 })] }));
|
|
459
618
|
}
|
|
460
619
|
/**
|
|
461
620
|
* One planned band, drawn at exactly the span the plan gives it. Unsized (the
|
|
@@ -467,8 +626,11 @@ function FrameBand({ rows, row = false, gap, regionRef, children, }) {
|
|
|
467
626
|
/** A sized sub-floor plan draws the plain fallback, clipped to the measured size. */
|
|
468
627
|
function PlainFallbackPaint({ data, size, }) {
|
|
469
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.
|
|
470
632
|
const lines = [
|
|
471
|
-
`tickmarkr ${data.binaryVersion}`,
|
|
633
|
+
`tickmarkr ${data.binaryVersion} · zone ${localZoneLabel(data)}`,
|
|
472
634
|
`${data.runId} ${data.status} ${data.elapsed}`,
|
|
473
635
|
...data.statusItems.map((item) => item.text),
|
|
474
636
|
];
|
|
@@ -524,14 +686,14 @@ function RunCockpitPaint({ data, columns, plan, content, interaction, keyProject
|
|
|
524
686
|
const setup = currentInteraction.tab === "setup";
|
|
525
687
|
return (_jsxs(Box, { flexDirection: "column", width: plan.size.columns, height: plan.size.rows, overflow: "hidden", children: [_jsxs(FrameBand, { rows: header.rows, row: true, regionRef: regionRef("header"), children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, height: header.rows, overflow: "hidden", children: _jsx(PlannedHeader, { data: data, plan: plan, columns: plan.size.columns - (caption?.columns ?? 0) }) }), caption !== undefined && (_jsx(Box, { ref: regionRef("caption"), width: caption.columns, height: caption.rows, flexShrink: 0, overflow: "hidden", children: _jsx(PlannedCaption, { data: data, columns: caption.columns }) }))] }), _jsx(FrameBand, { rows: requiredRegion(plan, "rule").rows, regionRef: regionRef("rule"), children: _jsx(RuleBand, { columns: plan.size.columns }) }), strip !== undefined && (_jsx(FrameBand, { rows: strip.rows, regionRef: regionRef("strip"), children: _jsx(ViewStrip, { selection: railSelection, columns: strip.columns }) })), _jsxs(FrameBand, { rows: body.rows, row: true, children: [rail !== undefined && (_jsx(Box, { ref: regionRef("rail"), width: rail.columns, height: rail.rows, flexShrink: 0, overflow: "hidden", children: setup
|
|
526
688
|
? (_jsx(SectionsBand, { focused: railFocused, selection: railSelection, columns: rail.columns }))
|
|
527
|
-
: (_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: setup
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
|
690
|
+
? (_jsx(SetupTabPanel, { data: data, selection: railSelection, focused: contentFocused, keyEntries: keyEntries, help: currentInteraction.help, bodyRows: content.items.rows, bodyColumns: content.items.columns }))
|
|
691
|
+
: activeView === "run"
|
|
692
|
+
? (_jsx(LadderRunPanel, { data: data, content: content, compactMeterWidth: 24, interaction: currentInteraction, keyEntries: keyEntries, focused: contentFocused }))
|
|
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 }) })] }));
|
|
532
694
|
}
|
|
533
695
|
/** Resolve every draw-time input; the plan is planFrame's own return, consumed unmodified. */
|
|
534
|
-
export function planRunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, }) {
|
|
696
|
+
export function planRunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, railColumns, }) {
|
|
535
697
|
const suppliedInteraction = interaction ?? initialRunInteractionState();
|
|
536
698
|
const suppliedRows = deriveRunViewRows(data, suppliedInteraction.activeView, suppliedInteraction.filterQuery);
|
|
537
699
|
const rowIds = suppliedRows.map((row) => row.id);
|
|
@@ -547,6 +709,7 @@ export function planRunCockpitFrame({ data, columns, rows, interaction, keyProje
|
|
|
547
709
|
tab: repaired.tab,
|
|
548
710
|
detail: repaired.opened !== null,
|
|
549
711
|
captionCells: cellWidth(frameCaption(data)),
|
|
712
|
+
...(railColumns === undefined ? {} : { railColumns }),
|
|
550
713
|
};
|
|
551
714
|
const plan = planFrame(size, view, state);
|
|
552
715
|
const content = rows === undefined || plan.kind !== "frame"
|
|
@@ -565,15 +728,56 @@ export function planRunCockpitFrame({ data, columns, rows, interaction, keyProje
|
|
|
565
728
|
};
|
|
566
729
|
}
|
|
567
730
|
/** The public surface always enters paint through one measured plan. */
|
|
568
|
-
export function RunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, }) {
|
|
731
|
+
export function RunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, onCommittedFrame, }) {
|
|
732
|
+
// The session's panel resize override, read from the pointer layer the live
|
|
733
|
+
// drag feeds and redrawn when the boundary moves. It enters the frame only
|
|
734
|
+
// as an input to this planning call — the drawn frame is the recomputed
|
|
735
|
+
// plan, so a resize can never paint beside the plan. Nothing persists it: a
|
|
736
|
+
// relaunch plans the default layout.
|
|
737
|
+
const railOverride = useSyncExternalStore(watchSessionRailOverride, sessionRailOverride, sessionRailOverride);
|
|
569
738
|
const plannedFrame = useMemo(() => planRunCockpitFrame({
|
|
570
739
|
data,
|
|
571
740
|
columns,
|
|
572
741
|
rows,
|
|
573
742
|
interaction,
|
|
574
743
|
keyProjection,
|
|
575
|
-
|
|
576
|
-
|
|
744
|
+
railColumns: railOverride ?? undefined,
|
|
745
|
+
}), [data, columns, rows, interaction, keyProjection, railOverride]);
|
|
746
|
+
useEffect(() => {
|
|
747
|
+
onCommittedFrame?.(plannedFrame, data);
|
|
748
|
+
}, [plannedFrame, data, onCommittedFrame]);
|
|
749
|
+
// Where the pointer is resting, read from the pointer layer that the live
|
|
750
|
+
// input path feeds and redrawn when it moves. There is no prop beside it: a
|
|
751
|
+
// hover cannot be handed to this frame at all, only reported to the terminal.
|
|
752
|
+
const resting = useSyncExternalStore(watchPointerRest, pointerRestingCell, pointerRestingCell);
|
|
753
|
+
// And only a frame that publishes its geometry for hit testing can be hovered
|
|
754
|
+
// — publishing it is what makes a pointer report resolve against this frame
|
|
755
|
+
// at all. A capture publishes none and resolves none, so no golden, anchor or
|
|
756
|
+
// colour capture can carry a highlight however the pointer came to rest: the
|
|
757
|
+
// pointerless appearance is what that path draws by construction, not what a
|
|
758
|
+
// guard strips back out of it.
|
|
759
|
+
const hover = onCommittedFrame === undefined ? null : resting;
|
|
760
|
+
const hoveredRow = useMemo(() => {
|
|
761
|
+
const plan = plannedFrame.plan;
|
|
762
|
+
if (hover === null || plan.kind !== "frame"
|
|
763
|
+
|| plannedFrame.content === undefined)
|
|
764
|
+
return null;
|
|
765
|
+
const interaction = plannedFrame.interaction;
|
|
766
|
+
// The whole surface a click resolves through, not only the geometry: what a
|
|
767
|
+
// click at this cell ACTS ON is the highlight's answer, and that route
|
|
768
|
+
// passes through the focus the frame is drawn with and the rows a key may
|
|
769
|
+
// stand on. Deriving the highlight from the row lookup alone would advertise
|
|
770
|
+
// a row of the drawn view while the rail marker stands on another — where
|
|
771
|
+
// the click opens the marked view and selects nothing.
|
|
772
|
+
return pointerHoverRow({
|
|
773
|
+
plan,
|
|
774
|
+
interaction,
|
|
775
|
+
drawnRowIds: drawnRunViewRowIds(data, interaction, plannedFrame.content.items.rows),
|
|
776
|
+
rowIds: selectableRunViewRowIds(interaction.activeView, deriveRunViewRows(data, interaction.activeView, interaction.filterQuery)
|
|
777
|
+
.map((row) => row.id)),
|
|
778
|
+
}, hover) ?? null;
|
|
779
|
+
}, [plannedFrame, data, hover]);
|
|
780
|
+
return (_jsx(HoveredRow.Provider, { value: hoveredRow, children: _jsx(RunCockpitFrameFromPlan, { data: data, plannedFrame: plannedFrame }) }));
|
|
577
781
|
}
|
|
578
782
|
function requiredRegion(plan, id) {
|
|
579
783
|
const region = plan.regions.find((candidate) => candidate.id === id);
|
|
@@ -588,6 +792,21 @@ function assertPlanSpans(plan) {
|
|
|
588
792
|
let nextRow = 0;
|
|
589
793
|
for (const [id, span] of Object.entries(plan.rowSpans)) {
|
|
590
794
|
const region = requiredRegion(plan, id);
|
|
795
|
+
const host = FRAME_NESTED_ROW_BANDS[id];
|
|
796
|
+
if (host !== undefined) {
|
|
797
|
+
// A nested band refines its host's rows rather than tiling beside them,
|
|
798
|
+
// so it advances no row cursor — and is held to its span and to staying
|
|
799
|
+
// inside the band that hosts it.
|
|
800
|
+
const within = requiredRegion(plan, host);
|
|
801
|
+
if (region.rows !== span ||
|
|
802
|
+
region.row < within.row ||
|
|
803
|
+
region.row + region.rows > within.row + within.rows ||
|
|
804
|
+
region.column < within.column ||
|
|
805
|
+
region.column + region.columns > within.column + within.columns) {
|
|
806
|
+
throw new Error(`planned row region ${id} has offset/span ${region.row}/${region.rows} column ${region.column}/${region.columns}, expected span ${span} inside ${host} at ${within.row}/${within.rows} column ${within.column}/${within.columns}`);
|
|
807
|
+
}
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
591
810
|
const fullWidth = id !== "body";
|
|
592
811
|
if (region.row !== nextRow ||
|
|
593
812
|
region.rows !== span ||
|