@cleocode/animations 2026.5.106 → 2026.5.108

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.
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Static table rendering — column-aligned rows with terminal-width awareness.
3
+ *
4
+ * @remarks
5
+ * Part of the Human Render Contract (Epic T10114, ADR-077). Consumes a typed
6
+ * {@link TableResponse} (schema + rows + total) and emits an aligned monospace
7
+ * table that fits within the active terminal width. Wide string columns shrink
8
+ * proportionally when the total width exceeds the budget; narrow numeric
9
+ * columns are preserved.
10
+ *
11
+ * @epic T10114
12
+ * @task T10128
13
+ * @subtask T10143
14
+ */
15
+ /** Unicode horizontal rule used between header and data. */
16
+ const HRULE_UNICODE = '─';
17
+ /** ASCII fallback horizontal rule. */
18
+ const HRULE_ASCII = '-';
19
+ /** Unicode ellipsis used for truncated cell values. */
20
+ const ELLIPSIS_UNICODE = '…';
21
+ /** ASCII fallback ellipsis. */
22
+ const ELLIPSIS_ASCII = '...';
23
+ /** Two-space column separator. */
24
+ const COLUMN_GAP = ' ';
25
+ /** Minimum width retained for any string column after proportional shrink. */
26
+ const MIN_COLUMN_WIDTH = 4;
27
+ /**
28
+ * Render a {@link TableResponse} as an aligned columnar string.
29
+ *
30
+ * @param resp - Typed table response (schema + rows + total).
31
+ * @param opts - Render gate + width / ASCII overrides.
32
+ * @returns Multi-line aligned table. Empty when `opts.ctx.enabled` is `false`.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * renderTable(
37
+ * {
38
+ * rows: [{ id: 'T1', title: 'Implement', status: 'done' }],
39
+ * schema: {
40
+ * columns: [
41
+ * { key: 'id', header: 'ID' },
42
+ * { key: 'title', header: 'Title' },
43
+ * { key: 'status', header: 'Status' },
44
+ * ],
45
+ * },
46
+ * total: 1,
47
+ * },
48
+ * { ctx },
49
+ * );
50
+ * ```
51
+ */
52
+ export function renderTable(resp, opts) {
53
+ if (!opts.ctx.enabled)
54
+ return '';
55
+ const useAscii = opts.asciiBoxDrawing ?? opts.ctx.inputs.noColor;
56
+ const hrule = useAscii ? HRULE_ASCII : HRULE_UNICODE;
57
+ const ellipsis = useAscii ? ELLIPSIS_ASCII : ELLIPSIS_UNICODE;
58
+ const columns = resp.schema.columns;
59
+ if (columns.length === 0) {
60
+ return `(${resp.total} rows)`;
61
+ }
62
+ const formatted = resp.rows.map((row) => columns.map((col) => formatCell(row, col)));
63
+ // 1. Natural widths — max(header, every-cell).
64
+ const naturalWidths = columns.map((col, idx) => {
65
+ let max = col.header.length;
66
+ for (const fmtRow of formatted) {
67
+ const cell = fmtRow[idx] ?? '';
68
+ if (cell.length > max)
69
+ max = cell.length;
70
+ }
71
+ if (typeof col.width === 'number' && col.width > 0) {
72
+ return Math.min(max, col.width);
73
+ }
74
+ return max;
75
+ });
76
+ // 2. Apply max-width shrink if needed (string columns only).
77
+ const maxWidth = opts.maxWidth ?? process.stdout.columns ?? 80;
78
+ const finalWidths = applyMaxWidth(naturalWidths, columns, maxWidth);
79
+ // 3. Render header.
80
+ const headerRow = renderRow(columns.map((col) => col.header), columns, finalWidths, ellipsis);
81
+ // 4. Render separator.
82
+ const separatorRow = finalWidths.map((w) => hrule.repeat(w)).join(COLUMN_GAP);
83
+ // 5. Render data rows.
84
+ const dataRows = formatted.map((row) => renderRow(row, columns, finalWidths, ellipsis));
85
+ // 6. Footer.
86
+ const footer = `(${resp.total} ${resp.total === 1 ? 'row' : 'rows'})`;
87
+ return [headerRow, separatorRow, ...dataRows, footer].join('\n');
88
+ }
89
+ /** Extract the cell value for `column` from `row` and run it through `format`. */
90
+ function formatCell(row, column) {
91
+ const raw = row[column.key];
92
+ if (column.format !== undefined)
93
+ return column.format(raw);
94
+ if (raw === null || raw === undefined)
95
+ return '';
96
+ return String(raw);
97
+ }
98
+ /**
99
+ * Compute the final per-column widths.
100
+ *
101
+ * @remarks
102
+ * The total width budget is `maxWidth - (columns.length - 1) * COLUMN_GAP`.
103
+ * If the naturally-required widths fit, return them unchanged. Otherwise
104
+ * shrink columns whose `align` is unset or `'left'` (treated as string
105
+ * columns) proportionally to their natural width. Numeric / right-aligned /
106
+ * center-aligned columns and any column with an explicit `width` are not
107
+ * shrunk.
108
+ */
109
+ function applyMaxWidth(naturalWidths, columns, maxWidth) {
110
+ const totalGap = (columns.length - 1) * COLUMN_GAP.length;
111
+ const budget = Math.max(MIN_COLUMN_WIDTH * columns.length, maxWidth - totalGap);
112
+ const natural = naturalWidths.reduce((a, b) => a + b, 0);
113
+ if (natural <= budget)
114
+ return [...naturalWidths];
115
+ const isShrinkable = columns.map((col) => (col.align === undefined || col.align === 'left') && col.width === undefined);
116
+ const fixedSum = naturalWidths.reduce((sum, w, idx) => sum + (isShrinkable[idx] ? 0 : w), 0);
117
+ const shrinkableSum = naturalWidths.reduce((sum, w, idx) => sum + (isShrinkable[idx] ? w : 0), 0);
118
+ const shrinkBudget = Math.max(0, budget - fixedSum);
119
+ if (shrinkableSum === 0 || shrinkBudget === 0)
120
+ return [...naturalWidths];
121
+ return naturalWidths.map((w, idx) => {
122
+ if (!isShrinkable[idx])
123
+ return w;
124
+ const scaled = Math.floor((w * shrinkBudget) / shrinkableSum);
125
+ return Math.max(MIN_COLUMN_WIDTH, scaled);
126
+ });
127
+ }
128
+ /** Render one aligned row joined by {@link COLUMN_GAP}. */
129
+ function renderRow(cells, columns, widths, ellipsis) {
130
+ // The trailing column is rendered without padding — there is no neighbour
131
+ // to align to, and pad-right whitespace makes snapshot diffs noisy.
132
+ const lastIdx = cells.length - 1;
133
+ return cells
134
+ .map((cell, idx) => {
135
+ const width = widths[idx] ?? cell.length;
136
+ const align = columns[idx]?.align ?? 'left';
137
+ const truncated = truncate(cell, width, ellipsis);
138
+ if (idx === lastIdx && align === 'left')
139
+ return truncated;
140
+ return alignCell(truncated, width, align);
141
+ })
142
+ .join(COLUMN_GAP);
143
+ }
144
+ /**
145
+ * Truncate `text` to `width` characters by replacing the tail with `ellipsis`.
146
+ *
147
+ * @remarks
148
+ * No-op when `text.length <= width`. When `width <= ellipsis.length`, returns
149
+ * the leading slice of `text` without an ellipsis to avoid producing output
150
+ * wider than the budget.
151
+ */
152
+ function truncate(text, width, ellipsis) {
153
+ if (text.length <= width)
154
+ return text;
155
+ if (width <= ellipsis.length)
156
+ return text.slice(0, width);
157
+ return text.slice(0, width - ellipsis.length) + ellipsis;
158
+ }
159
+ /** Pad `text` to `width` characters according to `align`. */
160
+ function alignCell(text, width, align) {
161
+ if (text.length >= width)
162
+ return text;
163
+ const padding = width - text.length;
164
+ switch (align) {
165
+ case 'right':
166
+ return ' '.repeat(padding) + text;
167
+ case 'center': {
168
+ const left = Math.floor(padding / 2);
169
+ const right = padding - left;
170
+ return ' '.repeat(left) + text + ' '.repeat(right);
171
+ }
172
+ default:
173
+ return text + ' '.repeat(padding);
174
+ }
175
+ }
176
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.js","sourceRoot":"","sources":["../../../src/render/table.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Static tree rendering — box-drawing hierarchy with depth, fold, and icons.
3
+ *
4
+ * @remarks
5
+ * Part of the Human Render Contract (Epic T10114, ADR-077). Consumes a
6
+ * {@link TreeResponse} (flat, pre-order list of {@link FlatTreeNode}s) and
7
+ * emits a box-drawing tree that mirrors the canonical Saga → Epic → Task →
8
+ * Subtask layout.
9
+ *
10
+ * Connector glyphs:
11
+ * - `├─` non-last child
12
+ * - `└─` last child
13
+ * - `│ ` vertical continuation across a non-last ancestor
14
+ * - ` ` last-sibling ancestor (no vertical bar)
15
+ *
16
+ * Each rendered row carries the matching {@link KindIcon} prefix and a
17
+ * trailing {@link StatusIcon}. When the {@link AnimateContext} indicates
18
+ * `noColor` mode (or `opts.asciiBoxDrawing === true`) glyphs collapse to
19
+ * ASCII fallbacks (`+-`, `| `, ` `) and icons resolve via {@link ascii}.
20
+ *
21
+ * @epic T10114
22
+ * @task T10128
23
+ * @subtask T10142
24
+ */
25
+ import type { TreeResponse } from '@cleocode/contracts/render/tree.js';
26
+ import type { AnimateContext } from '../animate-context.js';
27
+ /** Options for {@link renderTree}. */
28
+ export interface RenderTreeViewOptions {
29
+ /** Render gate — primitive returns `''` when `enabled === false`. */
30
+ readonly ctx: AnimateContext;
31
+ /**
32
+ * When a node has more direct children than this threshold, only the first
33
+ * `foldAt - 1` are rendered and a `…` summary line replaces the rest.
34
+ *
35
+ * @defaultValue `50`
36
+ */
37
+ readonly foldAt?: number;
38
+ /**
39
+ * When `true`, force ASCII box-drawing glyphs regardless of the context's
40
+ * `noColor` signal. When `false`, force Unicode. When omitted, defer to
41
+ * `ctx.inputs.noColor`.
42
+ */
43
+ readonly asciiBoxDrawing?: boolean;
44
+ }
45
+ /**
46
+ * Render a {@link TreeResponse} as a multi-line box-drawing tree.
47
+ *
48
+ * @param resp - The flattened tree response to render.
49
+ * @param opts - Render gate + fold + ASCII overrides.
50
+ * @returns The formatted tree. Empty when `opts.ctx.enabled` is `false`.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * renderTree(saga, { ctx });
55
+ * // 🌲 SG-WORKTRUNK-OWN ✅
56
+ * // ├─ 📋 T9977 Worktree native ownership ✅
57
+ * // │ ├─ • T9983 Reader cache 🚧
58
+ * // │ └─ • T9984 NAPI shim ✅
59
+ * // └─ 📋 T9981 Doctor budget ⏳
60
+ * ```
61
+ */
62
+ export declare function renderTree<T>(resp: TreeResponse<T>, opts: RenderTreeViewOptions): string;
63
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../../src/render/tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAIV,YAAY,EACb,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,sCAAsC;AACtC,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAwCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,qBAAqB,GAAG,MAAM,CAiCxF"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Static tree rendering — box-drawing hierarchy with depth, fold, and icons.
3
+ *
4
+ * @remarks
5
+ * Part of the Human Render Contract (Epic T10114, ADR-077). Consumes a
6
+ * {@link TreeResponse} (flat, pre-order list of {@link FlatTreeNode}s) and
7
+ * emits a box-drawing tree that mirrors the canonical Saga → Epic → Task →
8
+ * Subtask layout.
9
+ *
10
+ * Connector glyphs:
11
+ * - `├─` non-last child
12
+ * - `└─` last child
13
+ * - `│ ` vertical continuation across a non-last ancestor
14
+ * - ` ` last-sibling ancestor (no vertical bar)
15
+ *
16
+ * Each rendered row carries the matching {@link KindIcon} prefix and a
17
+ * trailing {@link StatusIcon}. When the {@link AnimateContext} indicates
18
+ * `noColor` mode (or `opts.asciiBoxDrawing === true`) glyphs collapse to
19
+ * ASCII fallbacks (`+-`, `| `, ` `) and icons resolve via {@link ascii}.
20
+ *
21
+ * @epic T10114
22
+ * @task T10128
23
+ * @subtask T10142
24
+ */
25
+ import { ascii, KindIcon, StatusIcon } from '@cleocode/contracts/render/icon.js';
26
+ /** Default fold threshold — match ADR-077 §3 guidance. */
27
+ const DEFAULT_FOLD_AT = 50;
28
+ /** Unicode connector glyphs. */
29
+ const CONNECTOR_UNICODE = Object.freeze({
30
+ branch: '├─ ',
31
+ leaf: '└─ ',
32
+ vertical: '│ ',
33
+ blank: ' ',
34
+ });
35
+ /** ASCII fallback connector glyphs. */
36
+ const CONNECTOR_ASCII = Object.freeze({
37
+ branch: '+- ',
38
+ leaf: '+- ',
39
+ vertical: '| ',
40
+ blank: ' ',
41
+ });
42
+ /** Pretty kind label appended to the fold summary line. */
43
+ const KIND_LABEL = {
44
+ saga: 'sagas',
45
+ epic: 'epics',
46
+ task: 'tasks',
47
+ subtask: 'subtasks',
48
+ };
49
+ /**
50
+ * Render a {@link TreeResponse} as a multi-line box-drawing tree.
51
+ *
52
+ * @param resp - The flattened tree response to render.
53
+ * @param opts - Render gate + fold + ASCII overrides.
54
+ * @returns The formatted tree. Empty when `opts.ctx.enabled` is `false`.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * renderTree(saga, { ctx });
59
+ * // 🌲 SG-WORKTRUNK-OWN ✅
60
+ * // ├─ 📋 T9977 Worktree native ownership ✅
61
+ * // │ ├─ • T9983 Reader cache 🚧
62
+ * // │ └─ • T9984 NAPI shim ✅
63
+ * // └─ 📋 T9981 Doctor budget ⏳
64
+ * ```
65
+ */
66
+ export function renderTree(resp, opts) {
67
+ if (!opts.ctx.enabled)
68
+ return '';
69
+ if (resp.tree.length === 0)
70
+ return '';
71
+ const useAscii = opts.asciiBoxDrawing ?? opts.ctx.inputs.noColor;
72
+ const connectors = useAscii ? CONNECTOR_ASCII : CONNECTOR_UNICODE;
73
+ const foldAt = opts.foldAt ?? DEFAULT_FOLD_AT;
74
+ // Build the parent → ordered-children map from the pre-order flat list.
75
+ // Preserves the input order — callers control sibling order via the wire
76
+ // payload, this primitive must not re-sort.
77
+ const childrenByParent = new Map();
78
+ let rootNode;
79
+ for (const node of resp.tree) {
80
+ if (node.parentId === null) {
81
+ if (node.id === resp.root)
82
+ rootNode = node;
83
+ continue;
84
+ }
85
+ const bucket = childrenByParent.get(node.parentId);
86
+ if (bucket)
87
+ bucket.push(node);
88
+ else
89
+ childrenByParent.set(node.parentId, [node]);
90
+ }
91
+ if (rootNode === undefined)
92
+ return '';
93
+ const lines = [];
94
+ // Root line — no connector prefix.
95
+ lines.push(formatRow(rootNode, '', useAscii));
96
+ emitChildren(rootNode.id, [], childrenByParent, connectors, foldAt, useAscii, lines);
97
+ return lines.join('\n');
98
+ }
99
+ /**
100
+ * Recursively emit children of `parentId` with the correct ancestor prefix.
101
+ *
102
+ * @param ancestorIsLast - For each ancestor, whether it was the last sibling
103
+ * in its parent's child list. Drives the `│ ` vs ` ` continuation glyph.
104
+ * @param connectors - Resolved connector set (Unicode or ASCII).
105
+ */
106
+ function emitChildren(parentId, ancestorIsLast, childrenByParent, connectors, foldAt, useAscii, out) {
107
+ const children = childrenByParent.get(parentId);
108
+ if (children === undefined || children.length === 0)
109
+ return;
110
+ const ancestorPrefix = ancestorIsLast
111
+ .map((isLast) => (isLast ? connectors.blank : connectors.vertical))
112
+ .join('');
113
+ const shouldFold = children.length > foldAt;
114
+ const visibleCount = shouldFold ? foldAt - 1 : children.length;
115
+ const visible = children.slice(0, visibleCount);
116
+ for (let i = 0; i < visible.length; i++) {
117
+ const child = visible[i];
118
+ if (child === undefined)
119
+ continue;
120
+ const isLastInBatch = !shouldFold && i === children.length - 1;
121
+ const connector = isLastInBatch ? connectors.leaf : connectors.branch;
122
+ out.push(formatRow(child, `${ancestorPrefix}${connector}`, useAscii));
123
+ emitChildren(child.id, [...ancestorIsLast, isLastInBatch], childrenByParent, connectors, foldAt, useAscii, out);
124
+ }
125
+ if (shouldFold) {
126
+ const hidden = children.length - visibleCount;
127
+ const sampleKind = children[0]?.kind ?? 'task';
128
+ const label = KIND_LABEL[sampleKind];
129
+ out.push(`${ancestorPrefix}${connectors.leaf}… and ${hidden} more ${label} (run cleo tree ${parentId} to expand)`);
130
+ }
131
+ }
132
+ /** Build a single tree row: `<connectors><kind icon> <title> <status icon>`. */
133
+ function formatRow(node, prefix, useAscii) {
134
+ const kindIcon = resolveKindIcon(node.kind, useAscii);
135
+ const statusIcon = resolveStatusIcon(node.status, useAscii);
136
+ return `${prefix}${kindIcon} ${node.title} ${statusIcon}`.trimEnd();
137
+ }
138
+ /** Pick the {@link KindIcon} for a node kind, falling back to ASCII when asked. */
139
+ function resolveKindIcon(kind, useAscii) {
140
+ const icon = KIND_BY_NODE_KIND[kind];
141
+ return useAscii ? ascii(icon) : icon;
142
+ }
143
+ /** Pick the {@link StatusIcon} for a node status, falling back to ASCII. */
144
+ function resolveStatusIcon(status, useAscii) {
145
+ const icon = STATUS_BY_NODE_STATUS[status];
146
+ return useAscii ? ascii(icon) : icon;
147
+ }
148
+ /** Node-kind → KindIcon. Subtask uses bullet-like glyph per ADR-077. */
149
+ const KIND_BY_NODE_KIND = {
150
+ saga: KindIcon.SAGA,
151
+ epic: KindIcon.EPIC,
152
+ task: KindIcon.TASK,
153
+ subtask: KindIcon.SUBTASK,
154
+ };
155
+ /** Node-status → StatusIcon. */
156
+ const STATUS_BY_NODE_STATUS = {
157
+ pending: StatusIcon.PENDING,
158
+ in_progress: StatusIcon.ACTIVE,
159
+ done: StatusIcon.DONE,
160
+ blocked: StatusIcon.BLOCKED,
161
+ cancelled: StatusIcon.CANCELLED,
162
+ archived: StatusIcon.ARCHIVED,
163
+ };
164
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sourceRoot":"","sources":["../../../src/render/tree.ts"],"names":[],"mappings":"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"}