@git-stunts/git-warp 10.1.2 → 10.4.2
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 +31 -4
- package/bin/warp-graph.js +1242 -59
- package/index.d.ts +31 -0
- package/index.js +4 -0
- package/package.json +13 -3
- package/src/domain/WarpGraph.js +487 -140
- package/src/domain/crdt/LWW.js +1 -1
- package/src/domain/crdt/ORSet.js +10 -6
- package/src/domain/crdt/VersionVector.js +5 -1
- package/src/domain/errors/EmptyMessageError.js +2 -4
- package/src/domain/errors/ForkError.js +4 -0
- package/src/domain/errors/IndexError.js +4 -0
- package/src/domain/errors/OperationAbortedError.js +4 -0
- package/src/domain/errors/QueryError.js +4 -0
- package/src/domain/errors/SchemaUnsupportedError.js +4 -0
- package/src/domain/errors/ShardCorruptionError.js +2 -6
- package/src/domain/errors/ShardLoadError.js +2 -6
- package/src/domain/errors/ShardValidationError.js +2 -7
- package/src/domain/errors/StorageError.js +2 -6
- package/src/domain/errors/SyncError.js +4 -0
- package/src/domain/errors/TraversalError.js +4 -0
- package/src/domain/errors/WarpError.js +2 -4
- package/src/domain/errors/WormholeError.js +4 -0
- package/src/domain/services/AnchorMessageCodec.js +1 -4
- package/src/domain/services/BitmapIndexBuilder.js +10 -6
- package/src/domain/services/BitmapIndexReader.js +27 -21
- package/src/domain/services/BoundaryTransitionRecord.js +22 -15
- package/src/domain/services/CheckpointMessageCodec.js +1 -7
- package/src/domain/services/CheckpointSerializerV5.js +20 -19
- package/src/domain/services/CheckpointService.js +18 -18
- package/src/domain/services/CommitDagTraversalService.js +13 -1
- package/src/domain/services/DagPathFinding.js +40 -18
- package/src/domain/services/DagTopology.js +7 -6
- package/src/domain/services/DagTraversal.js +5 -3
- package/src/domain/services/Frontier.js +7 -6
- package/src/domain/services/HealthCheckService.js +15 -14
- package/src/domain/services/HookInstaller.js +64 -13
- package/src/domain/services/HttpSyncServer.js +15 -14
- package/src/domain/services/IndexRebuildService.js +12 -12
- package/src/domain/services/IndexStalenessChecker.js +13 -6
- package/src/domain/services/JoinReducer.js +28 -27
- package/src/domain/services/LogicalTraversal.js +7 -6
- package/src/domain/services/MessageCodecInternal.js +2 -0
- package/src/domain/services/ObserverView.js +6 -6
- package/src/domain/services/PatchBuilderV2.js +9 -9
- package/src/domain/services/PatchMessageCodec.js +1 -7
- package/src/domain/services/ProvenanceIndex.js +6 -8
- package/src/domain/services/ProvenancePayload.js +1 -2
- package/src/domain/services/QueryBuilder.js +29 -23
- package/src/domain/services/StateDiff.js +7 -7
- package/src/domain/services/StateSerializerV5.js +8 -6
- package/src/domain/services/StreamingBitmapIndexBuilder.js +29 -23
- package/src/domain/services/SyncProtocol.js +23 -26
- package/src/domain/services/TemporalQuery.js +4 -3
- package/src/domain/services/TranslationCost.js +4 -4
- package/src/domain/services/WormholeService.js +19 -15
- package/src/domain/types/TickReceipt.js +10 -6
- package/src/domain/types/WarpTypesV2.js +2 -3
- package/src/domain/utils/CachedValue.js +1 -1
- package/src/domain/utils/LRUCache.js +3 -3
- package/src/domain/utils/MinHeap.js +2 -2
- package/src/domain/utils/RefLayout.js +106 -15
- package/src/domain/utils/WriterId.js +2 -2
- package/src/domain/utils/defaultCodec.js +9 -2
- package/src/domain/utils/defaultCrypto.js +36 -0
- package/src/domain/utils/parseCursorBlob.js +51 -0
- package/src/domain/utils/roaring.js +5 -5
- package/src/domain/utils/seekCacheKey.js +32 -0
- package/src/domain/warp/PatchSession.js +3 -3
- package/src/domain/warp/Writer.js +2 -2
- package/src/infrastructure/adapters/BunHttpAdapter.js +21 -8
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +311 -0
- package/src/infrastructure/adapters/ClockAdapter.js +2 -2
- package/src/infrastructure/adapters/DenoHttpAdapter.js +22 -9
- package/src/infrastructure/adapters/GitGraphAdapter.js +16 -27
- package/src/infrastructure/adapters/NodeCryptoAdapter.js +16 -3
- package/src/infrastructure/adapters/NodeHttpAdapter.js +33 -11
- package/src/infrastructure/adapters/WebCryptoAdapter.js +21 -11
- package/src/infrastructure/codecs/CborCodec.js +16 -8
- package/src/ports/BlobPort.js +2 -2
- package/src/ports/CodecPort.js +2 -2
- package/src/ports/CommitPort.js +8 -21
- package/src/ports/ConfigPort.js +3 -3
- package/src/ports/CryptoPort.js +7 -7
- package/src/ports/GraphPersistencePort.js +12 -14
- package/src/ports/HttpServerPort.js +1 -5
- package/src/ports/IndexStoragePort.js +1 -0
- package/src/ports/LoggerPort.js +9 -9
- package/src/ports/RefPort.js +5 -5
- package/src/ports/SeekCachePort.js +73 -0
- package/src/ports/TreePort.js +3 -3
- package/src/visualization/layouts/converters.js +14 -7
- package/src/visualization/layouts/elkAdapter.js +24 -11
- package/src/visualization/layouts/elkLayout.js +23 -7
- package/src/visualization/layouts/index.js +3 -3
- package/src/visualization/renderers/ascii/check.js +30 -17
- package/src/visualization/renderers/ascii/graph.js +122 -16
- package/src/visualization/renderers/ascii/history.js +29 -90
- package/src/visualization/renderers/ascii/index.js +1 -1
- package/src/visualization/renderers/ascii/info.js +9 -7
- package/src/visualization/renderers/ascii/materialize.js +20 -16
- package/src/visualization/renderers/ascii/opSummary.js +81 -0
- package/src/visualization/renderers/ascii/path.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +344 -0
- package/src/visualization/renderers/ascii/table.js +1 -1
- package/src/visualization/renderers/svg/index.js +5 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII renderer for the `seek --view` command.
|
|
3
|
+
*
|
|
4
|
+
* Displays a swimlane dashboard: one horizontal track per writer, with
|
|
5
|
+
* relative-offset column headers that map directly to `--tick=+N/-N` CLI
|
|
6
|
+
* syntax. Included patches (at or before the cursor) render as filled
|
|
7
|
+
* dots on a solid line; excluded (future) patches render as open circles
|
|
8
|
+
* on a dotted line.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import boxen from 'boxen';
|
|
12
|
+
import { colors } from './colors.js';
|
|
13
|
+
import { padRight } from '../../utils/unicode.js';
|
|
14
|
+
import { formatSha, formatWriterName } from './formatters.js';
|
|
15
|
+
import { TIMELINE } from './symbols.js';
|
|
16
|
+
import { formatOpSummary } from './opSummary.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{ ticks: number[], tipSha?: string, tickShas?: Record<number, string> }} WriterInfo
|
|
20
|
+
* @typedef {{ graph: string, tick: number, maxTick: number, ticks: number[], nodes: number, edges: number, patchCount: number, perWriter: Map<string, WriterInfo> | Record<string, WriterInfo>, diff?: { nodes?: number, edges?: number }, tickReceipt?: Record<string, any> }} SeekPayload
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Maximum number of tick columns shown in the windowed view. */
|
|
24
|
+
const MAX_COLS = 9;
|
|
25
|
+
|
|
26
|
+
/** Character width of each tick column (marker + connector gap). */
|
|
27
|
+
const COL_W = 6;
|
|
28
|
+
|
|
29
|
+
/** Character width reserved for the writer name column. */
|
|
30
|
+
const NAME_W = 10;
|
|
31
|
+
|
|
32
|
+
/** Middle-dot used for excluded-zone connectors. */
|
|
33
|
+
const DOT_MID = '\u00B7'; // ·
|
|
34
|
+
|
|
35
|
+
/** Open circle used for excluded-zone patch markers. */
|
|
36
|
+
const CIRCLE_OPEN = '\u25CB'; // ○
|
|
37
|
+
|
|
38
|
+
/** @param {number} n @returns {string} */
|
|
39
|
+
function formatDelta(n) {
|
|
40
|
+
if (typeof n !== 'number' || !Number.isFinite(n) || n === 0) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
const sign = n > 0 ? '+' : '';
|
|
44
|
+
return ` (${sign}${n})`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {number} n
|
|
49
|
+
* @param {string} singular
|
|
50
|
+
* @param {string} plural
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
function pluralize(n, singular, plural) {
|
|
54
|
+
return n === 1 ? singular : plural;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @param {Record<string, any> | undefined} tickReceipt @returns {string[]} */
|
|
58
|
+
function buildReceiptLines(tickReceipt) {
|
|
59
|
+
if (!tickReceipt || typeof tickReceipt !== 'object') {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const entries = Object.entries(tickReceipt)
|
|
64
|
+
.filter(([writerId, entry]) => writerId && entry && typeof entry === 'object')
|
|
65
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
66
|
+
|
|
67
|
+
const lines = [];
|
|
68
|
+
for (const [writerId, entry] of entries) {
|
|
69
|
+
const sha = typeof entry.sha === 'string' ? entry.sha : null;
|
|
70
|
+
const opSummary = entry.opSummary && typeof entry.opSummary === 'object' ? entry.opSummary : entry;
|
|
71
|
+
const name = padRight(formatWriterName(writerId, NAME_W), NAME_W);
|
|
72
|
+
const shaStr = sha ? ` ${formatSha(sha)}` : '';
|
|
73
|
+
lines.push(` ${name}${shaStr} ${formatOpSummary(opSummary, 40)}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return lines;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Window
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Computes a sliding window of tick positions centered on the current tick.
|
|
85
|
+
*
|
|
86
|
+
* When all points fit within {@link MAX_COLS}, the full array is returned.
|
|
87
|
+
* Otherwise a window of MAX_COLS entries is centered on `currentIdx`, with
|
|
88
|
+
* clamping at both ends.
|
|
89
|
+
*
|
|
90
|
+
* @param {number[]} allPoints - All tick positions (including virtual tick 0)
|
|
91
|
+
* @param {number} currentIdx - Index of the current tick in `allPoints`
|
|
92
|
+
* @returns {{ points: number[], currentCol: number, moreLeft: boolean, moreRight: boolean }}
|
|
93
|
+
*/
|
|
94
|
+
function computeWindow(allPoints, currentIdx) {
|
|
95
|
+
if (allPoints.length <= MAX_COLS) {
|
|
96
|
+
return {
|
|
97
|
+
points: allPoints,
|
|
98
|
+
currentCol: currentIdx,
|
|
99
|
+
moreLeft: false,
|
|
100
|
+
moreRight: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const half = Math.floor(MAX_COLS / 2);
|
|
105
|
+
let start = currentIdx - half;
|
|
106
|
+
if (start < 0) {
|
|
107
|
+
start = 0;
|
|
108
|
+
}
|
|
109
|
+
let end = start + MAX_COLS;
|
|
110
|
+
if (end > allPoints.length) {
|
|
111
|
+
end = allPoints.length;
|
|
112
|
+
start = end - MAX_COLS;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
points: allPoints.slice(start, end),
|
|
117
|
+
currentCol: currentIdx - start,
|
|
118
|
+
moreLeft: start > 0,
|
|
119
|
+
moreRight: end < allPoints.length,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// Header row
|
|
125
|
+
// ============================================================================
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Builds the column header row showing relative step offsets.
|
|
129
|
+
*
|
|
130
|
+
* The current tick is rendered as `[N]` (absolute tick number); all other
|
|
131
|
+
* columns show their signed step distance (`-2`, `-1`, `+1`, `+2`, etc.)
|
|
132
|
+
* matching the `--tick=+N/-N` CLI syntax.
|
|
133
|
+
*
|
|
134
|
+
* @param {{ points: number[], currentCol: number }} win - Computed window
|
|
135
|
+
* @returns {string} Formatted, indented header line
|
|
136
|
+
*/
|
|
137
|
+
function buildHeaderRow(win) {
|
|
138
|
+
const { points, currentCol } = win;
|
|
139
|
+
let header = '';
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < points.length; i++) {
|
|
142
|
+
const rel = i - currentCol;
|
|
143
|
+
let label;
|
|
144
|
+
if (rel === 0) {
|
|
145
|
+
label = `[${points[i]}]`;
|
|
146
|
+
} else if (rel > 0) {
|
|
147
|
+
label = `+${rel}`;
|
|
148
|
+
} else {
|
|
149
|
+
label = String(rel);
|
|
150
|
+
}
|
|
151
|
+
header += label.padEnd(COL_W);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const margin = ' '.repeat(NAME_W + 2);
|
|
155
|
+
return ` ${margin}${header.trimEnd()}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Writer swimlane
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Renders a single cell (marker) in the swimlane grid.
|
|
164
|
+
*
|
|
165
|
+
* @param {boolean} hasPatch - Whether this writer has a patch at this tick
|
|
166
|
+
* @param {boolean} incl - Whether this tick is in the included zone
|
|
167
|
+
* @returns {string} A single styled character
|
|
168
|
+
*/
|
|
169
|
+
function renderCell(hasPatch, incl) {
|
|
170
|
+
if (hasPatch) {
|
|
171
|
+
return incl ? colors.success(TIMELINE.dot) : colors.muted(CIRCLE_OPEN);
|
|
172
|
+
}
|
|
173
|
+
return incl ? TIMELINE.line : colors.muted(DOT_MID);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Builds the swimlane track string for a writer across the window columns.
|
|
178
|
+
*
|
|
179
|
+
* @param {Set<number>} patchSet - Set of ticks where this writer has patches
|
|
180
|
+
* @param {number[]} points - Window tick positions
|
|
181
|
+
* @param {number} currentTick - Active seek cursor tick
|
|
182
|
+
* @returns {string} Styled swimlane track
|
|
183
|
+
*/
|
|
184
|
+
function buildLane(patchSet, points, currentTick) {
|
|
185
|
+
let lane = '';
|
|
186
|
+
for (let i = 0; i < points.length; i++) {
|
|
187
|
+
const t = points[i];
|
|
188
|
+
const incl = t <= currentTick;
|
|
189
|
+
|
|
190
|
+
if (i > 0) {
|
|
191
|
+
const n = COL_W - 1;
|
|
192
|
+
lane += incl
|
|
193
|
+
? TIMELINE.line.repeat(n)
|
|
194
|
+
: colors.muted(DOT_MID.repeat(n));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
lane += renderCell(patchSet.has(t), incl);
|
|
198
|
+
}
|
|
199
|
+
return lane;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Builds one writer's horizontal swimlane row.
|
|
204
|
+
*
|
|
205
|
+
* Each tick position in the window gets a marker character:
|
|
206
|
+
* - `●` (green) — writer has a patch here AND tick ≤ currentTick (included)
|
|
207
|
+
* - `○` (muted) — writer has a patch here AND tick > currentTick (excluded)
|
|
208
|
+
* - `─` (solid) — no patch, included zone
|
|
209
|
+
* - `·` (muted) — no patch, excluded zone
|
|
210
|
+
*
|
|
211
|
+
* Between consecutive columns, connector characters of the appropriate style
|
|
212
|
+
* fill the gap (COL_W − 1 chars).
|
|
213
|
+
*
|
|
214
|
+
* @param {Object} opts
|
|
215
|
+
* @param {string} opts.writerId
|
|
216
|
+
* @param {WriterInfo} opts.writerInfo - `{ ticks, tipSha, tickShas }`
|
|
217
|
+
* @param {{ points: number[] }} opts.win - Computed window
|
|
218
|
+
* @param {number} opts.currentTick - Active seek cursor tick
|
|
219
|
+
* @returns {string} Formatted, indented swimlane line
|
|
220
|
+
*/
|
|
221
|
+
function buildWriterSwimRow({ writerId, writerInfo, win, currentTick }) {
|
|
222
|
+
const patchSet = new Set(writerInfo.ticks);
|
|
223
|
+
const tickShas = writerInfo.tickShas || {};
|
|
224
|
+
const lane = buildLane(patchSet, win.points, currentTick);
|
|
225
|
+
|
|
226
|
+
// SHA of the highest included patch
|
|
227
|
+
const included = writerInfo.ticks.filter((t) => t <= currentTick);
|
|
228
|
+
const maxIncl = included.length > 0 ? included[included.length - 1] : null;
|
|
229
|
+
const sha = maxIncl !== null
|
|
230
|
+
? (tickShas[maxIncl] || writerInfo.tipSha)
|
|
231
|
+
: writerInfo.tipSha;
|
|
232
|
+
|
|
233
|
+
const name = padRight(formatWriterName(writerId, NAME_W), NAME_W);
|
|
234
|
+
const shaStr = sha ? ` ${formatSha(sha)}` : '';
|
|
235
|
+
|
|
236
|
+
return ` ${name} ${lane}${shaStr}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// Body assembly
|
|
241
|
+
// ============================================================================
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Builds the tick-position array and index of the current tick.
|
|
245
|
+
*
|
|
246
|
+
* Ensures the current tick is always present: if `tick` is absent from
|
|
247
|
+
* `ticks` (e.g. saved cursor after writer refs changed), it is inserted
|
|
248
|
+
* at the correct sorted position so the window always centres on it.
|
|
249
|
+
*
|
|
250
|
+
* @param {number[]} ticks - Discovered Lamport ticks
|
|
251
|
+
* @param {number} tick - Current cursor tick
|
|
252
|
+
* @returns {{ allPoints: number[], currentIdx: number }}
|
|
253
|
+
*/
|
|
254
|
+
function buildTickPoints(ticks, tick) {
|
|
255
|
+
const allPoints = (ticks[0] === 0) ? [...ticks] : [0, ...ticks];
|
|
256
|
+
let currentIdx = allPoints.indexOf(tick);
|
|
257
|
+
if (currentIdx === -1) {
|
|
258
|
+
let ins = allPoints.findIndex((t) => t > tick);
|
|
259
|
+
if (ins === -1) {
|
|
260
|
+
ins = allPoints.length;
|
|
261
|
+
}
|
|
262
|
+
allPoints.splice(ins, 0, tick);
|
|
263
|
+
currentIdx = ins;
|
|
264
|
+
}
|
|
265
|
+
return { allPoints, currentIdx };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Builds the body lines for the seek dashboard.
|
|
270
|
+
*
|
|
271
|
+
* @param {SeekPayload} payload - Seek payload from the CLI handler
|
|
272
|
+
* @returns {string[]} Lines for the box body
|
|
273
|
+
*/
|
|
274
|
+
function buildSeekBodyLines(payload) {
|
|
275
|
+
const { graph, tick, maxTick, ticks, nodes, edges, patchCount, perWriter, diff, tickReceipt } = payload;
|
|
276
|
+
const lines = [];
|
|
277
|
+
|
|
278
|
+
lines.push('');
|
|
279
|
+
lines.push(` ${colors.bold('GRAPH:')} ${graph}`);
|
|
280
|
+
lines.push(` ${colors.bold('POSITION:')} tick ${tick} of ${maxTick}`);
|
|
281
|
+
lines.push('');
|
|
282
|
+
|
|
283
|
+
if (ticks.length === 0) {
|
|
284
|
+
lines.push(` ${colors.muted('(no ticks)')}`);
|
|
285
|
+
} else {
|
|
286
|
+
const { allPoints, currentIdx } = buildTickPoints(ticks, tick);
|
|
287
|
+
const win = computeWindow(allPoints, currentIdx);
|
|
288
|
+
|
|
289
|
+
// Column headers with relative offsets
|
|
290
|
+
lines.push(buildHeaderRow(win));
|
|
291
|
+
|
|
292
|
+
// Per-writer swimlanes
|
|
293
|
+
/** @type {Array<[string, WriterInfo]>} */
|
|
294
|
+
const writerEntries = perWriter instanceof Map
|
|
295
|
+
? [...perWriter.entries()]
|
|
296
|
+
: Object.entries(perWriter).map(([k, v]) => [k, v]);
|
|
297
|
+
|
|
298
|
+
for (const [writerId, writerInfo] of writerEntries) {
|
|
299
|
+
lines.push(buildWriterSwimRow({ writerId, writerInfo, win, currentTick: tick }));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
lines.push('');
|
|
304
|
+
const edgeLabel = pluralize(edges, 'edge', 'edges');
|
|
305
|
+
const nodeLabel = pluralize(nodes, 'node', 'nodes');
|
|
306
|
+
const patchLabel = pluralize(patchCount, 'patch', 'patches');
|
|
307
|
+
|
|
308
|
+
const nodesStr = `${nodes} ${nodeLabel}${formatDelta(diff?.nodes ?? 0)}`;
|
|
309
|
+
const edgesStr = `${edges} ${edgeLabel}${formatDelta(diff?.edges ?? 0)}`;
|
|
310
|
+
lines.push(` ${colors.bold('State:')} ${nodesStr}, ${edgesStr}, ${patchCount} ${patchLabel}`);
|
|
311
|
+
|
|
312
|
+
const receiptLines = buildReceiptLines(tickReceipt);
|
|
313
|
+
if (receiptLines.length > 0) {
|
|
314
|
+
lines.push('');
|
|
315
|
+
lines.push(` ${colors.bold(`Tick ${tick}:`)}`);
|
|
316
|
+
lines.push(...receiptLines);
|
|
317
|
+
}
|
|
318
|
+
lines.push('');
|
|
319
|
+
|
|
320
|
+
return lines;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ============================================================================
|
|
324
|
+
// Public API
|
|
325
|
+
// ============================================================================
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Renders the seek view dashboard inside a double-bordered box.
|
|
329
|
+
*
|
|
330
|
+
* @param {SeekPayload} payload - Seek payload from the CLI handler
|
|
331
|
+
* @returns {string} Boxen-wrapped ASCII dashboard with trailing newline
|
|
332
|
+
*/
|
|
333
|
+
export function renderSeekView(payload) {
|
|
334
|
+
const lines = buildSeekBodyLines(payload);
|
|
335
|
+
const body = lines.join('\n');
|
|
336
|
+
|
|
337
|
+
return `${boxen(body, {
|
|
338
|
+
title: ' SEEK ',
|
|
339
|
+
titleAlignment: 'center',
|
|
340
|
+
padding: 0,
|
|
341
|
+
borderStyle: 'double',
|
|
342
|
+
borderColor: 'cyan',
|
|
343
|
+
})}\n`;
|
|
344
|
+
}
|
|
@@ -6,7 +6,7 @@ import Table from 'cli-table3';
|
|
|
6
6
|
* @param {Object} [options] - Options forwarded to cli-table3 constructor
|
|
7
7
|
* @param {string[]} [options.head] - Column headers
|
|
8
8
|
* @param {Object} [options.style] - Style overrides (defaults: head=cyan, border=gray)
|
|
9
|
-
* @returns {
|
|
9
|
+
* @returns {InstanceType<typeof Table>} A cli-table3 instance
|
|
10
10
|
*/
|
|
11
11
|
export function createTable(options = {}) {
|
|
12
12
|
const defaultStyle = { head: ['cyan'], border: ['gray'] };
|
|
@@ -16,6 +16,7 @@ const PALETTE = {
|
|
|
16
16
|
arrowFill: '#a6adc8',
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
/** @param {string} str @returns {string} */
|
|
19
20
|
function escapeXml(str) {
|
|
20
21
|
return String(str)
|
|
21
22
|
.replace(/&/g, '&')
|
|
@@ -46,6 +47,7 @@ function renderStyle() {
|
|
|
46
47
|
].join('\n');
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/** @param {{ id: string, x: number, y: number, width: number, height: number, label?: string }} node @returns {string} */
|
|
49
51
|
function renderNode(node) {
|
|
50
52
|
const { x, y, width, height } = node;
|
|
51
53
|
const label = escapeXml(node.label ?? node.id);
|
|
@@ -59,6 +61,7 @@ function renderNode(node) {
|
|
|
59
61
|
].join('\n');
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/** @param {{ startPoint?: { x: number, y: number }, bendPoints?: Array<{ x: number, y: number }>, endPoint?: { x: number, y: number } }} section @returns {Array<{ x: number, y: number }>} */
|
|
62
65
|
function sectionToPoints(section) {
|
|
63
66
|
const pts = [];
|
|
64
67
|
if (section.startPoint) {
|
|
@@ -73,6 +76,7 @@ function sectionToPoints(section) {
|
|
|
73
76
|
return pts;
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
/** @param {{ sections?: Array<{ startPoint?: { x: number, y: number }, bendPoints?: Array<{ x: number, y: number }>, endPoint?: { x: number, y: number } }>, label?: string }} edge @returns {string} */
|
|
76
80
|
function renderEdge(edge) {
|
|
77
81
|
const { sections } = edge;
|
|
78
82
|
if (!sections || sections.length === 0) {
|
|
@@ -115,7 +119,7 @@ function renderEdge(edge) {
|
|
|
115
119
|
/**
|
|
116
120
|
* Renders a PositionedGraph as an SVG string.
|
|
117
121
|
*
|
|
118
|
-
* @param {
|
|
122
|
+
* @param {{ nodes?: Array<{ id: string, x: number, y: number, width: number, height: number, label?: string }>, edges?: Array<{ sections?: Array<any>, label?: string }>, width?: number, height?: number }} positionedGraph - PositionedGraph from runLayout()
|
|
119
123
|
* @param {{ title?: string }} [options]
|
|
120
124
|
* @returns {string} Complete SVG markup
|
|
121
125
|
*/
|