tickmarkr 1.83.0 → 1.84.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/tui/cockpit/components.d.ts +28 -1
- package/dist/tui/cockpit/components.js +19 -3
- 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 +145 -27
- package/package.json +1 -1
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { type ReactElement, type ReactNode } from "react";
|
|
2
2
|
import { TEXT_EMPHASIS_TOKENS } from "./theme.js";
|
|
3
3
|
export { SPARKLINE_BUCKET_WINDOW } from "./derive.js";
|
|
4
|
+
/**
|
|
5
|
+
* The hover highlight's non-colour half: one cell wide, drawn in the separator
|
|
6
|
+
* the row already spends, from the block family the cockpit already draws with.
|
|
7
|
+
* Colour is the other half and the louder one, but Chalk suppresses inverse and
|
|
8
|
+
* colour together at level 0, so a highlight made of ink alone does not exist
|
|
9
|
+
* under `NO_COLOR` or a colourless TTY. This is a character; it always does.
|
|
10
|
+
*/
|
|
11
|
+
export declare const HOVER_MARKER = "\u258C";
|
|
12
|
+
/**
|
|
13
|
+
* The columns below which a bordered panel no longer reads — the floor a band
|
|
14
|
+
* allocation defends, and the floor planFrame's session resize override clamps
|
|
15
|
+
* the body panel at: a drag surrenders an element whole or not at all, it
|
|
16
|
+
* never collapses one below what reads.
|
|
17
|
+
*/
|
|
18
|
+
export declare const READABLE_PANEL_COLUMNS = 20;
|
|
4
19
|
export declare const PANEL_CHROME_ROWS = 3;
|
|
5
20
|
export declare const BAND_CONTINUATION_PREFIX = "\u21B3 ";
|
|
6
21
|
export type BandColumnContent = {
|
|
@@ -98,11 +113,23 @@ export declare function StatusStrip({ items, width, }: {
|
|
|
98
113
|
items: readonly StatusStripItem[];
|
|
99
114
|
width: number;
|
|
100
115
|
}): ReactElement;
|
|
101
|
-
export declare function JournalRowPanel({ rows, width, focused, title, selection, }: {
|
|
116
|
+
export declare function JournalRowPanel({ rows, width, focused, title, selection, hover, }: {
|
|
102
117
|
rows: readonly JournalRow[];
|
|
103
118
|
width?: number | string;
|
|
104
119
|
focused?: boolean;
|
|
105
120
|
title?: string;
|
|
106
121
|
/** Index of the row the pointer marks; undefined draws no pointer. */
|
|
107
122
|
selection?: number;
|
|
123
|
+
/**
|
|
124
|
+
* Index of the row a live pointer is resting on; undefined draws no
|
|
125
|
+
* highlight, which is every surface nobody is pointing at — a capture above
|
|
126
|
+
* all. The highlight is ink AND shape: the brightest step of the existing
|
|
127
|
+
* data ramp worn inverted, plus {@link HOVER_MARKER} standing in the one
|
|
128
|
+
* separator cell the row already spends between its state glyph and its text.
|
|
129
|
+
* Ink alone would be no highlight at all under `NO_COLOR` or a colourless
|
|
130
|
+
* TTY, where Chalk suppresses inverse and colour alike; the marker is a drawn
|
|
131
|
+
* character, so it survives every colour level. It costs the row no cell it
|
|
132
|
+
* did not already own, so a hover shifts nothing an operator was reading.
|
|
133
|
+
*/
|
|
134
|
+
hover?: number;
|
|
108
135
|
}): ReactElement;
|
|
@@ -18,9 +18,23 @@ import { formatKeybarEntries, keybarEntries, SETUP_KEY_BINDINGS, } from "./keys.
|
|
|
18
18
|
*/
|
|
19
19
|
import { cellWidth as stringWidth, sliceCells } from "./width.js";
|
|
20
20
|
const SPARKLINE_GLYPHS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
21
|
+
/**
|
|
22
|
+
* The hover highlight's non-colour half: one cell wide, drawn in the separator
|
|
23
|
+
* the row already spends, from the block family the cockpit already draws with.
|
|
24
|
+
* Colour is the other half and the louder one, but Chalk suppresses inverse and
|
|
25
|
+
* colour together at level 0, so a highlight made of ink alone does not exist
|
|
26
|
+
* under `NO_COLOR` or a colourless TTY. This is a character; it always does.
|
|
27
|
+
*/
|
|
28
|
+
export const HOVER_MARKER = "▌";
|
|
21
29
|
const BAND_GAP_COLUMNS = 1;
|
|
22
30
|
const PANEL_HORIZONTAL_CHROME = 4;
|
|
23
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The columns below which a bordered panel no longer reads — the floor a band
|
|
33
|
+
* allocation defends, and the floor planFrame's session resize override clamps
|
|
34
|
+
* the body panel at: a drag surrenders an element whole or not at all, it
|
|
35
|
+
* never collapses one below what reads.
|
|
36
|
+
*/
|
|
37
|
+
export const READABLE_PANEL_COLUMNS = 20;
|
|
24
38
|
export const PANEL_CHROME_ROWS = 3;
|
|
25
39
|
export const BAND_CONTINUATION_PREFIX = "↳ ";
|
|
26
40
|
const SETUP_KEYS = keybarEntries(SETUP_KEY_BINDINGS);
|
|
@@ -269,6 +283,8 @@ export function KeyRoster({ entries, width, }) {
|
|
|
269
283
|
export function StatusStrip({ items, width, }) {
|
|
270
284
|
return (_jsx(OneLineItems, { width: width, label: "cockpit status", children: items.map((item, index) => (_jsxs(Box, { flexShrink: 0, children: [index > 0 && _jsx(BodyText, { emphasis: "dim", children: " \u00B7 " }), _jsx(StateGlyph, { state: item.state }), _jsxs(BodyText, { children: [" ", item.text] })] }, `${item.state}:${item.text}`))) }));
|
|
271
285
|
}
|
|
272
|
-
export function JournalRowPanel({ rows, width, focused, title = "JOURNAL", selection, }) {
|
|
273
|
-
return (_jsx(Panel, { title: title, focused: focused, width: width, children: rows.map((row, index) => (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", children: [_jsx(BodyText, { emphasis: "dim", children: row.time }), _jsx(BodyText, { children: " " }), _jsx(StateGlyph, { state: row.state }), _jsx(BodyText, { children: " " }), index === selection && (_jsxs(BodyText, { emphasis: "strong", children: [GLYPHS.pointer, " "] })),
|
|
286
|
+
export function JournalRowPanel({ rows, width, focused, title = "JOURNAL", selection, hover, }) {
|
|
287
|
+
return (_jsx(Panel, { title: title, focused: focused, width: width, children: rows.map((row, index) => (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", children: [_jsx(BodyText, { emphasis: "dim", children: row.time }), _jsx(BodyText, { children: " " }), _jsx(StateGlyph, { state: row.state }), _jsx(BodyText, { emphasis: index === hover ? "strong" : "normal", children: index === hover ? HOVER_MARKER : " " }), index === selection && (_jsxs(BodyText, { emphasis: "strong", children: [GLYPHS.pointer, " "] })), index === hover
|
|
288
|
+
? (_jsx(Text, { inverse: true, color: `ansi256(${COCKPIT_DATA_RAMP[0].xterm})`, children: row.text }))
|
|
289
|
+
: _jsx(BodyText, { children: row.text })] }, row.id))) }));
|
|
274
290
|
}
|
|
@@ -87,11 +87,40 @@ export interface FrameState {
|
|
|
87
87
|
detail?: boolean;
|
|
88
88
|
/** Header caption, pre-measured in terminal cells by the caller. */
|
|
89
89
|
captionCells?: number;
|
|
90
|
+
/**
|
|
91
|
+
* The session's panel resize override: the rail columns the operator dragged
|
|
92
|
+
* the boundary to. An INPUT to the plan, never a bypass of it — the plan
|
|
93
|
+
* recomputes from the measured size plus this override and the renderer
|
|
94
|
+
* draws the recomputed plan unmodified. It lives only in the running
|
|
95
|
+
* process: nothing writes it anywhere, and a relaunch plans the default
|
|
96
|
+
* layout. The plan clamps it at both panels' readable floors — see
|
|
97
|
+
* `planRailColumns`.
|
|
98
|
+
*/
|
|
99
|
+
railColumns?: number;
|
|
90
100
|
}
|
|
91
101
|
export declare const FRAME_WIDTH_FLOOR = 40;
|
|
92
102
|
export declare const FRAME_HEIGHT_FLOOR = 14;
|
|
93
103
|
export declare const SIDEBAR_COLUMN_FLOOR = 64;
|
|
94
104
|
export declare const RAIL_COLUMNS = 15;
|
|
105
|
+
/**
|
|
106
|
+
* The narrowest rail that still reads: the selection marker, one space and the
|
|
107
|
+
* longest view name, whole — the name measured in display cells by the width
|
|
108
|
+
* authority, never String length, so a name that ever gains a wide glyph moves
|
|
109
|
+
* the floor with it. A drag never takes the rail below it — below the floor
|
|
110
|
+
* the element stands at the floor, surrendered whole per the contract, never
|
|
111
|
+
* collapsed.
|
|
112
|
+
*/
|
|
113
|
+
export declare const RAIL_READABLE_FLOOR_COLUMNS: number;
|
|
114
|
+
/**
|
|
115
|
+
* The rail width the plan draws for a session resize override, clamped at both
|
|
116
|
+
* panels' readable floors: the rail never narrows past its own floor, and
|
|
117
|
+
* never widens past what leaves the body its readable floor
|
|
118
|
+
* (`READABLE_PANEL_COLUMNS`). The clamp is the plan's — the override is a
|
|
119
|
+
* request, the floors are the contract, and an element is surrendered whole or
|
|
120
|
+
* not at all, never collapsed by a drag. No override is the contract's
|
|
121
|
+
* constant rail.
|
|
122
|
+
*/
|
|
123
|
+
export declare function planRailColumns(columns: number, override?: number): number;
|
|
95
124
|
export declare const STANDARD_TAIL_HEIGHT = 16;
|
|
96
125
|
export declare const FULL_TAIL_HEIGHT = 24;
|
|
97
126
|
export declare const TAIL_STANDARD_ROWS = 1;
|
|
@@ -127,7 +156,24 @@ export interface FrameFit {
|
|
|
127
156
|
* ordering decision — not a comment about one.
|
|
128
157
|
*/
|
|
129
158
|
export declare const FRAME_SURRENDER_FIT: Readonly<Record<SurrenderedRegionId, (fit: FrameFit) => boolean>>;
|
|
130
|
-
export type FrameRegionId = "header" | "rule" | "strip" | "rail" | "body" | "rule2" | "tail" | "status" | "keybar" | "caption";
|
|
159
|
+
export type FrameRegionId = "header" | "rule" | "strip" | "rail" | "body" | "items" | "rule2" | "tail" | "status" | "keybar" | "caption";
|
|
160
|
+
/**
|
|
161
|
+
* Border plus horizontal padding the drawn panel charges around its body —
|
|
162
|
+
* half of it on each side. The plan owns this because the plan owns the item
|
|
163
|
+
* rows: the paint draws its list into the region planned below, and nothing
|
|
164
|
+
* downstream subtracts chrome of its own to find it again.
|
|
165
|
+
*/
|
|
166
|
+
export declare const RUN_PANEL_HORIZONTAL_CHROME = 4;
|
|
167
|
+
/**
|
|
168
|
+
* Row bands drawn INSIDE another band rather than beside it, mapped to the band
|
|
169
|
+
* that hosts them. A nested band refines its host's rows, so the band walk skips
|
|
170
|
+
* it when tiling the height and asserts containment instead. It is no less a
|
|
171
|
+
* member of `rowSpans` for that: the oracle pins its span against the planned
|
|
172
|
+
* region, and demands the paint register a node at exactly that rectangle —
|
|
173
|
+
* which is precisely what a parallel field beside the plan could never be held
|
|
174
|
+
* to.
|
|
175
|
+
*/
|
|
176
|
+
export declare const FRAME_NESTED_ROW_BANDS: Readonly<Partial<Record<FrameRegionId, FrameRegionId>>>;
|
|
131
177
|
/** The vitals elements in block order, top first; a short rail surrenders from the top, so the meter is the last to go. */
|
|
132
178
|
export declare const SIDEBAR_VITALS_ORDER: readonly ["tasks", "gates", "meter"];
|
|
133
179
|
export type SidebarVitalsElement = (typeof SIDEBAR_VITALS_ORDER)[number];
|
|
@@ -139,6 +185,13 @@ export declare const SIDEBAR_VITALS_COMPACT_ELEMENT_ROWS = 1;
|
|
|
139
185
|
export interface SidebarPlan {
|
|
140
186
|
/** Rows the menu block occupies at the rail's top: the five views, plus the label row when it fits. */
|
|
141
187
|
menuRows: number;
|
|
188
|
+
/**
|
|
189
|
+
* The frame row each view's name is drawn on. The plan does the menu's
|
|
190
|
+
* arithmetic once, here, where the label row is decided — so a rail hit is a
|
|
191
|
+
* lookup in the plan's own rows rather than a second reconstruction of the
|
|
192
|
+
* menu's shape from `menuRows`.
|
|
193
|
+
*/
|
|
194
|
+
viewRows: Readonly<Record<FrameView, number>>;
|
|
142
195
|
/**
|
|
143
196
|
* Blank rows between the last menu row and the first vitals row. Never zero
|
|
144
197
|
* while any vitals element draws — a short rail surrenders whole vitals
|
|
@@ -157,8 +210,14 @@ export interface SidebarPlan {
|
|
|
157
210
|
* one gap row is the vitals budget, spent on the full two-row block when it
|
|
158
211
|
* fits whole, else on the one-row elements, surrendered from the top of the
|
|
159
212
|
* block with the meter surrendered last. An element draws whole or not at all.
|
|
213
|
+
* `railRow` is the frame row the rail band starts at, so the view rows the plan
|
|
214
|
+
* reports are frame rows a reported cell is compared against directly. It is
|
|
215
|
+
* required for that reason: a default here would answer rail-relative rows in a
|
|
216
|
+
* field documented as frame rows — the same defaulted-measurement defect the key
|
|
217
|
+
* delivery carried, and this one would land in the rows a rail hit resolves
|
|
218
|
+
* through.
|
|
160
219
|
*/
|
|
161
|
-
export declare function planSidebar(railRows: number): SidebarPlan;
|
|
220
|
+
export declare function planSidebar(railRows: number, railRow: number): SidebarPlan;
|
|
162
221
|
export interface FrameRegion {
|
|
163
222
|
id: FrameRegionId;
|
|
164
223
|
row: number;
|
|
@@ -182,8 +241,11 @@ export interface PlannedFrame {
|
|
|
182
241
|
content: FrameBandContent;
|
|
183
242
|
regions: readonly FrameRegion[];
|
|
184
243
|
/**
|
|
185
|
-
* Row bands, top to bottom
|
|
186
|
-
*
|
|
244
|
+
* Row bands, top to bottom. `items` is nested inside the body band and so
|
|
245
|
+
* refines it rather than tiling beside it (see FRAME_NESTED_ROW_BANDS); the
|
|
246
|
+
* remaining spans tile the height exactly. The caption is excluded: it adds
|
|
247
|
+
* no row extent of its own, being the header's own row refined on the column
|
|
248
|
+
* axis, and the column map already carries no row.
|
|
187
249
|
*/
|
|
188
250
|
rowSpans: Readonly<Record<string, number>>;
|
|
189
251
|
/**
|
|
@@ -217,7 +279,9 @@ export type FramePlan = PlannedFrame | PlainFallbackPlan;
|
|
|
217
279
|
* height exactly — header, rule, the view strip (40–63 columns), the body
|
|
218
280
|
* band, a second rule, the journal tail surrendered whole by height tier,
|
|
219
281
|
* status, keybar — and, at ≥64 columns, column bands that tile the width
|
|
220
|
-
* exactly: the
|
|
282
|
+
* exactly: the rail beside the body, at the constant 15 columns or at the
|
|
283
|
+
* session's dragged override clamped to both panels' readable floors
|
|
284
|
+
* (`planRailColumns`). The body is the sole
|
|
221
285
|
* flexible element on both axes. The view and state decide what the flexible
|
|
222
286
|
* bands draw (the Journal view is the full-height tail; SETUP turns the tail
|
|
223
287
|
* into PENDING WRITES; an open detail owns the body) without moving any
|
|
@@ -231,7 +295,12 @@ export type FramePlan = PlannedFrame | PlainFallbackPlan;
|
|
|
231
295
|
* of the block, the meter last, rather than closing the gap. rowSpans and
|
|
232
296
|
* columnSpans are band maps: they deliberately exclude the nested caption,
|
|
233
297
|
* and column bands are reported for the body band only, so each map's spans
|
|
234
|
-
* sum to the full height and width respectively.
|
|
298
|
+
* sum to the full height and width respectively. The body band's item rows are
|
|
299
|
+
* a band of the plan in their own right — a region in `regions` and a span in
|
|
300
|
+
* `rowSpans`, nested inside the body band rather than tiling beside it — so the
|
|
301
|
+
* region the paint draws its list into and the region a pointer resolves a row
|
|
302
|
+
* through are the one thing the oracle pins, and neither subtracts the drawn
|
|
303
|
+
* panel's chrome for itself.
|
|
235
304
|
*/
|
|
236
305
|
export declare function planFrame(size: MeasuredSize, view?: FrameView, state?: FrameState): FramePlan;
|
|
237
306
|
export declare function resolveCockpitLayout(columns: number, rows: number): CockpitLayout;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PANEL_CHROME_ROWS, READABLE_PANEL_COLUMNS } from "./components.js";
|
|
1
2
|
import { cellWidth } from "./width.js";
|
|
2
3
|
export const COCKPIT_ROW_FLOOR = 14;
|
|
3
4
|
export const COCKPIT_COLUMN_FLOOR = 40;
|
|
@@ -57,6 +58,30 @@ export const FRAME_WIDTH_FLOOR = COCKPIT_COLUMN_FLOOR;
|
|
|
57
58
|
export const FRAME_HEIGHT_FLOOR = COCKPIT_ROW_FLOOR;
|
|
58
59
|
export const SIDEBAR_COLUMN_FLOOR = 64;
|
|
59
60
|
export const RAIL_COLUMNS = 15;
|
|
61
|
+
/**
|
|
62
|
+
* The narrowest rail that still reads: the selection marker, one space and the
|
|
63
|
+
* longest view name, whole — the name measured in display cells by the width
|
|
64
|
+
* authority, never String length, so a name that ever gains a wide glyph moves
|
|
65
|
+
* the floor with it. A drag never takes the rail below it — below the floor
|
|
66
|
+
* the element stands at the floor, surrendered whole per the contract, never
|
|
67
|
+
* collapsed.
|
|
68
|
+
*/
|
|
69
|
+
export const RAIL_READABLE_FLOOR_COLUMNS = Math.max(...FRAME_VIEWS.map((view) => cellWidth(view))) + 2;
|
|
70
|
+
/**
|
|
71
|
+
* The rail width the plan draws for a session resize override, clamped at both
|
|
72
|
+
* panels' readable floors: the rail never narrows past its own floor, and
|
|
73
|
+
* never widens past what leaves the body its readable floor
|
|
74
|
+
* (`READABLE_PANEL_COLUMNS`). The clamp is the plan's — the override is a
|
|
75
|
+
* request, the floors are the contract, and an element is surrendered whole or
|
|
76
|
+
* not at all, never collapsed by a drag. No override is the contract's
|
|
77
|
+
* constant rail.
|
|
78
|
+
*/
|
|
79
|
+
export function planRailColumns(columns, override) {
|
|
80
|
+
if (override === undefined || !Number.isFinite(override))
|
|
81
|
+
return RAIL_COLUMNS;
|
|
82
|
+
const ceiling = Math.max(RAIL_READABLE_FLOOR_COLUMNS, Math.floor(columns) - READABLE_PANEL_COLUMNS);
|
|
83
|
+
return Math.min(ceiling, Math.max(RAIL_READABLE_FLOOR_COLUMNS, Math.floor(override)));
|
|
84
|
+
}
|
|
60
85
|
export const STANDARD_TAIL_HEIGHT = 16;
|
|
61
86
|
export const FULL_TAIL_HEIGHT = 24;
|
|
62
87
|
export const TAIL_STANDARD_ROWS = 1;
|
|
@@ -93,6 +118,23 @@ export const FRAME_SURRENDER_FIT = {
|
|
|
93
118
|
caption: ({ captionCells, columns }) => captionCells >= 1 && captionCells <= columns - HEADER_FIXED_CELLS,
|
|
94
119
|
tail: ({ tailRows }) => tailRows > 0,
|
|
95
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* Border plus horizontal padding the drawn panel charges around its body —
|
|
123
|
+
* half of it on each side. The plan owns this because the plan owns the item
|
|
124
|
+
* rows: the paint draws its list into the region planned below, and nothing
|
|
125
|
+
* downstream subtracts chrome of its own to find it again.
|
|
126
|
+
*/
|
|
127
|
+
export const RUN_PANEL_HORIZONTAL_CHROME = 4;
|
|
128
|
+
/**
|
|
129
|
+
* Row bands drawn INSIDE another band rather than beside it, mapped to the band
|
|
130
|
+
* that hosts them. A nested band refines its host's rows, so the band walk skips
|
|
131
|
+
* it when tiling the height and asserts containment instead. It is no less a
|
|
132
|
+
* member of `rowSpans` for that: the oracle pins its span against the planned
|
|
133
|
+
* region, and demands the paint register a node at exactly that rectangle —
|
|
134
|
+
* which is precisely what a parallel field beside the plan could never be held
|
|
135
|
+
* to.
|
|
136
|
+
*/
|
|
137
|
+
export const FRAME_NESTED_ROW_BANDS = { items: "body" };
|
|
96
138
|
/* ------------------------------------------------------------------ */
|
|
97
139
|
/* The sidebar band's composition: the menu at the rail's top, the */
|
|
98
140
|
/* vitals anchored to the rail's bottom, and at least one blank row */
|
|
@@ -113,17 +155,28 @@ export const SIDEBAR_VITALS_COMPACT_ELEMENT_ROWS = 1;
|
|
|
113
155
|
* one gap row is the vitals budget, spent on the full two-row block when it
|
|
114
156
|
* fits whole, else on the one-row elements, surrendered from the top of the
|
|
115
157
|
* block with the meter surrendered last. An element draws whole or not at all.
|
|
158
|
+
* `railRow` is the frame row the rail band starts at, so the view rows the plan
|
|
159
|
+
* reports are frame rows a reported cell is compared against directly. It is
|
|
160
|
+
* required for that reason: a default here would answer rail-relative rows in a
|
|
161
|
+
* field documented as frame rows — the same defaulted-measurement defect the key
|
|
162
|
+
* delivery carried, and this one would land in the rows a rail hit resolves
|
|
163
|
+
* through.
|
|
116
164
|
*/
|
|
117
|
-
export function planSidebar(railRows) {
|
|
165
|
+
export function planSidebar(railRows, railRow) {
|
|
118
166
|
const menuRows = railRows >= SIDEBAR_MENU_LABEL_ROWS
|
|
119
167
|
? 1 + FRAME_VIEWS.length
|
|
120
168
|
: FRAME_VIEWS.length;
|
|
169
|
+
// The views occupy the menu block's last rows: whatever the block exceeds
|
|
170
|
+
// them by is the label row the plan funded above them.
|
|
171
|
+
const firstViewRow = railRow + (menuRows - FRAME_VIEWS.length);
|
|
172
|
+
const viewRows = Object.fromEntries(FRAME_VIEWS.map((view, index) => [view, firstViewRow + index]));
|
|
121
173
|
const fullRows = SIDEBAR_VITALS_ORDER.length * SIDEBAR_VITALS_FULL_ELEMENT_ROWS;
|
|
122
174
|
// The gap is funded before the vitals are: the budget is what remains after it.
|
|
123
175
|
const budget = railRows - menuRows - 1;
|
|
124
176
|
if (budget >= fullRows) {
|
|
125
177
|
return {
|
|
126
178
|
menuRows,
|
|
179
|
+
viewRows,
|
|
127
180
|
gapRows: railRows - menuRows - fullRows,
|
|
128
181
|
vitalsMode: "full",
|
|
129
182
|
vitals: SIDEBAR_VITALS_ORDER,
|
|
@@ -132,11 +185,29 @@ export function planSidebar(railRows) {
|
|
|
132
185
|
const kept = Math.max(0, Math.min(SIDEBAR_VITALS_ORDER.length, Math.floor(budget)));
|
|
133
186
|
return {
|
|
134
187
|
menuRows,
|
|
188
|
+
viewRows,
|
|
135
189
|
gapRows: kept > 0 ? railRows - menuRows - kept : 0,
|
|
136
190
|
vitalsMode: "compact",
|
|
137
191
|
vitals: SIDEBAR_VITALS_ORDER.slice(SIDEBAR_VITALS_ORDER.length - kept),
|
|
138
192
|
};
|
|
139
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* The item rows of a body band: the band less the chrome the drawn panel puts
|
|
196
|
+
* around its list — the border row and the title row above the first item, the
|
|
197
|
+
* border row below it, and a bordered padded column on each side. THE ONE
|
|
198
|
+
* PLACE that subtraction happens, and it happens in the plan: paint and hit
|
|
199
|
+
* resolution alike consume this region rather than deriving it again.
|
|
200
|
+
*/
|
|
201
|
+
function planItemRows(body) {
|
|
202
|
+
const heading = PANEL_CHROME_ROWS - 1; // border row, then the title row
|
|
203
|
+
return {
|
|
204
|
+
id: "items",
|
|
205
|
+
row: body.row + heading,
|
|
206
|
+
rows: Math.max(1, body.rows - PANEL_CHROME_ROWS),
|
|
207
|
+
column: body.column + RUN_PANEL_HORIZONTAL_CHROME / 2,
|
|
208
|
+
columns: Math.max(1, body.columns - RUN_PANEL_HORIZONTAL_CHROME),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
140
211
|
function tailRowsFor(rows) {
|
|
141
212
|
if (rows >= FULL_TAIL_HEIGHT)
|
|
142
213
|
return TAIL_FULL_ROWS;
|
|
@@ -151,7 +222,9 @@ function tailRowsFor(rows) {
|
|
|
151
222
|
* height exactly — header, rule, the view strip (40–63 columns), the body
|
|
152
223
|
* band, a second rule, the journal tail surrendered whole by height tier,
|
|
153
224
|
* status, keybar — and, at ≥64 columns, column bands that tile the width
|
|
154
|
-
* exactly: the
|
|
225
|
+
* exactly: the rail beside the body, at the constant 15 columns or at the
|
|
226
|
+
* session's dragged override clamped to both panels' readable floors
|
|
227
|
+
* (`planRailColumns`). The body is the sole
|
|
155
228
|
* flexible element on both axes. The view and state decide what the flexible
|
|
156
229
|
* bands draw (the Journal view is the full-height tail; SETUP turns the tail
|
|
157
230
|
* into PENDING WRITES; an open detail owns the body) without moving any
|
|
@@ -165,7 +238,12 @@ function tailRowsFor(rows) {
|
|
|
165
238
|
* of the block, the meter last, rather than closing the gap. rowSpans and
|
|
166
239
|
* columnSpans are band maps: they deliberately exclude the nested caption,
|
|
167
240
|
* and column bands are reported for the body band only, so each map's spans
|
|
168
|
-
* sum to the full height and width respectively.
|
|
241
|
+
* sum to the full height and width respectively. The body band's item rows are
|
|
242
|
+
* a band of the plan in their own right — a region in `regions` and a span in
|
|
243
|
+
* `rowSpans`, nested inside the body band rather than tiling beside it — so the
|
|
244
|
+
* region the paint draws its list into and the region a pointer resolves a row
|
|
245
|
+
* through are the one thing the oracle pins, and neither subtracts the drawn
|
|
246
|
+
* panel's chrome for itself.
|
|
169
247
|
*/
|
|
170
248
|
export function planFrame(size, view = "tasks", state = {}) {
|
|
171
249
|
const columns = Math.floor(size.columns);
|
|
@@ -178,6 +256,9 @@ export function planFrame(size, view = "tasks", state = {}) {
|
|
|
178
256
|
}
|
|
179
257
|
const tab = state.tab ?? "watch";
|
|
180
258
|
const band = columns >= SIDEBAR_COLUMN_FLOOR ? "sidebar" : "strip";
|
|
259
|
+
// The one panel the session can resize: the rail, at the operator's dragged
|
|
260
|
+
// override clamped to the readable floors, or the contract's constant rail.
|
|
261
|
+
const railColumns = planRailColumns(columns, state.railColumns);
|
|
181
262
|
const stripRows = band === "strip" ? 1 : 0;
|
|
182
263
|
const tailRows = tailRowsFor(rows);
|
|
183
264
|
// Fixed chrome is five rows: header, rule, a second rule below the body
|
|
@@ -221,25 +302,21 @@ export function planFrame(size, view = "tasks", state = {}) {
|
|
|
221
302
|
row: bodyRow,
|
|
222
303
|
rows: bodyRows,
|
|
223
304
|
column: 0,
|
|
224
|
-
columns:
|
|
225
|
-
});
|
|
226
|
-
regions.push({
|
|
227
|
-
id: "body",
|
|
228
|
-
row: bodyRow,
|
|
229
|
-
rows: bodyRows,
|
|
230
|
-
column: RAIL_COLUMNS,
|
|
231
|
-
columns: columns - RAIL_COLUMNS,
|
|
305
|
+
columns: railColumns,
|
|
232
306
|
});
|
|
233
307
|
}
|
|
234
|
-
|
|
235
|
-
|
|
308
|
+
const bodyRegion = band === "sidebar"
|
|
309
|
+
? {
|
|
236
310
|
id: "body",
|
|
237
311
|
row: bodyRow,
|
|
238
312
|
rows: bodyRows,
|
|
239
|
-
column:
|
|
240
|
-
columns,
|
|
241
|
-
}
|
|
242
|
-
|
|
313
|
+
column: railColumns,
|
|
314
|
+
columns: columns - railColumns,
|
|
315
|
+
}
|
|
316
|
+
: { id: "body", row: bodyRow, rows: bodyRows, column: 0, columns };
|
|
317
|
+
regions.push(bodyRegion);
|
|
318
|
+
const itemsRegion = planItemRows(bodyRegion);
|
|
319
|
+
regions.push(itemsRegion);
|
|
243
320
|
if (captionFits) {
|
|
244
321
|
regions.push({
|
|
245
322
|
id: "caption",
|
|
@@ -253,13 +330,14 @@ export function planFrame(size, view = "tasks", state = {}) {
|
|
|
253
330
|
if (stripRows > 0)
|
|
254
331
|
rowSpans.strip = stripRows;
|
|
255
332
|
rowSpans.body = bodyRows;
|
|
333
|
+
rowSpans.items = itemsRegion.rows;
|
|
256
334
|
rowSpans.rule2 = 1;
|
|
257
335
|
if (tailRows > 0)
|
|
258
336
|
rowSpans.tail = tailRows;
|
|
259
337
|
rowSpans.status = 1;
|
|
260
338
|
rowSpans.keybar = 1;
|
|
261
339
|
const columnSpans = band === "sidebar"
|
|
262
|
-
? { rail:
|
|
340
|
+
? { rail: railColumns, body: columns - railColumns }
|
|
263
341
|
: { body: columns };
|
|
264
342
|
return {
|
|
265
343
|
kind: "frame",
|
|
@@ -275,7 +353,7 @@ export function planFrame(size, view = "tasks", state = {}) {
|
|
|
275
353
|
rowSpans,
|
|
276
354
|
columnSpans,
|
|
277
355
|
flexible: { row: "body", column: "body" },
|
|
278
|
-
sidebar: band === "sidebar" ? planSidebar(bodyRows) : null,
|
|
356
|
+
sidebar: band === "sidebar" ? planSidebar(bodyRows, bodyRow) : null,
|
|
279
357
|
surrendered,
|
|
280
358
|
};
|
|
281
359
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CockpitGraph, RunCockpitData, RunCockpitSource } from "./derive.js";
|
|
2
2
|
import { type RunInteractionState, type RunKeyEvent } from "./keys.js";
|
|
3
|
+
import { type PointerReport, type PointerSurface } from "./pointer.js";
|
|
3
4
|
/**
|
|
4
5
|
* THE engagement selection rule — stated once, here, so it can change in one
|
|
5
6
|
* place without touching the renderer or the command: an explicit engagement
|
|
@@ -46,6 +47,17 @@ export declare function reconcileLiveRunInteraction(interaction: RunInteractionS
|
|
|
46
47
|
* one place rows enter the dispatcher, so no key can reach a journal row.
|
|
47
48
|
*/
|
|
48
49
|
export declare function liveRunViewRowIds(interaction: RunInteractionState, data: RunCockpitData): readonly string[];
|
|
50
|
+
/**
|
|
51
|
+
* The suite's own planning of the surface a frame at this data, state and
|
|
52
|
+
* measured size commits — the same composition the renderer publishes through
|
|
53
|
+
* its commit seam, so a hit is tested against the frame production actually
|
|
54
|
+
* painted. The live input path never calls this: it resolves through the
|
|
55
|
+
* committed frame, and what nothing has painted yet has no geometry.
|
|
56
|
+
*/
|
|
57
|
+
export declare function liveRunPointerSurface(data: RunCockpitData, interaction: RunInteractionState, size: {
|
|
58
|
+
readonly columns: number;
|
|
59
|
+
readonly rows: number;
|
|
60
|
+
}): PointerSurface | undefined;
|
|
49
61
|
export type LiveCockpitSurface = {
|
|
50
62
|
readonly data: RunCockpitData;
|
|
51
63
|
readonly interaction: RunInteractionState;
|
|
@@ -62,7 +74,8 @@ export type LiveCockpitSurface = {
|
|
|
62
74
|
export type LiveCockpitDelivery = {
|
|
63
75
|
readonly snapshot: () => LiveCockpitSurface;
|
|
64
76
|
readonly refresh: () => boolean;
|
|
65
|
-
readonly key: (event: RunKeyEvent
|
|
77
|
+
readonly key: (event: RunKeyEvent) => boolean;
|
|
78
|
+
readonly pointer: (report: PointerReport) => boolean;
|
|
66
79
|
readonly batch: <Result>(deliver: () => Result) => Result;
|
|
67
80
|
};
|
|
68
81
|
export declare function runLiveCockpit({ input, output, cwd, runId, binaryVersion, refreshMs, now, debug, onDelivery, }: {
|