@hegemonart/get-design-done 1.54.0 → 1.56.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +92 -0
- package/README.md +6 -0
- package/SKILL.md +1 -0
- package/agents/design-fixer.md +16 -0
- package/bin/gdd-dashboard +91 -0
- package/dist/claude-code/.claude/skills/override/SKILL.md +86 -0
- package/hooks/gdd-decision-injector.js +58 -0
- package/hooks/gdd-fact-force.js +345 -0
- package/hooks/gdd-risk-gate.js +406 -0
- package/hooks/hooks.json +18 -0
- package/package.json +2 -1
- package/reference/schemas/events.schema.json +61 -1
- package/reference/skill-graph.md +2 -1
- package/scripts/lib/dashboard/graph-html.cjs +0 -0
- package/scripts/lib/health-mirror/index.cjs +146 -1
- package/scripts/lib/manifest/skills.json +8 -0
- package/scripts/lib/risk/calibration.cjs +385 -0
- package/scripts/lib/risk/compute-risk.cjs +229 -0
- package/scripts/lib/risk/consumers.cjs +211 -0
- package/scripts/lib/risk/override.cjs +87 -0
- package/scripts/lib/risk/route.cjs +59 -0
- package/scripts/lib/risk/tables.cjs +221 -0
- package/sdk/cli/commands/dashboard.ts +419 -0
- package/sdk/cli/index.js +253 -2
- package/sdk/cli/index.ts +7 -0
- package/sdk/dashboard/data/_pkg-root.cjs +92 -0
- package/sdk/dashboard/data/cost-aggregator.cjs +187 -0
- package/sdk/dashboard/data/discovery.cjs +297 -0
- package/sdk/dashboard/data/risk-surface.cjs +136 -0
- package/sdk/dashboard/data/source.cjs +576 -0
- package/sdk/dashboard/tui/ansi.cjs +355 -0
- package/sdk/dashboard/tui/index.cjs +778 -0
- package/sdk/mcp/gdd-mcp/server.js +70 -0
- package/skills/override/SKILL.md +86 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* sdk/dashboard/tui/ansi.cjs — Phase 55 (GDD Dashboard, DEP-FREE), TUI-01 substrate.
|
|
4
|
+
*
|
|
5
|
+
* The hand-rolled terminal-render toolkit that REPLACES Ink/Yoga with ZERO new dependency
|
|
6
|
+
* (Node builtins only — in fact this file requires nothing). It is the layout + paint core
|
|
7
|
+
* the TUI main loop (sdk/dashboard/tui/index.cjs, executor D) draws its 5 panes with.
|
|
8
|
+
*
|
|
9
|
+
* Design rule: every helper here is PURE — it RETURNS a string / string[] / op[] and performs
|
|
10
|
+
* NO I/O. The main loop owns the single `process.stdout.write(...)`. That separation is what
|
|
11
|
+
* makes the whole render layer deterministically unit-testable (test/suite/phase-55-ansi.test.cjs)
|
|
12
|
+
* and is the heart of the dep-free / no-Yoga decision (CONTEXT D1, the ANSI render core contract).
|
|
13
|
+
*
|
|
14
|
+
* WIDTH MODEL (the subtle part). Terminals lay text out in CELLS, not bytes or UTF-16 units:
|
|
15
|
+
* - SGR color escapes occupy zero cells -> we strip them before measuring (`visibleWidth`).
|
|
16
|
+
* - A code point can be 0, 1, or 2 cells wide. We iterate by CODE POINT (for..of / spread),
|
|
17
|
+
* never by `.length` (UTF-16 units), so an astral char (emoji, U+1F600) is one indivisible
|
|
18
|
+
* unit -> we can NEVER slice a surrogate pair in half.
|
|
19
|
+
* - `wcwidthLite(cp)` is a compact East-Asian-Width table: the major CJK / fullwidth / wide-
|
|
20
|
+
* emoji ranges count as 2 columns; combining marks count as 0; everything else as 1. This
|
|
21
|
+
* is the floor the spec asks for (code-point counting) plus a wide-char bonus for CJK.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// --- ANSI control constants ------------------------------------------------
|
|
25
|
+
const ESC = '\x1b';
|
|
26
|
+
const CSI = ESC + '[';
|
|
27
|
+
const RESET = CSI + '0m';
|
|
28
|
+
|
|
29
|
+
// SGR matcher used to strip color codes before measuring visible width. Matches a CSI
|
|
30
|
+
// sequence terminated by 'm' (the SGR final byte). Kept narrow (only 'm') on purpose — we
|
|
31
|
+
// never emit cursor-move sequences inside content lines.
|
|
32
|
+
// eslint-disable-next-line no-control-regex
|
|
33
|
+
const SGR_RE = /\x1b\[[0-9;]*m/g;
|
|
34
|
+
|
|
35
|
+
// --- cursor / screen control (return the escape strings; no writes) --------
|
|
36
|
+
|
|
37
|
+
/** CUP — move the cursor to (row, col), 1-based, as the terminal expects. */
|
|
38
|
+
function cursorTo(row, col) {
|
|
39
|
+
return `${CSI}${Math.max(1, row | 0)};${Math.max(1, col | 0)}H`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Erase the whole screen and home the cursor. */
|
|
43
|
+
function clearScreen() {
|
|
44
|
+
return `${CSI}2J${CSI}H`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Erase the entire current line (cursor row), leaving the cursor where it is. */
|
|
48
|
+
function clearLine() {
|
|
49
|
+
return `${CSI}2K`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** DECTCEM — hide the cursor. */
|
|
53
|
+
function hideCursor() {
|
|
54
|
+
return `${CSI}?25l`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** DECTCEM — show the cursor. */
|
|
58
|
+
function showCursor() {
|
|
59
|
+
return `${CSI}?25h`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Enter the alternate screen buffer (so quitting restores the user's scrollback). */
|
|
63
|
+
function altScreenEnter() {
|
|
64
|
+
return `${CSI}?1049h`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Leave the alternate screen buffer. */
|
|
68
|
+
function altScreenExit() {
|
|
69
|
+
return `${CSI}?1049l`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- color (SGR) -----------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
// Named foreground SGR codes (the 8 base colors + their bright variants + a gray alias).
|
|
75
|
+
const FG = {
|
|
76
|
+
black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37,
|
|
77
|
+
gray: 90, grey: 90, brightBlack: 90,
|
|
78
|
+
brightRed: 91, brightGreen: 92, brightYellow: 93, brightBlue: 94,
|
|
79
|
+
brightMagenta: 95, brightCyan: 96, brightWhite: 97,
|
|
80
|
+
};
|
|
81
|
+
// Background codes are the foreground codes + 10.
|
|
82
|
+
const BG = Object.fromEntries(Object.entries(FG).map(([k, v]) => [k, v + 10]));
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a color spec to its SGR opener string(s).
|
|
86
|
+
* - integer 0-255 -> a STANDALONE 256-color sequence `\x1b[38;5;<n>m` (fg) / `\x1b[48;5;<n>m`
|
|
87
|
+
* (bg). Emitted on its own (not folded into a longer `;`-list): the `38;5;n` extended-color
|
|
88
|
+
* selector is an atomic 3-param unit and some terminals mishandle it when mixed with other
|
|
89
|
+
* params — keeping it standalone is the defensively-portable choice (and what the pinned
|
|
90
|
+
* contract asserts).
|
|
91
|
+
* - named string -> the table code (a bare numeric param, foldable into the attr opener).
|
|
92
|
+
* Returns { attrs:number[] (foldable params), seqs:string[] (standalone sequences) }.
|
|
93
|
+
*/
|
|
94
|
+
function colorParts(spec, isBg) {
|
|
95
|
+
if (spec == null) return { attrs: [], seqs: [] };
|
|
96
|
+
if (typeof spec === 'number' && Number.isInteger(spec) && spec >= 0 && spec <= 255) {
|
|
97
|
+
return { attrs: [], seqs: [`${CSI}${isBg ? 48 : 38};5;${spec}m`] };
|
|
98
|
+
}
|
|
99
|
+
const code = (isBg ? BG : FG)[spec];
|
|
100
|
+
return code == null ? { attrs: [], seqs: [] } : { attrs: [code], seqs: [] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Wrap `text` in an SGR opener (+ any standalone 256-color sequences) and a SINGLE trailing
|
|
105
|
+
* reset.
|
|
106
|
+
* opts: { fg?, bg?, bold?, dim?, underline?, noColor? }
|
|
107
|
+
* No-op (returns `text` unchanged) when opts.noColor is truthy, when process.env.NO_COLOR is
|
|
108
|
+
* set (the NO_COLOR convention), or when no style was requested.
|
|
109
|
+
*/
|
|
110
|
+
function color(text, opts = {}) {
|
|
111
|
+
const s = String(text);
|
|
112
|
+
if (opts.noColor || process.env.NO_COLOR) return s;
|
|
113
|
+
|
|
114
|
+
const attrs = []; // foldable into one opener: 1/2/4 + named color codes
|
|
115
|
+
if (opts.bold) attrs.push(1);
|
|
116
|
+
if (opts.dim) attrs.push(2);
|
|
117
|
+
if (opts.underline) attrs.push(4);
|
|
118
|
+
|
|
119
|
+
const fg = colorParts(opts.fg, false);
|
|
120
|
+
const bg = colorParts(opts.bg, true);
|
|
121
|
+
attrs.push(...fg.attrs, ...bg.attrs);
|
|
122
|
+
|
|
123
|
+
const opener = attrs.length ? `${CSI}${attrs.join(';')}m` : '';
|
|
124
|
+
const standalone = [...fg.seqs, ...bg.seqs].join('');
|
|
125
|
+
|
|
126
|
+
if (!opener && !standalone) return s; // nothing to wrap
|
|
127
|
+
return `${opener}${standalone}${s}${RESET}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- width model -----------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* wcwidth-lite: cells occupied by a single code point.
|
|
134
|
+
* 0 -> zero-width combining marks (so they don't inflate the measured width)
|
|
135
|
+
* 2 -> wide (East-Asian Wide/Fullwidth) + the common wide-emoji planes
|
|
136
|
+
* 1 -> everything else (the floor)
|
|
137
|
+
*/
|
|
138
|
+
function wcwidthLite(cp) {
|
|
139
|
+
// C0/C1 controls measured as 0 (they don't advance the cursor as a glyph would).
|
|
140
|
+
if (cp === 0) return 0;
|
|
141
|
+
if (cp < 32 || (cp >= 0x7f && cp < 0xa0)) return 0;
|
|
142
|
+
|
|
143
|
+
// Combining marks / zero-width: count as 0 columns.
|
|
144
|
+
if (
|
|
145
|
+
(cp >= 0x0300 && cp <= 0x036f) || // combining diacritical marks
|
|
146
|
+
(cp >= 0x200b && cp <= 0x200f) || // zero-width space / joiners / marks
|
|
147
|
+
cp === 0xfeff || // BOM / zero-width no-break space
|
|
148
|
+
(cp >= 0xfe00 && cp <= 0xfe0f) // variation selectors
|
|
149
|
+
) return 0;
|
|
150
|
+
|
|
151
|
+
// Wide (count as 2 columns). A compact but representative East-Asian-Width table.
|
|
152
|
+
if (
|
|
153
|
+
(cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
|
|
154
|
+
(cp >= 0x2e80 && cp <= 0x303e) || // CJK radicals / Kangxi
|
|
155
|
+
(cp >= 0x3041 && cp <= 0x33ff) || // Hiragana, Katakana, CJK symbols
|
|
156
|
+
(cp >= 0x3400 && cp <= 0x4dbf) || // CJK Ext A
|
|
157
|
+
(cp >= 0x4e00 && cp <= 0x9fff) || // CJK Unified Ideographs
|
|
158
|
+
(cp >= 0xa000 && cp <= 0xa4cf) || // Yi
|
|
159
|
+
(cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables
|
|
160
|
+
(cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs
|
|
161
|
+
(cp >= 0xfe30 && cp <= 0xfe4f) || // CJK Compatibility Forms
|
|
162
|
+
(cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms
|
|
163
|
+
(cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth signs
|
|
164
|
+
(cp >= 0x1f300 && cp <= 0x1faff) || // emoji & pictographs (most live here)
|
|
165
|
+
(cp >= 0x20000 && cp <= 0x3fffd) // CJK Ext B..G (supplementary ideographic planes)
|
|
166
|
+
) return 2;
|
|
167
|
+
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Visible column width of `s`: SGR stripped, counted per code point with wcwidthLite. */
|
|
172
|
+
function visibleWidth(s) {
|
|
173
|
+
const plain = String(s).replace(SGR_RE, '');
|
|
174
|
+
let w = 0;
|
|
175
|
+
for (const ch of plain) w += wcwidthLite(ch.codePointAt(0));
|
|
176
|
+
return w;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --- truncate / pad (width-aware, surrogate-safe) --------------------------
|
|
180
|
+
|
|
181
|
+
const ELLIPSIS = '…'; // a single 1-column glyph
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Width-aware truncation. If `s` already fits in `width` visible columns it is returned as-is.
|
|
185
|
+
* Otherwise it is cut to `width` columns with a trailing 1-column ellipsis. Iterates by code
|
|
186
|
+
* point, so a surrogate pair (astral char / emoji) is an indivisible unit and is NEVER split:
|
|
187
|
+
* a wide char that cannot fit alongside the ellipsis is dropped whole.
|
|
188
|
+
*
|
|
189
|
+
* NOTE: operates on the plain text (no embedded SGR). Callers that need color should color
|
|
190
|
+
* the already-truncated result, or color whole cells (see columns/box, which truncate plain
|
|
191
|
+
* content). This keeps the width math exact.
|
|
192
|
+
*/
|
|
193
|
+
function truncate(s, width) {
|
|
194
|
+
const w = Math.max(0, width | 0);
|
|
195
|
+
if (w === 0) return '';
|
|
196
|
+
if (visibleWidth(s) <= w) return String(s);
|
|
197
|
+
|
|
198
|
+
const budget = w - 1; // reserve one column for the ellipsis
|
|
199
|
+
let out = '';
|
|
200
|
+
let used = 0;
|
|
201
|
+
for (const ch of String(s)) {
|
|
202
|
+
const cw = wcwidthLite(ch.codePointAt(0));
|
|
203
|
+
if (used + cw > budget) break; // a wide char that won't fit is dropped whole (surrogate-safe)
|
|
204
|
+
out += ch;
|
|
205
|
+
used += cw;
|
|
206
|
+
}
|
|
207
|
+
return out + ELLIPSIS;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Pad (or truncate) `s` to exactly `width` visible columns, spaces on the RIGHT. */
|
|
211
|
+
function padRight(s, width) {
|
|
212
|
+
const w = Math.max(0, width | 0);
|
|
213
|
+
const str = String(s);
|
|
214
|
+
const vis = visibleWidth(str);
|
|
215
|
+
if (vis > w) return truncate(str, w);
|
|
216
|
+
return str + ' '.repeat(w - vis);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Pad (or truncate) `s` to exactly `width` visible columns, spaces on the LEFT. */
|
|
220
|
+
function padLeft(s, width) {
|
|
221
|
+
const w = Math.max(0, width | 0);
|
|
222
|
+
const str = String(s);
|
|
223
|
+
const vis = visibleWidth(str);
|
|
224
|
+
if (vis > w) return truncate(str, w);
|
|
225
|
+
return ' '.repeat(w - vis) + str;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- columns (the row layout helper) ---------------------------------------
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Lay `cells` out in fixed-width columns. Each cell is padded/truncated to its width via
|
|
232
|
+
* padRight, then joined with `sep` (default a single space). The returned row therefore has a
|
|
233
|
+
* deterministic, exact visible width: sum(widths) + sep*(n-1).
|
|
234
|
+
*/
|
|
235
|
+
function columns(cells, widths, sep = ' ') {
|
|
236
|
+
const list = Array.isArray(cells) ? cells : [];
|
|
237
|
+
const w = Array.isArray(widths) ? widths : [];
|
|
238
|
+
return list.map((c, i) => padRight(c == null ? '' : c, w[i] == null ? 0 : w[i])).join(sep);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// --- box (the bordered-panel helper — the Yoga replacement) ----------------
|
|
242
|
+
|
|
243
|
+
const BORDERS = {
|
|
244
|
+
round: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' },
|
|
245
|
+
space: { tl: ' ', tr: ' ', bl: ' ', br: ' ', h: ' ', v: ' ' },
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Render a bordered box as string[] — EVERY returned line is exactly `width` visible columns.
|
|
250
|
+
*
|
|
251
|
+
* opts: { title?, lines: string[], width, height?, border? }
|
|
252
|
+
* title — embedded in the top edge (truncated to fit), optional.
|
|
253
|
+
* lines — content rows; each is padded/truncated to the inner width (width-2).
|
|
254
|
+
* width — total outer width (borders included). The hard invariant.
|
|
255
|
+
* height — optional total row count; content is padded with blank rows or truncated.
|
|
256
|
+
* border — 'round' (default, rounded line-drawing) | 'space' (borderless padding).
|
|
257
|
+
*
|
|
258
|
+
* Layout: [top edge] + [content rows] + [bottom edge]. Inner content width = width - 2 (the two
|
|
259
|
+
* vertical borders). Deterministic and pure.
|
|
260
|
+
*/
|
|
261
|
+
function box(opts = {}) {
|
|
262
|
+
const width = Math.max(2, opts.width | 0);
|
|
263
|
+
const b = BORDERS[opts.border] || BORDERS.round;
|
|
264
|
+
const inner = width - 2;
|
|
265
|
+
const lines = Array.isArray(opts.lines) ? opts.lines : [];
|
|
266
|
+
|
|
267
|
+
// Top edge with an embedded, truncated title. The title sits one cell in from the left
|
|
268
|
+
// corner; the remaining edge is filled with the horizontal glyph.
|
|
269
|
+
let top;
|
|
270
|
+
const rawTitle = opts.title == null ? '' : String(opts.title);
|
|
271
|
+
if (rawTitle && inner > 0) {
|
|
272
|
+
// Reserve 1 leading + at least 1 trailing horizontal cell around the title.
|
|
273
|
+
const titleMax = Math.max(0, inner - 2);
|
|
274
|
+
const title = truncate(rawTitle, titleMax); // fit to the edge; no padding
|
|
275
|
+
const titleW = visibleWidth(title);
|
|
276
|
+
const fill = inner - 1 - titleW; // 1 leading h before the title
|
|
277
|
+
top = b.tl + b.h + title + b.h.repeat(Math.max(0, fill)) + b.tr;
|
|
278
|
+
} else {
|
|
279
|
+
top = b.tl + b.h.repeat(inner) + b.tr;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Content rows: pad/truncate each to the inner width and wrap in vertical borders.
|
|
283
|
+
let content = lines.map((ln) => b.v + padRight(ln == null ? '' : ln, inner) + b.v);
|
|
284
|
+
|
|
285
|
+
// Honor an explicit height (top + N content rows + bottom). Pad with blank rows or truncate.
|
|
286
|
+
if (opts.height != null) {
|
|
287
|
+
const targetContent = Math.max(0, (opts.height | 0) - 2);
|
|
288
|
+
if (content.length < targetContent) {
|
|
289
|
+
const blank = b.v + ' '.repeat(inner) + b.v;
|
|
290
|
+
while (content.length < targetContent) content.push(blank);
|
|
291
|
+
} else if (content.length > targetContent) {
|
|
292
|
+
content = content.slice(0, targetContent);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const bottom = b.bl + b.h.repeat(inner) + b.br;
|
|
297
|
+
return [top, ...content, bottom];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// --- diff repaint ----------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Compute the minimal set of row repaints between two frames. Returns [{row, text}] for ONLY
|
|
304
|
+
* the rows whose text changed (row is 1-based, matching cursorTo). Rows present in `next` but
|
|
305
|
+
* not `prev` are emitted (appended); rows in `prev` but gone from `next` are emitted with an
|
|
306
|
+
* empty `text` so the main loop can clear them. Identical frames -> []. Pure.
|
|
307
|
+
*
|
|
308
|
+
* The TUI loop applies each op as `cursorTo(row,1) + clearLine() + text`, so a steady frame
|
|
309
|
+
* costs zero writes and a single-cell change repaints exactly one line (no flicker).
|
|
310
|
+
*/
|
|
311
|
+
function diffRender(prevLines, nextLines) {
|
|
312
|
+
const prev = Array.isArray(prevLines) ? prevLines : [];
|
|
313
|
+
const next = Array.isArray(nextLines) ? nextLines : [];
|
|
314
|
+
const ops = [];
|
|
315
|
+
const n = Math.max(prev.length, next.length);
|
|
316
|
+
for (let i = 0; i < n; i++) {
|
|
317
|
+
const before = i < prev.length ? prev[i] : undefined;
|
|
318
|
+
const after = i < next.length ? next[i] : undefined;
|
|
319
|
+
if (after === undefined) {
|
|
320
|
+
// Row removed in the new frame -> clear it.
|
|
321
|
+
ops.push({ row: i + 1, text: '' });
|
|
322
|
+
} else if (before !== after) {
|
|
323
|
+
ops.push({ row: i + 1, text: after });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return ops;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = {
|
|
330
|
+
// cursor / screen
|
|
331
|
+
cursorTo,
|
|
332
|
+
clearScreen,
|
|
333
|
+
clearLine,
|
|
334
|
+
hideCursor,
|
|
335
|
+
showCursor,
|
|
336
|
+
altScreenEnter,
|
|
337
|
+
altScreenExit,
|
|
338
|
+
// color
|
|
339
|
+
color,
|
|
340
|
+
// width
|
|
341
|
+
visibleWidth,
|
|
342
|
+
wcwidthLite,
|
|
343
|
+
// layout
|
|
344
|
+
truncate,
|
|
345
|
+
padRight,
|
|
346
|
+
padLeft,
|
|
347
|
+
columns,
|
|
348
|
+
box,
|
|
349
|
+
// diff repaint
|
|
350
|
+
diffRender,
|
|
351
|
+
// constants exported for executor D (so panes can compose raw escapes if needed)
|
|
352
|
+
ESC,
|
|
353
|
+
CSI,
|
|
354
|
+
RESET,
|
|
355
|
+
};
|