tickmarkr 1.79.0 → 1.81.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/kimi.js +23 -6
- package/dist/brand.d.ts +4 -0
- package/dist/brand.js +4 -0
- package/dist/cli/commands/ui.d.ts +1 -1
- package/dist/cli/commands/ui.js +45 -4
- package/dist/drivers/herdr.js +28 -6
- package/dist/tui/cockpit/capture.d.ts +117 -0
- package/dist/tui/cockpit/capture.js +329 -0
- package/dist/tui/cockpit/components.d.ts +142 -0
- package/dist/tui/cockpit/components.js +274 -0
- package/dist/tui/cockpit/demo.d.ts +41 -0
- package/dist/tui/cockpit/demo.js +144 -0
- package/dist/tui/cockpit/derive.d.ts +39 -0
- package/dist/tui/cockpit/derive.js +390 -0
- package/dist/tui/cockpit/keys.d.ts +149 -0
- package/dist/tui/cockpit/keys.js +265 -0
- package/dist/tui/cockpit/layout.d.ts +76 -0
- package/dist/tui/cockpit/layout.js +84 -0
- package/dist/tui/cockpit/live.d.ts +31 -0
- package/dist/tui/cockpit/live.js +135 -0
- package/dist/tui/cockpit/run-cockpit.d.ts +26 -0
- package/dist/tui/cockpit/run-cockpit.js +248 -0
- package/dist/tui/cockpit/setup-cockpit.d.ts +44 -0
- package/dist/tui/cockpit/setup-cockpit.js +532 -0
- package/dist/tui/cockpit/theme.d.ts +161 -0
- package/dist/tui/cockpit/theme.js +170 -0
- package/package.json +2 -1
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
import { cloneElement, } from "react";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { BANNER, GLYPHS, PLAIN_BANNER, } from "../../brand.js";
|
|
6
|
+
import { DEFAULT_CONFIG, INTEGRITY_FLOOR_SHAPES, TIER_RANK, TickmarkrConfigSchema, unifiedYamlDiff, } from "../../config/config.js";
|
|
7
|
+
import { SHAPES } from "../../graph/schema.js";
|
|
8
|
+
import { allocateBandColumns, BandLines, BodyText, CockpitGrid, composeBandLine, JournalRowPanel, measureBandRows, Panel, StateGlyph, StatTile, StatusStrip, } from "./components.js";
|
|
9
|
+
import { FULL_JOURNAL_ROWS, resolveCockpitLayout, } from "./layout.js";
|
|
10
|
+
import { deriveRunCockpitData, PANEL_CHROME_ROWS, panelRows, wrappedRows, } from "./run-cockpit.js";
|
|
11
|
+
import { initialSetupInteractionState, keybarEntries, SETUP_PANEL_FOCUS_ORDER, SURFACE_KEY_BINDINGS, } from "./keys.js";
|
|
12
|
+
const APPROVED_SIDE_RAIL_COLUMN_FLOOR = 80;
|
|
13
|
+
const setupAdditionalBindings = (bindings) => bindings.filter((binding) => !SURFACE_KEY_BINDINGS.run.some((runBinding) => runBinding.key === binding.key));
|
|
14
|
+
function deepMerge(base, overlay) {
|
|
15
|
+
if (overlay === undefined || overlay === null)
|
|
16
|
+
return base;
|
|
17
|
+
if (Array.isArray(base)
|
|
18
|
+
|| Array.isArray(overlay)
|
|
19
|
+
|| typeof base !== "object"
|
|
20
|
+
|| typeof overlay !== "object"
|
|
21
|
+
|| base === null) {
|
|
22
|
+
return overlay;
|
|
23
|
+
}
|
|
24
|
+
const merged = {
|
|
25
|
+
...base,
|
|
26
|
+
};
|
|
27
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
28
|
+
if (value === null) {
|
|
29
|
+
delete merged[key];
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
merged[key] = key in merged
|
|
33
|
+
? deepMerge(merged[key], value)
|
|
34
|
+
: deepMerge({}, value);
|
|
35
|
+
}
|
|
36
|
+
return merged;
|
|
37
|
+
}
|
|
38
|
+
function floorEdits(layers) {
|
|
39
|
+
const explicit = new Set();
|
|
40
|
+
const tombstoned = new Set();
|
|
41
|
+
for (const layer of layers) {
|
|
42
|
+
const floors = layer?.routing?.floors;
|
|
43
|
+
if (!floors || Array.isArray(floors))
|
|
44
|
+
continue;
|
|
45
|
+
for (const [shape, value] of Object.entries(floors)) {
|
|
46
|
+
if (value === null) {
|
|
47
|
+
explicit.delete(shape);
|
|
48
|
+
tombstoned.add(shape);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
tombstoned.delete(shape);
|
|
52
|
+
explicit.add(shape);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { explicit, tombstoned };
|
|
57
|
+
}
|
|
58
|
+
function lowerTier(tier) {
|
|
59
|
+
return tier === "frontier" ? "mid" : "cheap";
|
|
60
|
+
}
|
|
61
|
+
function integrityMinimum(shape) {
|
|
62
|
+
return INTEGRITY_FLOOR_SHAPES.includes(shape)
|
|
63
|
+
? "frontier"
|
|
64
|
+
: "cheap";
|
|
65
|
+
}
|
|
66
|
+
function presetFloor(mode, shape, base) {
|
|
67
|
+
if (mode === "partner-led")
|
|
68
|
+
return "frontier";
|
|
69
|
+
if (mode === "risk-based")
|
|
70
|
+
return base;
|
|
71
|
+
const lowered = lowerTier(base);
|
|
72
|
+
const minimum = integrityMinimum(shape);
|
|
73
|
+
return TIER_RANK[lowered] >= TIER_RANK[minimum] ? lowered : minimum;
|
|
74
|
+
}
|
|
75
|
+
function resolveCapturedConfig(captures) {
|
|
76
|
+
const globalLayer = parse(captures.config.global.raw);
|
|
77
|
+
const repoLayer = parse(captures.config.repo.raw);
|
|
78
|
+
const parsed = TickmarkrConfigSchema.parse(deepMerge(deepMerge(structuredClone(DEFAULT_CONFIG), globalLayer), repoLayer));
|
|
79
|
+
const mode = parsed.routing.mode ?? "risk-based";
|
|
80
|
+
const edits = floorEdits([globalLayer, repoLayer]);
|
|
81
|
+
for (const shape of SHAPES) {
|
|
82
|
+
if (edits.tombstoned.has(shape) || edits.explicit.has(shape))
|
|
83
|
+
continue;
|
|
84
|
+
parsed.routing.floors[shape] = presetFloor(mode, shape, DEFAULT_CONFIG.routing.floors[shape]);
|
|
85
|
+
}
|
|
86
|
+
if (mode === "partner-led") {
|
|
87
|
+
parsed.routing.explore = { ...parsed.routing.explore, mode: "off" };
|
|
88
|
+
}
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
function stagedPaths(value, path = []) {
|
|
92
|
+
if (value === null
|
|
93
|
+
|| Array.isArray(value)
|
|
94
|
+
|| typeof value !== "object") {
|
|
95
|
+
return path.length > 0 ? [path.join(".")] : [];
|
|
96
|
+
}
|
|
97
|
+
return Object.entries(value).flatMap(([key, child]) => stagedPaths(child, [...path, key]));
|
|
98
|
+
}
|
|
99
|
+
function deniedReason(reason) {
|
|
100
|
+
if (!reason)
|
|
101
|
+
return "channel denied access";
|
|
102
|
+
return reason.match(/Invalid params: "[^"]+"/)?.[0]
|
|
103
|
+
?? reason.split(/\.\s+Run\b/, 1)[0]
|
|
104
|
+
?? reason;
|
|
105
|
+
}
|
|
106
|
+
export function deriveSetupCockpitData(captures, binaryVersion) {
|
|
107
|
+
const harnessHealth = Object.entries(captures.doctor.data)
|
|
108
|
+
.filter(([id]) => id !== "autoPrefer")
|
|
109
|
+
.map(([id, raw]) => [id, raw]);
|
|
110
|
+
const harnesses = harnessHealth.map(([id, health]) => {
|
|
111
|
+
const models = new Set([
|
|
112
|
+
...(health.models ?? []),
|
|
113
|
+
...Object.keys(health.modelAuth ?? {}),
|
|
114
|
+
]);
|
|
115
|
+
const state = health.installed !== true
|
|
116
|
+
? "inactive"
|
|
117
|
+
: health.authed === true
|
|
118
|
+
? "pass"
|
|
119
|
+
: "fail";
|
|
120
|
+
return {
|
|
121
|
+
id,
|
|
122
|
+
version: health.version ?? "not installed",
|
|
123
|
+
modelCount: models.size,
|
|
124
|
+
state,
|
|
125
|
+
stateWord: state === "pass" ? "authed"
|
|
126
|
+
: state === "inactive" ? "missing"
|
|
127
|
+
: "unauthed",
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
const deniedChannels = harnessHealth.flatMap(([id, health]) => Object.entries(health.modelAuth ?? {}).flatMap(([model, verdict]) => verdict.authed === false
|
|
131
|
+
? [{
|
|
132
|
+
channel: `${id}:${model}`,
|
|
133
|
+
reason: deniedReason(verdict.reason),
|
|
134
|
+
}]
|
|
135
|
+
: []));
|
|
136
|
+
const authenticated = harnessHealth.filter(([, health]) => health.installed === true && health.authed === true).length;
|
|
137
|
+
const routable = harnessHealth.filter(([, health]) => health.installed === true
|
|
138
|
+
&& health.authed === true
|
|
139
|
+
&& Object.values(health.modelAuth ?? {}).some((model) => model.authed === true)).length;
|
|
140
|
+
const repoLayer = parse(captures.config.repo.raw);
|
|
141
|
+
const reviewSource = captures.journals.find((capture) => capture.fileName === "run-20260724-231138.journal.jsonl");
|
|
142
|
+
if (!reviewSource)
|
|
143
|
+
throw new Error("healthy cockpit capture is missing");
|
|
144
|
+
return {
|
|
145
|
+
binaryVersion,
|
|
146
|
+
harnesses,
|
|
147
|
+
deniedChannels,
|
|
148
|
+
counts: {
|
|
149
|
+
found: harnesses.length,
|
|
150
|
+
authenticated,
|
|
151
|
+
routable,
|
|
152
|
+
},
|
|
153
|
+
config: resolveCapturedConfig(captures),
|
|
154
|
+
overlayDiff: unifiedYamlDiff(captures.config.global.raw, captures.config.repo.raw, captures.config.repo.fileName),
|
|
155
|
+
stagedChanges: stagedPaths(repoLayer),
|
|
156
|
+
reviewRows: deriveRunCockpitData(reviewSource, binaryVersion).journalRows,
|
|
157
|
+
overlayTarget: captures.config.repo.fileName,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function identityBannerLines() {
|
|
161
|
+
return (process.env.NO_COLOR === undefined ? BANNER : PLAIN_BANNER).trimEnd().split("\n");
|
|
162
|
+
}
|
|
163
|
+
function identityVersionLines(data, interaction) {
|
|
164
|
+
return [
|
|
165
|
+
`v${data.binaryVersion} · binary ${GLYPHS.pass}`,
|
|
166
|
+
`setup · step ${interaction?.step ?? 2}/6`,
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
function IdentityHeader({ data, secondary = true, interaction, }) {
|
|
170
|
+
const mark = identityBannerLines();
|
|
171
|
+
const [version, step] = identityVersionLines(data, interaction);
|
|
172
|
+
return (_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Box, { flexDirection: "column", children: (secondary ? mark : mark.slice(0, 1)).map((line, index) => (_jsx(BodyText, { emphasis: "strong", children: line }, `${index}:${line}`))) }), _jsxs(Box, { flexDirection: "column", alignItems: "flex-end", children: [_jsx(BodyText, { emphasis: "strong", children: version }), secondary && _jsx(BodyText, { children: step })] })] }));
|
|
173
|
+
}
|
|
174
|
+
function NavigationPanel({ width = 16, compact = false, step = 2, focused = false, }) {
|
|
175
|
+
const entries = [
|
|
176
|
+
"Detect",
|
|
177
|
+
"Harnesses",
|
|
178
|
+
"Fleet",
|
|
179
|
+
"Gates",
|
|
180
|
+
"Consults",
|
|
181
|
+
"Reviewers",
|
|
182
|
+
"Review",
|
|
183
|
+
];
|
|
184
|
+
if (!compact) {
|
|
185
|
+
return (_jsx(Panel, { title: "SETUP", width: width, focused: focused, children: entries.map((entry, index) => (_jsxs(BodyText, { emphasis: index + 1 === step ? "strong" : "normal", children: [index + 1 === step ? `${GLYPHS.pointer} ` : " ", entry] }, entry))) }));
|
|
186
|
+
}
|
|
187
|
+
return (_jsx(Panel, { title: "SETUP", width: width, focused: focused, children: entries.map((entry, index) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsxs(BodyText, { emphasis: index + 1 === step ? "strong" : "normal", children: [index + 1 === step ? `${GLYPHS.pointer} ` : " ", entry] }) }, entry))) }));
|
|
188
|
+
}
|
|
189
|
+
function KeysPanel({ bindings, width = 16, focused = false, }) {
|
|
190
|
+
return (_jsx(Panel, { title: "KEYS", width: width, focused: focused, children: keybarEntries(bindings).map((item) => (_jsxs(Box, { children: [_jsx(BodyText, { emphasis: "strong", children: item.key }), _jsxs(BodyText, { children: [" ", item.label] })] }, item.key))) }));
|
|
191
|
+
}
|
|
192
|
+
function CompactKeysPanel({ bindings, width = 16, focused = false, }) {
|
|
193
|
+
const contentColumns = Math.max(1, width - 4);
|
|
194
|
+
return (_jsx(Panel, { title: "KEYS", width: width, focused: focused, children: keybarEntries(setupAdditionalBindings(bindings)).map((item) => (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", children: [_jsx(BodyText, { emphasis: "strong", children: item.key }), _jsxs(BodyText, { children: [" ", item.label.slice(0, Math.max(0, contentColumns - [...item.key].length - 1))] })] }, item.key))) }));
|
|
195
|
+
}
|
|
196
|
+
function SetupKeybar({ bindings, width, }) {
|
|
197
|
+
return (_jsx(Box, { flexDirection: "row", flexWrap: "nowrap", width: width, height: 1, overflow: "hidden", "aria-label": "setup cockpit keys", children: keybarEntries(bindings).map((item, index) => (_jsxs(Box, { flexShrink: 0, children: [index > 0 && _jsx(BodyText, { emphasis: "dim", children: " \u00B7 " }), _jsx(BodyText, { emphasis: "strong", children: item.key }), _jsxs(BodyText, { emphasis: "dim", children: [" ", item.label] })] }, item.key))) }));
|
|
198
|
+
}
|
|
199
|
+
function SetupInteractionPanel({ data, interaction, bindings, }) {
|
|
200
|
+
if (interaction?.savePrompt === true) {
|
|
201
|
+
return (_jsx(LinePanel, { title: "SAVE", lines: ["confirm save · y yes · n cancel", ...overlayDiffLines(data)], columns: 72 }));
|
|
202
|
+
}
|
|
203
|
+
if (interaction?.help === true) {
|
|
204
|
+
return (_jsx(LinePanel, { title: "HELP", lines: keybarEntries(bindings).map((item) => `${item.key} ${item.label}`), columns: 72 }));
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
function setupStatusItems(data, interaction) {
|
|
209
|
+
const items = [
|
|
210
|
+
{ state: "neutral", text: `overlay ${data.overlayTarget}` },
|
|
211
|
+
{ state: "pass", text: "base untouched" },
|
|
212
|
+
interaction?.saved === true
|
|
213
|
+
? { state: "active", text: "changes saved" }
|
|
214
|
+
: { state: "warn", text: `${data.stagedChanges.length} changes unsaved` },
|
|
215
|
+
];
|
|
216
|
+
if (interaction !== undefined) {
|
|
217
|
+
if (interaction.all)
|
|
218
|
+
items.push({ state: "active", text: "all selected" });
|
|
219
|
+
else if (interaction.selected.length > 0) {
|
|
220
|
+
items.push({ state: "active", text: "selection selected" });
|
|
221
|
+
}
|
|
222
|
+
else if (interaction.selection >= 0) {
|
|
223
|
+
items.push({ state: "neutral", text: `selection ${interaction.selection + 1}` });
|
|
224
|
+
}
|
|
225
|
+
if (interaction.probeRevision > 0) {
|
|
226
|
+
items.push({ state: "pass", text: `re-probed ${interaction.probeRevision}` });
|
|
227
|
+
}
|
|
228
|
+
if (interaction.panel !== 1) {
|
|
229
|
+
const panel = SETUP_PANEL_FOCUS_ORDER[interaction.panel] ?? "HARNESSES";
|
|
230
|
+
items.push({ state: "neutral", text: `panel ${panel}` });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return items;
|
|
234
|
+
}
|
|
235
|
+
function fullKeysFit(rows, layout, data, columns, bindings) {
|
|
236
|
+
if (!layout.elements.secondaryHeader)
|
|
237
|
+
return false;
|
|
238
|
+
const wrappedKeyRows = {
|
|
239
|
+
"three-column": 0,
|
|
240
|
+
"folded-keys": 2,
|
|
241
|
+
stacked: 4,
|
|
242
|
+
}[layout.arrangement];
|
|
243
|
+
const panelRows = bindings.length
|
|
244
|
+
+ wrappedKeyRows
|
|
245
|
+
+ 3; // panel borders and title row
|
|
246
|
+
return headerRowsFor(layout, data, columns) + 2 + panelRows <= Math.floor(rows);
|
|
247
|
+
}
|
|
248
|
+
function sidePanelWidth(layout) {
|
|
249
|
+
if (layout.arrangement === "three-column")
|
|
250
|
+
return 16;
|
|
251
|
+
if (layout.arrangement === "folded-keys")
|
|
252
|
+
return 12;
|
|
253
|
+
return 11;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Every detail panel publishes the body lines it draws, so the height ladder
|
|
257
|
+
* can charge it the rows those very lines occupy rather than a constant that
|
|
258
|
+
* goes stale the moment the panel changes.
|
|
259
|
+
*/
|
|
260
|
+
function detectedRows(data) {
|
|
261
|
+
return [
|
|
262
|
+
...data.harnesses.map((harness) => ({
|
|
263
|
+
key: harness.id,
|
|
264
|
+
state: harness.state,
|
|
265
|
+
text: ` ${harness.stateWord} · ${harness.id} · ${harness.version}`
|
|
266
|
+
+ ` · ${harness.modelCount} models`,
|
|
267
|
+
})),
|
|
268
|
+
...data.deniedChannels.map((denied) => ({
|
|
269
|
+
key: denied.channel,
|
|
270
|
+
state: "fail",
|
|
271
|
+
text: ` denied · ${denied.channel} — ${denied.reason}`,
|
|
272
|
+
})),
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
function fleetLines(data) {
|
|
276
|
+
const routing = data.config.routing;
|
|
277
|
+
return [
|
|
278
|
+
`mode ${routing.mode ?? "risk-based"}`,
|
|
279
|
+
`floors ${Object.entries(routing.floors)
|
|
280
|
+
.map(([shape, tier]) => `${shape}:${tier}`)
|
|
281
|
+
.join(" · ")}`,
|
|
282
|
+
`deny ${(routing.deny?.models ?? []).join(", ") || "none"}`,
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
function gatesLines(data) {
|
|
286
|
+
return [
|
|
287
|
+
`test ${data.config.gates.test ?? "auto-detected"}`,
|
|
288
|
+
`diff cap ${data.config.gates.diffCap}`,
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
function consultsLines(data) {
|
|
292
|
+
const consult = data.config.consult;
|
|
293
|
+
return [
|
|
294
|
+
`seat ${consult.adapter}:${consult.model}`,
|
|
295
|
+
`stall ${consult.stallMinutes} minutes`,
|
|
296
|
+
...(consult.prefer ?? []).map((seat) => `prefer ${seat}`),
|
|
297
|
+
];
|
|
298
|
+
}
|
|
299
|
+
function reviewersLines(data) {
|
|
300
|
+
const review = data.config.review;
|
|
301
|
+
return [
|
|
302
|
+
`required ${String(review.required)}`,
|
|
303
|
+
`complexity threshold ${review.complexityThreshold}`,
|
|
304
|
+
...(review.prefer ?? []).map((reviewer) => `prefer ${reviewer}`),
|
|
305
|
+
];
|
|
306
|
+
}
|
|
307
|
+
function overlayDiffLines(data) {
|
|
308
|
+
const lines = data.overlayDiff.split("\n");
|
|
309
|
+
return [
|
|
310
|
+
...lines.slice(0, 2),
|
|
311
|
+
...lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).slice(0, 2),
|
|
312
|
+
...lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).slice(0, 4),
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
function HarnessRows({ rows, columns, }) {
|
|
316
|
+
return (_jsx(Panel, { title: "DETECTED", children: rows.flatMap((row) => {
|
|
317
|
+
const [first = "", ...continuations] = composeBandLine(row.text.trimStart(), Math.max(1, columns));
|
|
318
|
+
return [
|
|
319
|
+
_jsxs(Box, { children: [_jsx(StateGlyph, { state: row.state }), _jsxs(BodyText, { children: [" ", first] })] }, row.key),
|
|
320
|
+
...continuations.map((line, index) => (_jsx(BodyText, { children: line }, `${row.key}:continuation:${index}`))),
|
|
321
|
+
];
|
|
322
|
+
}) }));
|
|
323
|
+
}
|
|
324
|
+
function LinePanel({ title, lines, columns, emphasis, }) {
|
|
325
|
+
return (_jsx(Panel, { title: title, children: _jsx(BandLines, { lines: lines, columns: columns, emphasis: emphasis }) }));
|
|
326
|
+
}
|
|
327
|
+
function lineBandContent(title, lines) {
|
|
328
|
+
return { title, lines };
|
|
329
|
+
}
|
|
330
|
+
function LineBand({ columns, panels, }) {
|
|
331
|
+
const widths = allocateBandColumns(columns, panels);
|
|
332
|
+
return (_jsx(CockpitGrid, { columns: columns, columnContents: panels, children: panels.map((panel, index) => (_jsx(LinePanel, { title: panel.title, lines: panel.lines, columns: Math.max(1, widths[index] - 4) }, panel.title))) }));
|
|
333
|
+
}
|
|
334
|
+
function statsSummaryLine(data) {
|
|
335
|
+
return `found ${data.counts.found} · authenticated ${data.counts.authenticated}`
|
|
336
|
+
+ ` · routable ${data.counts.routable}`;
|
|
337
|
+
}
|
|
338
|
+
function SetupStats({ data, mode, }) {
|
|
339
|
+
if (mode === "summary") {
|
|
340
|
+
return _jsx(BodyText, { children: statsSummaryLine(data) });
|
|
341
|
+
}
|
|
342
|
+
return (_jsxs(CockpitGrid, { children: [_jsx(StatTile, { label: "FOUND", value: data.counts.found, samples: [data.counts.found] }), _jsx(StatTile, { label: "AUTHENTICATED", value: data.counts.authenticated, samples: [data.counts.authenticated] }), _jsx(StatTile, { label: "ROUTABLE", value: data.counts.routable, samples: [data.counts.routable] })] }));
|
|
343
|
+
}
|
|
344
|
+
function SizedReviewPanel({ rows, bodyRows, }) {
|
|
345
|
+
const visible = rows.slice(0, bodyRows);
|
|
346
|
+
const emptyRows = Math.max(0, bodyRows - visible.length);
|
|
347
|
+
const review = JournalRowPanel({ rows: visible, title: "REVIEW" });
|
|
348
|
+
const reviewProps = review.props;
|
|
349
|
+
return cloneElement(review, {}, reviewProps.children, emptyRows > 0 ? _jsx(Box, { height: emptyRows }, "review-space") : null);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The contracted ladder (§5.4) governs the elements every surface shares. The
|
|
353
|
+
* detail panels below it are the setup body, so they surrender first, from the
|
|
354
|
+
* bottom of this list upward: what a step is *about* outlives what merely
|
|
355
|
+
* describes the fleet.
|
|
356
|
+
*/
|
|
357
|
+
export const SETUP_DETAIL_ORDER = [
|
|
358
|
+
"detected",
|
|
359
|
+
"consultsAndReviewers",
|
|
360
|
+
"overlayDiff",
|
|
361
|
+
"fleetAndGates",
|
|
362
|
+
];
|
|
363
|
+
/** Columns the HARNESSES panel gives its body, after side rails and chrome. */
|
|
364
|
+
function contentInnerWidth(columns, layout) {
|
|
365
|
+
const rails = Math.floor(columns) >= APPROVED_SIDE_RAIL_COLUMN_FLOOR
|
|
366
|
+
? 2 * sidePanelWidth(layout) + 2 // both rails and the gaps beside them
|
|
367
|
+
: 0;
|
|
368
|
+
return Math.max(1, Math.floor(columns) - rails - 4);
|
|
369
|
+
}
|
|
370
|
+
function gridRows(panels, inner) {
|
|
371
|
+
return measureBandRows(inner, panels);
|
|
372
|
+
}
|
|
373
|
+
function detailRows(detail, data, inner) {
|
|
374
|
+
switch (detail) {
|
|
375
|
+
case "consultsAndReviewers":
|
|
376
|
+
return gridRows([
|
|
377
|
+
lineBandContent("CONSULTS", consultsLines(data)),
|
|
378
|
+
lineBandContent("REVIEWERS", reviewersLines(data)),
|
|
379
|
+
], inner);
|
|
380
|
+
case "overlayDiff":
|
|
381
|
+
return panelRows(overlayDiffLines(data), inner - 4);
|
|
382
|
+
case "fleetAndGates":
|
|
383
|
+
return gridRows([
|
|
384
|
+
lineBandContent("FLEET", fleetLines(data)),
|
|
385
|
+
lineBandContent("GATES", gatesLines(data)),
|
|
386
|
+
], inner);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/** Every detected row spends its first two columns on the state glyph and gap. */
|
|
390
|
+
function detectedPanelRows(rows, inner) {
|
|
391
|
+
const columns = Math.max(1, inner - 6);
|
|
392
|
+
return PANEL_CHROME_ROWS + rows.reduce((total, row) => total + composeBandLine(row.text.trimStart(), columns).length, 0);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* DETECTED is the body of the step the surface is showing, so — like the
|
|
396
|
+
* journal body it stands in for (§5.4 rank 5) — it shrinks rather than being
|
|
397
|
+
* replaced: it gives up rows from the bottom, never below one row per state it
|
|
398
|
+
* still has to report, and only disappears when even that will not fit.
|
|
399
|
+
*/
|
|
400
|
+
function detectedPlan(data, inner, budget) {
|
|
401
|
+
const all = detectedRows(data);
|
|
402
|
+
if (detectedPanelRows(all, inner) <= budget)
|
|
403
|
+
return all;
|
|
404
|
+
const states = new Set();
|
|
405
|
+
const smallest = all.filter((row) => {
|
|
406
|
+
if (states.has(row.state))
|
|
407
|
+
return false;
|
|
408
|
+
states.add(row.state);
|
|
409
|
+
return true;
|
|
410
|
+
});
|
|
411
|
+
if (detectedPanelRows(smallest, inner) > budget)
|
|
412
|
+
return [];
|
|
413
|
+
// Grow back toward the full panel, keeping the panel's own row order.
|
|
414
|
+
const kept = new Set(smallest);
|
|
415
|
+
for (const row of all) {
|
|
416
|
+
if (kept.has(row))
|
|
417
|
+
continue;
|
|
418
|
+
const grown = all.filter((candidate) => kept.has(candidate) || candidate === row);
|
|
419
|
+
if (detectedPanelRows(grown, inner) > budget)
|
|
420
|
+
break;
|
|
421
|
+
kept.add(row);
|
|
422
|
+
}
|
|
423
|
+
return all.filter((row) => kept.has(row));
|
|
424
|
+
}
|
|
425
|
+
function setupDetailPlan(rows, data, inner, base, journalPreferred) {
|
|
426
|
+
// DETECTED may borrow from the review body down to its one-row minimum; the
|
|
427
|
+
// configuration panels below it may not.
|
|
428
|
+
const detected = detectedPlan(data, inner, Math.max(0, Math.floor(rows) - base - 1));
|
|
429
|
+
const retained = new Set(detected.length > 0 ? ["detected"] : []);
|
|
430
|
+
let retainedRows = detected.length > 0
|
|
431
|
+
? detectedPanelRows(detected, inner)
|
|
432
|
+
: 0;
|
|
433
|
+
let available = Math.max(0, Math.floor(rows) - base - journalPreferred - retainedRows);
|
|
434
|
+
for (const detail of SETUP_DETAIL_ORDER.slice(1)) {
|
|
435
|
+
const cost = detailRows(detail, data, inner);
|
|
436
|
+
if (available < cost)
|
|
437
|
+
break;
|
|
438
|
+
retained.add(detail);
|
|
439
|
+
retainedRows += cost;
|
|
440
|
+
available -= cost;
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
detected,
|
|
444
|
+
retained,
|
|
445
|
+
retainedRows,
|
|
446
|
+
complete: retained.size === SETUP_DETAIL_ORDER.length
|
|
447
|
+
&& detected.length === detectedRows(data).length,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function versionColumnWidth(data) {
|
|
451
|
+
return Math.max(...identityVersionLines(data).map((line) => [...line].length));
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The banner sits beside the version column, so a narrow frame leaves it fewer
|
|
455
|
+
* columns and it wraps. Charged from the banner the header actually draws.
|
|
456
|
+
*/
|
|
457
|
+
function headerRowsFor(layout, data, columns) {
|
|
458
|
+
if (!layout.elements.secondaryHeader)
|
|
459
|
+
return 1;
|
|
460
|
+
const banner = Math.max(1, Math.floor(columns) - versionColumnWidth(data));
|
|
461
|
+
return identityBannerLines().reduce((rows, line) => rows + wrappedRows(line, banner), 0);
|
|
462
|
+
}
|
|
463
|
+
function statRowsFor(layout, data, inner) {
|
|
464
|
+
// A tile is its value, its sparkline and its chrome; the stacked band spends
|
|
465
|
+
// one more row because the widest label wraps.
|
|
466
|
+
if (layout.stats.mode === "tiles") {
|
|
467
|
+
return PANEL_CHROME_ROWS + 2 + (layout.arrangement === "stacked" ? 1 : 0);
|
|
468
|
+
}
|
|
469
|
+
return wrappedRows(statsSummaryLine(data), inner);
|
|
470
|
+
}
|
|
471
|
+
/** Every row the frame spends before the detail panels and the review body. */
|
|
472
|
+
function baseRows(layout, data, columns, inner) {
|
|
473
|
+
return headerRowsFor(layout, data, columns)
|
|
474
|
+
+ 2 // permanent status strip and keybar
|
|
475
|
+
+ 3 // outer HARNESSES panel border and title
|
|
476
|
+
+ statRowsFor(layout, data, inner)
|
|
477
|
+
+ PANEL_CHROME_ROWS; // review border and title
|
|
478
|
+
}
|
|
479
|
+
function LadderSetupPanel({ data, layout, columns, rows, interaction, bindings, focused = true, }) {
|
|
480
|
+
const inner = contentInnerWidth(columns, layout);
|
|
481
|
+
const preferredReviewRows = Math.min(FULL_JOURNAL_ROWS, layout.journalRows);
|
|
482
|
+
const base = baseRows(layout, data, columns, inner);
|
|
483
|
+
const details = setupDetailPlan(rows, data, inner, base, preferredReviewRows);
|
|
484
|
+
const spare = Math.max(1, Math.floor(rows) - base - details.retainedRows);
|
|
485
|
+
// Once the whole arrangement is drawn the review body is the only sink left,
|
|
486
|
+
// so it absorbs the surplus; below that it holds its contracted height.
|
|
487
|
+
const visibleReviewRows = details.complete
|
|
488
|
+
? spare
|
|
489
|
+
: Math.min(preferredReviewRows, spare);
|
|
490
|
+
const interactionPanel = SetupInteractionPanel({ data, interaction, bindings });
|
|
491
|
+
return (_jsx(Panel, { title: "HARNESSES", focused: focused, flexGrow: 1, children: interactionPanel ?? (_jsxs(_Fragment, { children: [_jsx(SetupStats, { data: data, mode: layout.stats.mode }), details.retained.has("detected") && (_jsx(HarnessRows, { rows: details.detected, columns: Math.max(1, inner - 6) })), details.retained.has("fleetAndGates") && (_jsx(LineBand, { columns: inner, panels: [
|
|
492
|
+
lineBandContent("FLEET", fleetLines(data)),
|
|
493
|
+
lineBandContent("GATES", gatesLines(data)),
|
|
494
|
+
] })), details.retained.has("consultsAndReviewers") && (_jsx(LineBand, { columns: inner, panels: [
|
|
495
|
+
lineBandContent("CONSULTS", consultsLines(data)),
|
|
496
|
+
lineBandContent("REVIEWERS", reviewersLines(data)),
|
|
497
|
+
] })), details.retained.has("overlayDiff") && (_jsx(LinePanel, { title: "OVERLAY DIFF", lines: overlayDiffLines(data), columns: Math.max(1, inner - 4), emphasis: "dim" })), _jsx(SizedReviewPanel, { rows: data.reviewRows, bodyRows: visibleReviewRows })] })) }));
|
|
498
|
+
}
|
|
499
|
+
function SetupPanel({ data, inner, interaction, bindings, focused = true, }) {
|
|
500
|
+
const interactionPanel = SetupInteractionPanel({ data, interaction, bindings });
|
|
501
|
+
return (_jsx(Panel, { title: "HARNESSES", focused: focused, flexGrow: 1, children: interactionPanel ?? (_jsxs(_Fragment, { children: [_jsxs(CockpitGrid, { children: [_jsx(StatTile, { label: "FOUND", value: data.counts.found, samples: [data.counts.found] }), _jsx(StatTile, { label: "AUTHENTICATED", value: data.counts.authenticated, samples: [data.counts.authenticated] }), _jsx(StatTile, { label: "ROUTABLE", value: data.counts.routable, samples: [data.counts.routable] })] }), _jsx(HarnessRows, { rows: detectedRows(data), columns: Math.max(1, inner - 6) }), _jsx(LineBand, { columns: inner, panels: [
|
|
502
|
+
lineBandContent("FLEET", fleetLines(data)),
|
|
503
|
+
lineBandContent("GATES", gatesLines(data)),
|
|
504
|
+
] }), _jsx(LineBand, { columns: inner, panels: [
|
|
505
|
+
lineBandContent("CONSULTS", consultsLines(data)),
|
|
506
|
+
lineBandContent("REVIEWERS", reviewersLines(data)),
|
|
507
|
+
] }), _jsx(LinePanel, { title: "OVERLAY DIFF", lines: overlayDiffLines(data), columns: Math.max(1, inner - 4), emphasis: "dim" }), _jsx(SizedReviewPanel, { rows: data.reviewRows, bodyRows: data.reviewRows.length })] })) }));
|
|
508
|
+
}
|
|
509
|
+
export function SetupCockpitFrame({ data, columns, rows, interaction, bindings = SURFACE_KEY_BINDINGS.setup, }) {
|
|
510
|
+
const currentInteraction = interaction ?? initialSetupInteractionState();
|
|
511
|
+
const focusedPanel = SETUP_PANEL_FOCUS_ORDER[currentInteraction.panel]
|
|
512
|
+
?? "HARNESSES";
|
|
513
|
+
if (rows === undefined) {
|
|
514
|
+
const inner = Math.max(1, Math.floor(columns) - (2 * 16) - 2 - 4);
|
|
515
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(IdentityHeader, { data: data, interaction: currentInteraction }), _jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(NavigationPanel, { step: currentInteraction.step, focused: focusedPanel === "SETUP" }), _jsx(SetupPanel, { data: data, inner: inner, interaction: currentInteraction, bindings: bindings, focused: focusedPanel === "HARNESSES" }), _jsx(KeysPanel, { bindings: bindings, focused: focusedPanel === "KEYS" })] }), _jsx(StatusStrip, { width: columns, items: setupStatusItems(data, currentInteraction) }), _jsx(SetupKeybar, { bindings: bindings, width: columns })] }));
|
|
516
|
+
}
|
|
517
|
+
const layout = resolveCockpitLayout(columns, rows);
|
|
518
|
+
if (layout.renderer === "plain")
|
|
519
|
+
return _jsx(_Fragment, {});
|
|
520
|
+
const showSideRails = columns >= APPROVED_SIDE_RAIL_COLUMN_FLOOR;
|
|
521
|
+
const contentLayout = showSideRails
|
|
522
|
+
? layout
|
|
523
|
+
: {
|
|
524
|
+
...layout,
|
|
525
|
+
stats: { ...layout.stats, mode: "summary", rows: 1 },
|
|
526
|
+
};
|
|
527
|
+
const showFullSidePanels = fullKeysFit(rows, contentLayout, data, columns, bindings);
|
|
528
|
+
const resolvedSidePanelWidth = sidePanelWidth(contentLayout);
|
|
529
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(IdentityHeader, { data: data, secondary: layout.elements.secondaryHeader, interaction: currentInteraction }), _jsxs(Box, { flexDirection: "row", gap: showSideRails ? 1 : 0, children: [showSideRails && (_jsx(Box, { width: resolvedSidePanelWidth, flexShrink: 0, children: _jsx(NavigationPanel, { width: resolvedSidePanelWidth, compact: !contentLayout.elements.secondaryHeader, step: currentInteraction.step, focused: focusedPanel === "SETUP" }) })), _jsx(LadderSetupPanel, { data: data, layout: contentLayout, columns: columns, rows: rows, interaction: currentInteraction, bindings: bindings, focused: focusedPanel === "HARNESSES" }), showSideRails && (showFullSidePanels
|
|
530
|
+
? (_jsx(Box, { width: resolvedSidePanelWidth, flexShrink: 0, children: _jsx(KeysPanel, { bindings: bindings, width: resolvedSidePanelWidth, focused: focusedPanel === "KEYS" }) }))
|
|
531
|
+
: (_jsx(Box, { width: resolvedSidePanelWidth, flexShrink: 0, children: _jsx(CompactKeysPanel, { bindings: bindings, width: resolvedSidePanelWidth, focused: focusedPanel === "KEYS" }) })))] }), _jsx(StatusStrip, { width: columns, items: setupStatusItems(data, currentInteraction) }), _jsx(SetupKeybar, { bindings: bindings, width: columns })] }));
|
|
532
|
+
}
|