tickmarkr 1.80.0 → 1.83.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/README.md +4 -4
- package/dist/brand.d.ts +4 -1
- package/dist/brand.js +46 -7
- package/dist/cli/commands/approve.js +53 -8
- package/dist/cli/commands/plan.js +17 -2
- package/dist/cli/commands/report.js +15 -6
- package/dist/cli/commands/ui.d.ts +1 -1
- package/dist/cli/commands/ui.js +47 -4
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/compile/collateral.d.ts +19 -0
- package/dist/compile/collateral.js +90 -0
- package/dist/compile/index.js +18 -4
- package/dist/drivers/herdr.d.ts +1 -0
- package/dist/drivers/herdr.js +19 -0
- package/dist/drivers/types.d.ts +1 -0
- package/dist/gates/acceptance.js +50 -8
- package/dist/gates/llm.js +34 -19
- package/dist/gates/review.d.ts +9 -1
- package/dist/gates/review.js +173 -7
- package/dist/gates/run-gates.d.ts +1 -0
- package/dist/gates/run-gates.js +18 -1
- package/dist/report/bundle.d.ts +6 -1
- package/dist/report/bundle.js +13 -3
- package/dist/run/daemon.d.ts +5 -0
- package/dist/run/daemon.js +188 -27
- package/dist/run/journal.d.ts +5 -0
- package/dist/run/journal.js +71 -1
- package/dist/tui/cockpit/capture.d.ts +107 -1
- package/dist/tui/cockpit/capture.js +287 -10
- package/dist/tui/cockpit/components.d.ts +15 -86
- package/dist/tui/cockpit/components.js +49 -55
- package/dist/tui/cockpit/derive.d.ts +73 -1
- package/dist/tui/cockpit/derive.js +211 -15
- package/dist/tui/cockpit/keys.d.ts +244 -0
- package/dist/tui/cockpit/keys.js +634 -0
- package/dist/tui/cockpit/layout.d.ts +162 -0
- package/dist/tui/cockpit/layout.js +229 -0
- package/dist/tui/cockpit/live.d.ts +80 -0
- package/dist/tui/cockpit/live.js +311 -0
- package/dist/tui/cockpit/run-cockpit.d.ts +65 -2
- package/dist/tui/cockpit/run-cockpit.js +705 -128
- package/dist/tui/cockpit/setup-cockpit.d.ts +149 -1
- package/dist/tui/cockpit/setup-cockpit.js +432 -56
- package/dist/tui/cockpit/views.d.ts +72 -0
- package/dist/tui/cockpit/views.js +56 -0
- package/dist/tui/cockpit/width.d.ts +105 -0
- package/dist/tui/cockpit/width.js +276 -0
- package/fixtures/speckit-sample/tasks.md +2 -2
- package/package.json +3 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { render, useApp, useInput } from "ink";
|
|
4
|
+
import { createElement, useEffect, useRef, useSyncExternalStore } from "react";
|
|
5
|
+
import { graphPath, loadGraph, stateDirName } from "../../graph/graph.js";
|
|
6
|
+
import { Journal, parseRunId } from "../../run/journal.js";
|
|
7
|
+
import { deriveRunCockpitData } from "./derive.js";
|
|
8
|
+
import { deriveRunViewRows, runKeyColumns, RunCockpitFrame } from "./run-cockpit.js";
|
|
9
|
+
import { dispatchRunSurfaceKey, openingRunSurfaceState, reconcileRunInteraction, RUN_INPUT_BINDINGS, selectableRunViewRowIds, } from "./keys.js";
|
|
10
|
+
// ponytail: fixed 2s re-derive cadence, matching `status --watch`; promote to a
|
|
11
|
+
// config knob only if an operator asks.
|
|
12
|
+
const REFRESH_MS = 2_000;
|
|
13
|
+
/**
|
|
14
|
+
* THE engagement selection rule — stated once, here, so it can change in one
|
|
15
|
+
* place without touching the renderer or the command: an explicit engagement
|
|
16
|
+
* reference from the command line wins; the bare command opens the most
|
|
17
|
+
* recently started engagement that has a readable journal.
|
|
18
|
+
*/
|
|
19
|
+
export function selectEngagementRunId(cwd, explicit) {
|
|
20
|
+
if (explicit !== undefined)
|
|
21
|
+
return parseRunId(explicit);
|
|
22
|
+
return Journal.latestRunId(cwd, { withJournal: true });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The journal bytes of a real engagement — never a committed capture. A read
|
|
26
|
+
* failure throws: the caller refuses rather than drawing from nothing.
|
|
27
|
+
*/
|
|
28
|
+
export function loadEngagementSource(cwd, runId) {
|
|
29
|
+
const id = parseRunId(runId);
|
|
30
|
+
const path = join(cwd, stateDirName(cwd), "runs", id, "journal.jsonl");
|
|
31
|
+
const raw = readFileSync(path, "utf8");
|
|
32
|
+
return { fileName: `${id}.journal.jsonl`, raw };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The compiled graph, when the repository has one. It lends the tasks view its
|
|
36
|
+
* identities and nothing else, so an engagement with no compiled graph at all
|
|
37
|
+
* still draws every task the journal mentions rather than refusing to open.
|
|
38
|
+
*
|
|
39
|
+
* Absence is the only fallback. A graph that exists and cannot be read, parsed
|
|
40
|
+
* or validated is a fault, and swallowing it would quietly demote the surface:
|
|
41
|
+
* every task the graph alone knows about would vanish from the rows, and a
|
|
42
|
+
* selection standing on one of them would be cleared as if the engagement had
|
|
43
|
+
* dropped it. So it throws, and the caller keeps its last good frame instead.
|
|
44
|
+
*/
|
|
45
|
+
export function loadEngagementGraph(cwd) {
|
|
46
|
+
if (!existsSync(graphPath(cwd)))
|
|
47
|
+
return undefined;
|
|
48
|
+
return loadGraph(cwd);
|
|
49
|
+
}
|
|
50
|
+
// The same s/m/h age vocabulary `status` speaks (status.ts fmtAge).
|
|
51
|
+
export function formatEventAge(ageMs) {
|
|
52
|
+
const safe = Math.max(0, ageMs);
|
|
53
|
+
if (safe < 90_000)
|
|
54
|
+
return `${Math.floor(safe / 1_000)}s`;
|
|
55
|
+
if (safe < 5_400_000)
|
|
56
|
+
return `${Math.floor(safe / 60_000)}m`;
|
|
57
|
+
return `${Math.floor(safe / 3_600_000)}h`;
|
|
58
|
+
}
|
|
59
|
+
function lastEventMs(raw) {
|
|
60
|
+
const lines = raw.split("\n");
|
|
61
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
62
|
+
const line = lines[index].trim();
|
|
63
|
+
if (!line)
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(line);
|
|
67
|
+
const ms = typeof parsed.ts === "string" ? Date.parse(parsed.ts) : NaN;
|
|
68
|
+
if (Number.isFinite(ms))
|
|
69
|
+
return ms;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// a torn trailing line does not hide the event written before it
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Live derivation over a real engagement's journal bytes. The header's
|
|
79
|
+
* elapsed field additionally states how long ago the engagement's last event
|
|
80
|
+
* arrived, so an engagement that has stopped moving reads as stale rather
|
|
81
|
+
* than as current. Throws on unreadable bytes (empty capture, no run-start) —
|
|
82
|
+
* the caller refuses instead of rendering a plausible surface from nothing.
|
|
83
|
+
*/
|
|
84
|
+
export function deriveLiveRunCockpitData(source, binaryVersion, now = Date.now, graph) {
|
|
85
|
+
const data = deriveRunCockpitData(source, binaryVersion, { graph });
|
|
86
|
+
const lastMs = lastEventMs(source.raw);
|
|
87
|
+
const staleness = lastMs === undefined
|
|
88
|
+
? "last event unknown"
|
|
89
|
+
: `last event ${formatEventAge(now() - lastMs)} ago`;
|
|
90
|
+
return { ...data, elapsed: `${data.elapsed} · ${staleness}` };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Repair the interaction state against the rows a refreshed view now owns.
|
|
94
|
+
* Selection is held by the row's source identity, so it survives rows landing
|
|
95
|
+
* above it and is dropped — never slid onto whoever inherited its index —
|
|
96
|
+
* when the refreshed engagement no longer carries it.
|
|
97
|
+
*/
|
|
98
|
+
export function reconcileLiveRunInteraction(interaction, data) {
|
|
99
|
+
return reconcileRunInteraction(interaction, liveRunViewRowIds(interaction, data));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The rows the live surface hands its keys — the engagement's own identities,
|
|
103
|
+
* minus the journal's, which are never selectable in either tab. This is the
|
|
104
|
+
* one place rows enter the dispatcher, so no key can reach a journal row.
|
|
105
|
+
*/
|
|
106
|
+
export function liveRunViewRowIds(interaction, data) {
|
|
107
|
+
return selectableRunViewRowIds(interaction.activeView,
|
|
108
|
+
// The frame's own rows, so a key can never stand on a row the surface did
|
|
109
|
+
// not draw — one derivation, read by both.
|
|
110
|
+
deriveRunViewRows(data, interaction.activeView, interaction.filterQuery)
|
|
111
|
+
.map((row) => row.id));
|
|
112
|
+
}
|
|
113
|
+
function createLiveCockpitDelivery({ cwd, runId, binaryVersion, now, }) {
|
|
114
|
+
// Both sources are read every refresh: a recompile between refreshes changes
|
|
115
|
+
// which tasks exist, and the surface must draw the graph the repository has
|
|
116
|
+
// now rather than the one it had when the cockpit opened.
|
|
117
|
+
const derive = () => deriveLiveRunCockpitData(loadEngagementSource(cwd, runId), binaryVersion, now, loadEngagementGraph(cwd));
|
|
118
|
+
const opening = openingRunSurfaceState();
|
|
119
|
+
let surface = {
|
|
120
|
+
data: derive(),
|
|
121
|
+
interaction: opening.interaction,
|
|
122
|
+
stashed: opening.stashed,
|
|
123
|
+
};
|
|
124
|
+
const listeners = new Set();
|
|
125
|
+
let batchDepth = 0;
|
|
126
|
+
let pendingDraw = false;
|
|
127
|
+
const publish = () => {
|
|
128
|
+
if (batchDepth > 0) {
|
|
129
|
+
pendingDraw = true;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const listener of listeners)
|
|
133
|
+
listener();
|
|
134
|
+
};
|
|
135
|
+
const transition = (apply) => {
|
|
136
|
+
const next = apply(surface);
|
|
137
|
+
if (next === surface)
|
|
138
|
+
return false;
|
|
139
|
+
surface = next;
|
|
140
|
+
publish();
|
|
141
|
+
return true;
|
|
142
|
+
};
|
|
143
|
+
const delivery = {
|
|
144
|
+
snapshot: () => surface,
|
|
145
|
+
subscribe: (listener) => {
|
|
146
|
+
listeners.add(listener);
|
|
147
|
+
return () => listeners.delete(listener);
|
|
148
|
+
},
|
|
149
|
+
batch: (deliver) => {
|
|
150
|
+
batchDepth += 1;
|
|
151
|
+
try {
|
|
152
|
+
return deliver();
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
batchDepth -= 1;
|
|
156
|
+
if (batchDepth === 0 && pendingDraw) {
|
|
157
|
+
pendingDraw = false;
|
|
158
|
+
publish();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
refresh: () => {
|
|
163
|
+
try {
|
|
164
|
+
const refreshed = derive();
|
|
165
|
+
return transition((current) => ({
|
|
166
|
+
...current,
|
|
167
|
+
data: refreshed,
|
|
168
|
+
interaction: reconcileLiveRunInteraction(current.interaction, refreshed),
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// A torn read mid-append, or a graph caught mid-recompile, keeps the
|
|
173
|
+
// last good frame rather than demoting or blanking the surface.
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
// Every key goes to the one surface dispatcher, in every state the surface
|
|
178
|
+
// can be in. The prompt is a scope inside that registry rather than an
|
|
179
|
+
// owner in front of it, so a global key — Tab above all — reaches its
|
|
180
|
+
// handler with a prompt open exactly as it does without one.
|
|
181
|
+
key: (event, columns = Number.MAX_SAFE_INTEGER) => transition((current) => {
|
|
182
|
+
const next = dispatchRunSurfaceKey(event, { interaction: current.interaction, stashed: current.stashed }, RUN_INPUT_BINDINGS, liveRunViewRowIds(current.interaction, current.data),
|
|
183
|
+
// The dispatcher decides on rail visibility, and the plan owns that.
|
|
184
|
+
runKeyColumns(columns));
|
|
185
|
+
if (next === undefined)
|
|
186
|
+
return current;
|
|
187
|
+
const interaction = reconcileLiveRunInteraction(next.interaction, current.data);
|
|
188
|
+
return interaction === current.interaction && next.stashed === current.stashed
|
|
189
|
+
? current
|
|
190
|
+
: { ...current, interaction, stashed: next.stashed };
|
|
191
|
+
}),
|
|
192
|
+
};
|
|
193
|
+
return delivery;
|
|
194
|
+
}
|
|
195
|
+
function measureOutput(output) {
|
|
196
|
+
const read = () => `${output.columns ?? 80}:${output.rows ?? 24}`;
|
|
197
|
+
let size = read();
|
|
198
|
+
const listeners = new Set();
|
|
199
|
+
const heldInkResizeListeners = new Set();
|
|
200
|
+
const resized = () => {
|
|
201
|
+
size = read();
|
|
202
|
+
for (const listener of listeners)
|
|
203
|
+
listener();
|
|
204
|
+
};
|
|
205
|
+
output.on("resize", resized);
|
|
206
|
+
// Ink otherwise repaints its previous tree as soon as the stream resizes.
|
|
207
|
+
// Hold that repaint until React has published the newly measured plan.
|
|
208
|
+
const subscribe = (event, listener) => {
|
|
209
|
+
if (event === "resize")
|
|
210
|
+
heldInkResizeListeners.add(listener);
|
|
211
|
+
else
|
|
212
|
+
output.on(event, listener);
|
|
213
|
+
return stdout;
|
|
214
|
+
};
|
|
215
|
+
const unsubscribe = (event, listener) => {
|
|
216
|
+
if (event === "resize")
|
|
217
|
+
heldInkResizeListeners.delete(listener);
|
|
218
|
+
else
|
|
219
|
+
output.off(event, listener);
|
|
220
|
+
return stdout;
|
|
221
|
+
};
|
|
222
|
+
const stdout = new Proxy(output, {
|
|
223
|
+
get(target, property) {
|
|
224
|
+
if (property === "on" || property === "addListener")
|
|
225
|
+
return subscribe;
|
|
226
|
+
if (property === "off" || property === "removeListener")
|
|
227
|
+
return unsubscribe;
|
|
228
|
+
const value = Reflect.get(target, property);
|
|
229
|
+
return typeof value === "function" && !Object.hasOwn(target, property)
|
|
230
|
+
? value.bind(target)
|
|
231
|
+
: value;
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
subscribe: (listener) => {
|
|
236
|
+
listeners.add(listener);
|
|
237
|
+
return () => listeners.delete(listener);
|
|
238
|
+
},
|
|
239
|
+
snapshot: () => size,
|
|
240
|
+
stdout,
|
|
241
|
+
settle: () => {
|
|
242
|
+
for (const listener of heldInkResizeListeners)
|
|
243
|
+
listener();
|
|
244
|
+
},
|
|
245
|
+
close: () => output.off("resize", resized),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function LiveApp({ delivery, size, refreshMs, }) {
|
|
249
|
+
const { exit } = useApp();
|
|
250
|
+
const surface = useSyncExternalStore(delivery.subscribe, delivery.snapshot, delivery.snapshot);
|
|
251
|
+
const measuredSize = useSyncExternalStore(size.subscribe, size.snapshot, size.snapshot);
|
|
252
|
+
const [columns, rows] = measuredSize.split(":").map((part) => Math.max(0, Math.floor(Number(part))));
|
|
253
|
+
const drawnSize = useRef(measuredSize);
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (drawnSize.current === measuredSize)
|
|
256
|
+
return;
|
|
257
|
+
drawnSize.current = measuredSize;
|
|
258
|
+
size.settle();
|
|
259
|
+
}, [measuredSize, size]);
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
const timer = setInterval(delivery.refresh, refreshMs);
|
|
262
|
+
return () => clearInterval(timer);
|
|
263
|
+
}, [delivery, refreshMs]);
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
if (surface.interaction.quit)
|
|
266
|
+
exit();
|
|
267
|
+
}, [exit, surface.interaction.quit]);
|
|
268
|
+
useInput((input, key) => {
|
|
269
|
+
if (key.ctrl && input === "c") {
|
|
270
|
+
exit();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
delivery.key({ input, key }, columns);
|
|
274
|
+
});
|
|
275
|
+
return createElement(RunCockpitFrame, {
|
|
276
|
+
data: surface.data,
|
|
277
|
+
columns,
|
|
278
|
+
rows,
|
|
279
|
+
interaction: surface.interaction,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
export async function runLiveCockpit({ input, output, cwd, runId, binaryVersion, refreshMs = REFRESH_MS, now = Date.now, debug = false, onDelivery, }) {
|
|
283
|
+
const delivery = createLiveCockpitDelivery({
|
|
284
|
+
cwd,
|
|
285
|
+
runId,
|
|
286
|
+
binaryVersion,
|
|
287
|
+
now,
|
|
288
|
+
});
|
|
289
|
+
onDelivery?.(delivery);
|
|
290
|
+
// Subscribe before Ink mounts so no stale-size repaint can overtake the
|
|
291
|
+
// surface's newly planned frame.
|
|
292
|
+
const size = measureOutput(output);
|
|
293
|
+
const app = render(createElement(LiveApp, {
|
|
294
|
+
delivery,
|
|
295
|
+
size,
|
|
296
|
+
refreshMs,
|
|
297
|
+
}), {
|
|
298
|
+
stdin: input,
|
|
299
|
+
stdout: size.stdout,
|
|
300
|
+
debug,
|
|
301
|
+
exitOnCtrlC: false,
|
|
302
|
+
patchConsole: false,
|
|
303
|
+
});
|
|
304
|
+
try {
|
|
305
|
+
await app.waitUntilExit();
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
size.close();
|
|
309
|
+
app.unmount();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
|
-
import type
|
|
2
|
+
import { type JournalRow } from "./components.js";
|
|
3
|
+
import { type RunCockpitData } from "./derive.js";
|
|
4
|
+
import { type KeybarEntry, type RunInteractionState, type RunKeyProjection } from "./keys.js";
|
|
5
|
+
import { type RunViewId } from "./views.js";
|
|
6
|
+
import { type FramePlan, type FrameRegion, type FrameState, type FrameCockpitLayout, type FrameView, type MeasuredSize, type PlannedFrame } from "./layout.js";
|
|
3
7
|
export { deriveRunCockpitData } from "./derive.js";
|
|
4
8
|
export type { RunCockpitData } from "./derive.js";
|
|
5
9
|
export { PANEL_CHROME_ROWS } from "./components.js";
|
|
10
|
+
/** The width focus and key routing resolve at. */
|
|
11
|
+
export declare function runKeyColumns(columns: number): number;
|
|
6
12
|
/**
|
|
7
13
|
* How many rows one line of body text occupies once the renderer wraps it into
|
|
8
14
|
* `columns`. This mirrors the renderer's own word wrap — greedy, and breaking a
|
|
@@ -12,8 +18,65 @@ export { PANEL_CHROME_ROWS } from "./components.js";
|
|
|
12
18
|
export declare function wrappedRows(text: string, columns: number): number;
|
|
13
19
|
/** The rows a bordered panel occupies when its body wraps into `columns`. */
|
|
14
20
|
export declare function panelRows(lines: readonly string[], columns: number): number;
|
|
15
|
-
|
|
21
|
+
/**
|
|
22
|
+
* The selectable rows owned by one view. The overview's journal is a passive
|
|
23
|
+
* tail, not its item collection; tasks, gates and fleet remain honestly empty
|
|
24
|
+
* until their dedicated derivations land. Journal rows keep the source-record
|
|
25
|
+
* identities assigned by the production journal derivation.
|
|
26
|
+
*/
|
|
27
|
+
export declare function deriveRunViewRows(data: RunCockpitData, viewId: RunViewId, filterQuery?: string): readonly JournalRow[];
|
|
28
|
+
/** What each drawn region draws: the resolved budgets its panels consume. */
|
|
29
|
+
export type RegionContent = {
|
|
30
|
+
readonly body: number;
|
|
31
|
+
readonly journal: number;
|
|
32
|
+
readonly bodyColumns: number;
|
|
33
|
+
readonly sideRails: boolean;
|
|
34
|
+
readonly stats: FrameCockpitLayout["stats"]["mode"] | "none";
|
|
35
|
+
readonly progressBar: boolean;
|
|
36
|
+
readonly progressCaption: boolean;
|
|
37
|
+
readonly approvedProgress: boolean;
|
|
38
|
+
};
|
|
39
|
+
export type PlannedRunCockpitFrame = {
|
|
40
|
+
readonly plan: FramePlan;
|
|
41
|
+
readonly columns: number;
|
|
42
|
+
readonly rows?: number;
|
|
43
|
+
/** What fills the drawn regions; absent on the unsized legacy surface. */
|
|
44
|
+
readonly content?: RegionContent;
|
|
45
|
+
/** The inputs planFrame planned from, so the guard can re-plan and refuse
|
|
46
|
+
* a span that moved after planning. */
|
|
47
|
+
readonly planning?: {
|
|
48
|
+
readonly size: MeasuredSize;
|
|
49
|
+
readonly view: FrameView;
|
|
50
|
+
readonly state: FrameState;
|
|
51
|
+
};
|
|
52
|
+
readonly interaction: RunInteractionState;
|
|
53
|
+
readonly keyEntries: readonly KeybarEntry[];
|
|
54
|
+
};
|
|
55
|
+
/** Resolve every draw-time input; the plan is planFrame's own return, consumed unmodified. */
|
|
56
|
+
export declare function planRunCockpitFrame({ data, columns, rows, interaction, keyProjection, }: {
|
|
57
|
+
data: RunCockpitData;
|
|
58
|
+
columns: number;
|
|
59
|
+
rows?: number;
|
|
60
|
+
interaction?: RunInteractionState;
|
|
61
|
+
keyProjection?: RunKeyProjection;
|
|
62
|
+
}): PlannedRunCockpitFrame;
|
|
63
|
+
/** The public surface always enters paint through one measured plan. */
|
|
64
|
+
export declare function RunCockpitFrame({ data, columns, rows, interaction, keyProjection, }: {
|
|
16
65
|
data: RunCockpitData;
|
|
17
66
|
columns: number;
|
|
18
67
|
rows?: number;
|
|
68
|
+
interaction?: RunInteractionState;
|
|
69
|
+
keyProjection?: RunKeyProjection;
|
|
70
|
+
}): ReactElement;
|
|
71
|
+
/**
|
|
72
|
+
* Assert the plan's tiled extents, the bytes production paints, and — given
|
|
73
|
+
* the offsets read off the painted nodes — that every planned region was
|
|
74
|
+
* drawn where the plan puts it.
|
|
75
|
+
*/
|
|
76
|
+
export declare function assertFrameConformance(plan: PlannedFrame, drawnFrame: string, drawnRegions?: readonly FrameRegion[]): void;
|
|
77
|
+
/** Production paint consumes only resolved plan inputs and fails closed with
|
|
78
|
+
* the same conformance assertion the acceptance suite runs. */
|
|
79
|
+
export declare function RunCockpitFrameFromPlan({ data, plannedFrame, }: {
|
|
80
|
+
data: RunCockpitData;
|
|
81
|
+
plannedFrame: PlannedRunCockpitFrame;
|
|
19
82
|
}): ReactElement;
|