toolcraft-openapi 0.0.17 → 0.0.19

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.
Files changed (88) hide show
  1. package/dist/bin/generate.js +7 -0
  2. package/dist/define-client.js +2 -2
  3. package/dist/generate.js +2 -2
  4. package/dist/http.d.ts +21 -2
  5. package/dist/http.js +147 -22
  6. package/dist/index.d.ts +1 -1
  7. package/dist/lock.d.ts +1 -1
  8. package/dist/lock.js +109 -5
  9. package/dist/mock/fetch.js +1 -1
  10. package/dist/network-error.d.ts +2 -0
  11. package/dist/network-error.js +83 -0
  12. package/dist/spec-source.js +103 -3
  13. package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
  14. package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
  15. package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
  16. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +1 -0
  17. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +1 -1
  18. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
  19. package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
  20. package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
  21. package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
  22. package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
  23. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
  24. package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
  25. package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
  26. package/node_modules/@poe-code/design-system/dist/components/text.js +11 -3
  27. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +20 -13
  28. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +5 -0
  29. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +146 -12
  30. package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +31 -0
  31. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  32. package/node_modules/@poe-code/design-system/dist/explorer/actions.d.ts +16 -0
  33. package/node_modules/@poe-code/design-system/dist/explorer/actions.js +39 -0
  34. package/node_modules/@poe-code/design-system/dist/explorer/demo.d.ts +13 -0
  35. package/node_modules/@poe-code/design-system/dist/explorer/demo.js +297 -0
  36. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +61 -0
  37. package/node_modules/@poe-code/design-system/dist/explorer/events.js +1 -0
  38. package/node_modules/@poe-code/design-system/dist/explorer/filter.d.ts +10 -0
  39. package/node_modules/@poe-code/design-system/dist/explorer/filter.js +95 -0
  40. package/node_modules/@poe-code/design-system/dist/explorer/index.d.ts +8 -0
  41. package/node_modules/@poe-code/design-system/dist/explorer/index.js +8 -0
  42. package/node_modules/@poe-code/design-system/dist/explorer/jobs.d.ts +7 -0
  43. package/node_modules/@poe-code/design-system/dist/explorer/jobs.js +59 -0
  44. package/node_modules/@poe-code/design-system/dist/explorer/keymap.d.ts +21 -0
  45. package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +363 -0
  46. package/node_modules/@poe-code/design-system/dist/explorer/layout.d.ts +20 -0
  47. package/node_modules/@poe-code/design-system/dist/explorer/layout.js +73 -0
  48. package/node_modules/@poe-code/design-system/dist/explorer/reducer.d.ts +9 -0
  49. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +704 -0
  50. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.d.ts +4 -0
  51. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +96 -0
  52. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.d.ts +4 -0
  53. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +49 -0
  54. package/node_modules/@poe-code/design-system/dist/explorer/render/header.d.ts +4 -0
  55. package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +56 -0
  56. package/node_modules/@poe-code/design-system/dist/explorer/render/index.d.ts +8 -0
  57. package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +61 -0
  58. package/node_modules/@poe-code/design-system/dist/explorer/render/list.d.ts +4 -0
  59. package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +106 -0
  60. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.d.ts +3 -0
  61. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +91 -0
  62. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.d.ts +8 -0
  63. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.js +156 -0
  64. package/node_modules/@poe-code/design-system/dist/explorer/runtime.d.ts +2 -0
  65. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +282 -0
  66. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.d.ts +50 -0
  67. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.js +101 -0
  68. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +130 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/state.js +87 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/theme.d.ts +27 -0
  71. package/node_modules/@poe-code/design-system/dist/explorer/theme.js +97 -0
  72. package/node_modules/@poe-code/design-system/dist/index.d.ts +7 -0
  73. package/node_modules/@poe-code/design-system/dist/index.js +5 -0
  74. package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
  75. package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
  76. package/node_modules/@poe-code/design-system/dist/prompts/index.js +2 -2
  77. package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
  78. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
  79. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
  80. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
  81. package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
  82. package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
  83. package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
  84. package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
  85. package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
  86. package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
  87. package/node_modules/@poe-code/design-system/package.json +6 -3
  88. package/package.json +2 -4
@@ -0,0 +1,96 @@
1
+ import { getExplorerStyles } from "../theme.js";
2
+ export function renderDetail(state, screen, layout) {
3
+ const rect = layout.detail;
4
+ const styles = getExplorerStyles();
5
+ screen.clearRect(rect);
6
+ if (rect.width <= 0 || rect.height <= 0) {
7
+ return;
8
+ }
9
+ const row = state.rows.find((r) => r.id === state.detail.rowId) ?? null;
10
+ if (layout.mode === "narrow-vertical") {
11
+ screen.put(rect.x, rect.y, `├─ Detail ${"─".repeat(Math.max(0, rect.width - 11))}┤`, styles.border);
12
+ renderDetailBody(state, screen, { ...rect, y: rect.y + 1, height: rect.height - 1 }, row);
13
+ return;
14
+ }
15
+ screen.put(rect.x, rect.y, "│", styles.border);
16
+ renderDetailBody(state, screen, { ...rect, x: rect.x + 1, width: rect.width - 1 }, row);
17
+ }
18
+ function renderDetailBody(state, screen, rect, row) {
19
+ const styles = getExplorerStyles();
20
+ const items = state.detail.items;
21
+ if (items === null) {
22
+ writeLine(screen, rect, 0, state.detail.loading ? "Loading detail..." : state.emptyHint, styles.muted);
23
+ return;
24
+ }
25
+ if (items.length === 0) {
26
+ writeLine(screen, rect, 0, state.emptyHint, styles.muted);
27
+ return;
28
+ }
29
+ if (items.length === 1 && items[0]?.title === undefined) {
30
+ renderBlob(screen, rect, renderItem(items[0], rect, row), state.detail.scroll);
31
+ return;
32
+ }
33
+ renderListMode(state, screen, rect, items, row);
34
+ }
35
+ function renderListMode(state, screen, rect, items, row) {
36
+ const styles = getExplorerStyles();
37
+ let y = 0;
38
+ for (let index = state.detail.scroll; index < items.length && y < rect.height; index += 1) {
39
+ const item = items[index];
40
+ const cursor = index === state.detail.cursor;
41
+ const title = item.title ?? item.id;
42
+ const prefix = cursor ? "▌ " : " ";
43
+ const badge = item.badge ? ` ${item.badge.text}` : "";
44
+ writeLine(screen, rect, y, `${prefix}${title}${badge}`, cursor ? styles.borderFocused : styles.accent);
45
+ y += 1;
46
+ if (item.subtitle && y < rect.height) {
47
+ writeLine(screen, rect, y, ` ${item.subtitle}`, styles.muted);
48
+ y += 1;
49
+ }
50
+ for (const line of renderItem(item, rect, row).split("\n")) {
51
+ if (y >= rect.height) {
52
+ break;
53
+ }
54
+ writeLine(screen, rect, y, ` ${line}`, {});
55
+ y += 1;
56
+ }
57
+ if (y < rect.height) {
58
+ y += 1;
59
+ }
60
+ }
61
+ }
62
+ function renderBlob(screen, rect, text, scroll) {
63
+ const lines = text.split("\n").slice(scroll);
64
+ for (let row = 0; row < rect.height; row += 1) {
65
+ writeLine(screen, rect, row, lines[row] ?? "");
66
+ }
67
+ }
68
+ function renderItem(item, rect, row) {
69
+ try {
70
+ const rendered = item.render({
71
+ width: rect.width,
72
+ height: rect.height,
73
+ row: row ?? { id: "", title: "" },
74
+ signal: new AbortController().signal
75
+ });
76
+ return typeof rendered === "string" ? rendered : "Loading detail...";
77
+ }
78
+ catch (error) {
79
+ return error instanceof Error ? `Error: ${error.message}` : "Error: detail failed";
80
+ }
81
+ }
82
+ function writeLine(screen, rect, row, text, style = {}) {
83
+ if (row < 0 || row >= rect.height) {
84
+ return;
85
+ }
86
+ screen.put(rect.x, rect.y + row, fit(text, rect.width), style);
87
+ }
88
+ function fit(text, width) {
89
+ if (width <= 0) {
90
+ return "";
91
+ }
92
+ if (text.length <= width) {
93
+ return text;
94
+ }
95
+ return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}…`;
96
+ }
@@ -0,0 +1,4 @@
1
+ import { ScreenBuffer } from "../../dashboard/buffer.js";
2
+ import type { ExplorerLayout } from "../layout.js";
3
+ import type { ExplorerState } from "../state.js";
4
+ export declare function renderFooter(state: ExplorerState, screen: ScreenBuffer, layout: ExplorerLayout): void;
@@ -0,0 +1,49 @@
1
+ import { getExplorerStyles } from "../theme.js";
2
+ export function renderFooter(state, screen, layout) {
3
+ const rect = layout.footer;
4
+ const styles = getExplorerStyles();
5
+ screen.clearRect(rect);
6
+ if (rect.width <= 0 || rect.height <= 0) {
7
+ return;
8
+ }
9
+ const hints = footerHints(state);
10
+ let x = rect.x + 2;
11
+ const y = rect.y;
12
+ for (const hint of hints) {
13
+ if (x >= rect.x + rect.width) {
14
+ break;
15
+ }
16
+ screen.put(x, y, `[${hint.key}]`, hint.running ? styles.muted : styles.accent);
17
+ x += hint.key.length + 2;
18
+ screen.put(x, y, ` ${hint.label}`, hint.running ? styles.muted : {});
19
+ x += hint.label.length + 3;
20
+ }
21
+ }
22
+ function footerHints(state) {
23
+ const hints = [];
24
+ if (state.focused === "detail") {
25
+ hints.push({ key: "Tab", label: "focus", running: false });
26
+ hints.push({ key: "Enter", label: "sub", running: false });
27
+ }
28
+ for (const [id, entry] of state.actionState) {
29
+ if (!entry.available || entry.action?.showInFooter === false) {
30
+ continue;
31
+ }
32
+ const key = actionKey(entry, id);
33
+ const label = state.selected.size > 0 && entry.source === "row"
34
+ ? `${entry.label} ${state.selected.size}`
35
+ : entry.label;
36
+ hints.push({ key, label, running: entry.running === true });
37
+ }
38
+ hints.push({ key: "?", label: "help", running: false });
39
+ hints.push({ key: "Ctrl+P", label: "palette", running: false });
40
+ hints.push({ key: "q", label: "quit", running: false });
41
+ return hints;
42
+ }
43
+ function actionKey(entry, fallback) {
44
+ const key = entry.action?.key;
45
+ if (Array.isArray(key)) {
46
+ return key[0] ?? fallback;
47
+ }
48
+ return key ?? (entry.action?.primary === true ? "Enter" : fallback);
49
+ }
@@ -0,0 +1,4 @@
1
+ import { ScreenBuffer } from "../../dashboard/buffer.js";
2
+ import type { ExplorerLayout } from "../layout.js";
3
+ import type { ExplorerState } from "../state.js";
4
+ export declare function renderHeader(state: ExplorerState, screen: ScreenBuffer, layout: ExplorerLayout): void;
@@ -0,0 +1,56 @@
1
+ import { getExplorerStyles } from "../theme.js";
2
+ export function renderHeader(state, screen, layout) {
3
+ const rect = layout.header;
4
+ const styles = getExplorerStyles();
5
+ screen.clearRect(rect);
6
+ if (rect.width <= 0 || rect.height <= 0) {
7
+ return;
8
+ }
9
+ if (layout.mode === "too-narrow") {
10
+ screen.put(0, 0, fit("Terminal too narrow", rect.width), styles.borderFocused);
11
+ return;
12
+ }
13
+ drawTopBorder(screen, state.title, rect.width, styles.border);
14
+ if (rect.height > 1) {
15
+ const prompt = `${state.title.toLocaleLowerCase()}>`;
16
+ const filter = state.filter.length > 0 ? ` ${state.filter}` : "";
17
+ const count = `${state.filtered.length}/${state.rows.length}`;
18
+ const selected = state.selected.size > 0 ? ` (${state.selected.size} selected)` : "";
19
+ const spinner = state.detail.loading ? " *" : "";
20
+ const right = `${count}${selected}${spinner}`;
21
+ screen.put(0, 1, "│", styles.border);
22
+ screen.put(Math.max(0, rect.width - 1), 1, "│", styles.border);
23
+ screen.put(2, 1, fit(`${prompt}${filter}`, Math.max(0, rect.width - right.length - 5)), styles.accent);
24
+ screen.put(Math.max(2, rect.width - right.length - 2), 1, right, styles.muted);
25
+ }
26
+ if (rect.height > 2) {
27
+ drawHorizontal(screen, 2, rect.width, styles.border);
28
+ }
29
+ }
30
+ function drawTopBorder(screen, title, width, style) {
31
+ if (width === 1) {
32
+ screen.put(0, 0, "┌", style);
33
+ return;
34
+ }
35
+ const label = `─ ${title} `;
36
+ const middle = label.length < width - 1
37
+ ? `${label}${"─".repeat(width - 1 - label.length)}`
38
+ : label.slice(0, Math.max(0, width - 1));
39
+ screen.put(0, 0, `┌${middle.slice(0, Math.max(0, width - 2))}┐`, style);
40
+ }
41
+ function drawHorizontal(screen, y, width, style) {
42
+ if (width === 1) {
43
+ screen.put(0, y, "├", style);
44
+ return;
45
+ }
46
+ screen.put(0, y, `├${"─".repeat(Math.max(0, width - 2))}┤`, style);
47
+ }
48
+ function fit(text, width) {
49
+ if (width <= 0) {
50
+ return "";
51
+ }
52
+ if (text.length <= width) {
53
+ return text;
54
+ }
55
+ return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}…`;
56
+ }
@@ -0,0 +1,8 @@
1
+ import { ScreenBuffer } from "../../dashboard/buffer.js";
2
+ import { type ExplorerState } from "../state.js";
3
+ export declare function renderExplorer(state: ExplorerState, screen: ScreenBuffer): void;
4
+ export { renderDetail } from "./detail.js";
5
+ export { renderFooter } from "./footer.js";
6
+ export { renderHeader } from "./header.js";
7
+ export { renderList } from "./list.js";
8
+ export { renderModal } from "./modal.js";
@@ -0,0 +1,61 @@
1
+ import { computeExplorerLayout } from "../layout.js";
2
+ import { REGION_ALL, REGION_DETAIL, REGION_FOOTER, REGION_HEADER, REGION_LIST, REGION_MODAL } from "../state.js";
3
+ import { getExplorerStyles } from "../theme.js";
4
+ import { renderDetail } from "./detail.js";
5
+ import { renderFooter } from "./footer.js";
6
+ import { renderHeader } from "./header.js";
7
+ import { renderList } from "./list.js";
8
+ import { renderModal } from "./modal.js";
9
+ const REGION_RENDERERS = [
10
+ [REGION_HEADER, renderHeader],
11
+ [REGION_LIST, renderList],
12
+ [REGION_DETAIL, renderDetail],
13
+ [REGION_FOOTER, renderFooter],
14
+ [REGION_MODAL, (state, screen) => renderModal(state, screen)]
15
+ ];
16
+ export function renderExplorer(state, screen) {
17
+ const layout = computeExplorerLayout({
18
+ cols: state.size.cols,
19
+ rows: state.size.rows,
20
+ detailHidden: state.layout === "narrow-list-only" || state.layout === "too-narrow"
21
+ });
22
+ const dirty = state.dirty === 0 ? REGION_ALL : state.dirty;
23
+ for (const [region, render] of REGION_RENDERERS) {
24
+ if ((dirty & region) !== 0) {
25
+ render(state, screen, layout);
26
+ }
27
+ }
28
+ if (state.modal !== null && (dirty & REGION_MODAL) === 0) {
29
+ renderModal(state, screen);
30
+ }
31
+ if (state.toast !== null) {
32
+ renderToast(state, screen);
33
+ }
34
+ }
35
+ function renderToast(state, screen) {
36
+ if (screen.width <= 0 || screen.height <= 0) {
37
+ return;
38
+ }
39
+ const y = Math.max(0, screen.height - 1);
40
+ screen.clearRect({ x: 0, y, width: screen.width, height: 1 });
41
+ if (state.toast === null) {
42
+ return;
43
+ }
44
+ const styles = getExplorerStyles();
45
+ const message = fit(` ${state.toast.message} `, screen.width);
46
+ screen.put(0, y, message, styles.accent);
47
+ }
48
+ function fit(text, width) {
49
+ if (width <= 0) {
50
+ return "";
51
+ }
52
+ if (text.length <= width) {
53
+ return text;
54
+ }
55
+ return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}…`;
56
+ }
57
+ export { renderDetail } from "./detail.js";
58
+ export { renderFooter } from "./footer.js";
59
+ export { renderHeader } from "./header.js";
60
+ export { renderList } from "./list.js";
61
+ export { renderModal } from "./modal.js";
@@ -0,0 +1,4 @@
1
+ import { ScreenBuffer } from "../../dashboard/buffer.js";
2
+ import type { ExplorerLayout } from "../layout.js";
3
+ import type { ExplorerState } from "../state.js";
4
+ export declare function renderList(state: ExplorerState, screen: ScreenBuffer, layout: ExplorerLayout): void;
@@ -0,0 +1,106 @@
1
+ import { getExplorerStyles } from "../theme.js";
2
+ const listLineCache = new WeakMap();
3
+ export function renderList(state, screen, layout) {
4
+ const styles = getExplorerStyles();
5
+ const rect = layout.list;
6
+ screen.clearRect(rect);
7
+ if (rect.width <= 0 || rect.height <= 0) {
8
+ return;
9
+ }
10
+ if (layout.mode === "too-narrow") {
11
+ writeLine(screen, rect, 0, "Terminal too narrow", styles.muted);
12
+ return;
13
+ }
14
+ const rectKey = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`;
15
+ const cached = listLineCache.get(screen);
16
+ const cache = cached?.rectKey === rectKey ? cached.lines : new Map();
17
+ listLineCache.set(screen, { rectKey, lines: cache });
18
+ if (state.filtered.length === 0) {
19
+ const hint = state.emptyHint;
20
+ writeLine(screen, rect, Math.floor(rect.height / 2), center(hint, rect.width), styles.muted);
21
+ cache.clear();
22
+ return;
23
+ }
24
+ let lastGroup;
25
+ let y = 0;
26
+ for (const rowIndex of state.filtered) {
27
+ if (y >= rect.height) {
28
+ break;
29
+ }
30
+ const row = state.rows[rowIndex];
31
+ if (!row) {
32
+ continue;
33
+ }
34
+ if (row.group && row.group !== lastGroup && y < rect.height) {
35
+ const hash = `group:${row.group}`;
36
+ if (cache.get(y) !== hash) {
37
+ writeLine(screen, rect, y, row.group, styles.muted);
38
+ cache.set(y, hash);
39
+ }
40
+ y += 1;
41
+ lastGroup = row.group;
42
+ }
43
+ if (y >= rect.height) {
44
+ break;
45
+ }
46
+ const selected = state.selected.has(row.id);
47
+ const cursor = rowIndex === state.filtered[state.cursor];
48
+ const positions = state.matchPositions.get(rowIndex) ?? [];
49
+ const hash = lineHash(row, selected, cursor, positions);
50
+ if (cache.get(y) !== hash) {
51
+ renderRow(screen, rect, y, row, { selected, cursor, focused: state.focused === "list", positions });
52
+ cache.set(y, hash);
53
+ }
54
+ y += 1;
55
+ if (row.subtitle && y < rect.height) {
56
+ const subtitleHash = `${hash}:subtitle:${row.subtitle}`;
57
+ if (cache.get(y) !== subtitleHash) {
58
+ writeLine(screen, rect, y, ` ${row.subtitle}`, styles.muted);
59
+ cache.set(y, subtitleHash);
60
+ }
61
+ y += 1;
62
+ }
63
+ }
64
+ }
65
+ function renderRow(screen, rect, rowY, row, opts) {
66
+ const styles = getExplorerStyles();
67
+ const marker = opts.selected ? "┃" : " ";
68
+ const cursor = opts.cursor ? "●" : "◌";
69
+ const focus = opts.cursor && opts.focused ? " ▌" : "";
70
+ const badge = row.badge ? ` ${row.badge.text}` : "";
71
+ const prefix = `${marker} ${cursor} `;
72
+ const available = Math.max(0, rect.width - prefix.length - focus.length - badge.length);
73
+ const title = stripAnsi(row.title).slice(0, available);
74
+ let x = rect.x;
75
+ const y = rect.y + rowY;
76
+ screen.put(x, y, prefix, opts.cursor ? styles.accent : styles.muted);
77
+ x += prefix.length;
78
+ for (let index = 0; index < title.length; index += 1) {
79
+ const style = opts.positions.includes(index) ? styles.matchHighlight : {};
80
+ screen.put(x + index, y, title[index], style);
81
+ }
82
+ if (row.badge) {
83
+ screen.put(rect.x + rect.width - badge.length - focus.length, y, badge, styles.tones[row.badge.tone ?? "muted"]);
84
+ }
85
+ if (focus) {
86
+ screen.put(rect.x + rect.width - focus.length, y, focus, styles.borderFocused);
87
+ }
88
+ }
89
+ function writeLine(screen, rect, row, text, style = {}) {
90
+ screen.put(rect.x, rect.y + row, fit(text, rect.width), style);
91
+ }
92
+ function lineHash(row, selected, cursor, positions) {
93
+ return `${row.id}:${selected ? 1 : 0}:${cursor ? 1 : 0}:${positions.join(",")}`;
94
+ }
95
+ function center(text, width) {
96
+ return `${" ".repeat(Math.max(0, Math.floor((width - text.length) / 2)))}${text}`;
97
+ }
98
+ function fit(text, width) {
99
+ if (text.length <= width) {
100
+ return text;
101
+ }
102
+ return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}…`;
103
+ }
104
+ function stripAnsi(value) {
105
+ return value.replace(/\u001b\[[0-9;]*m/g, "");
106
+ }
@@ -0,0 +1,3 @@
1
+ import { ScreenBuffer } from "../../dashboard/buffer.js";
2
+ import type { ExplorerState } from "../state.js";
3
+ export declare function renderModal(state: ExplorerState, screen: ScreenBuffer): void;
@@ -0,0 +1,91 @@
1
+ import { getExplorerStyles } from "../theme.js";
2
+ export function renderModal(state, screen) {
3
+ if (state.modal === null || screen.width <= 0 || screen.height <= 0) {
4
+ return;
5
+ }
6
+ const width = Math.min(screen.width - 2, Math.max(34, Math.floor(screen.width * 0.62)));
7
+ const height = Math.min(screen.height - 2, modalHeight(state));
8
+ const x = Math.max(0, Math.floor((screen.width - width) / 2));
9
+ const y = Math.max(0, Math.floor((screen.height - height) / 2));
10
+ const styles = getExplorerStyles();
11
+ drawBox(screen, x, y, width, height, title(state), styles.borderFocused);
12
+ const lines = modalLines(state);
13
+ for (let row = 0; row < Math.min(lines.length, height - 2); row += 1) {
14
+ screen.put(x + 2, y + 1 + row, fit(lines[row], width - 4), row === 1 ? styles.accent : {});
15
+ }
16
+ }
17
+ function modalLines(state) {
18
+ if (state.modal?.kind === "help") {
19
+ return [
20
+ "Navigation",
21
+ " ↑ ↓ k j move cursor",
22
+ " Tab cycle panes",
23
+ " / filter",
24
+ " ? help",
25
+ " q quit"
26
+ ];
27
+ }
28
+ if (state.modal?.kind === "confirm") {
29
+ return [
30
+ state.modal.action.destructive ? "Destructive action" : "Confirm action",
31
+ `${labelFor(state.modal.action)} ${state.modal.rows.length} row(s)?`,
32
+ "Enter confirms · Esc cancels"
33
+ ];
34
+ }
35
+ if (state.modal?.kind === "palette") {
36
+ return [
37
+ `Query: ${state.modal.query}`,
38
+ ...paletteLines(state)
39
+ ];
40
+ }
41
+ return [];
42
+ }
43
+ function title(state) {
44
+ if (state.modal?.kind === "help") {
45
+ return "Keybindings";
46
+ }
47
+ if (state.modal?.kind === "confirm") {
48
+ return "Confirm";
49
+ }
50
+ return "Command Palette";
51
+ }
52
+ function modalHeight(state) {
53
+ return Math.max(5, modalLines(state).length + 2);
54
+ }
55
+ function drawBox(screen, x, y, width, height, boxTitle, style) {
56
+ screen.clearRect({ x, y, width, height });
57
+ const titleSegment = `─ ${boxTitle} `;
58
+ screen.put(x, y, `╭${titleSegment}${"─".repeat(Math.max(0, width - titleSegment.length - 2))}╮`, style);
59
+ for (let row = 1; row < height - 1; row += 1) {
60
+ screen.put(x, y + row, "│", style);
61
+ screen.put(x + width - 1, y + row, "│", style);
62
+ }
63
+ screen.put(x, y + height - 1, `╰${"─".repeat(Math.max(0, width - 2))}╯`, style);
64
+ }
65
+ function paletteLines(state) {
66
+ if (state.modal?.kind !== "palette") {
67
+ return [];
68
+ }
69
+ const query = state.modal.query.toLocaleLowerCase();
70
+ const lines = [];
71
+ for (const entry of state.actionState.values()) {
72
+ if (entry.available !== true || entry.running === true || entry.action === undefined) {
73
+ continue;
74
+ }
75
+ if (query !== "" && !entry.label.toLocaleLowerCase().includes(query)) {
76
+ continue;
77
+ }
78
+ const prefix = lines.length === state.modal.cursor ? "▌ " : " ";
79
+ lines.push(`${prefix}${entry.label}`);
80
+ }
81
+ return lines.length === 0 ? [" No actions"] : lines;
82
+ }
83
+ function labelFor(action) {
84
+ return typeof action.label === "function" ? action.label() : action.label;
85
+ }
86
+ function fit(text, width) {
87
+ if (text.length <= width) {
88
+ return text;
89
+ }
90
+ return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}…`;
91
+ }
@@ -0,0 +1,8 @@
1
+ import { ScreenBuffer } from "../../dashboard/buffer.js";
2
+ import { type DetailItem, type ExplorerState, type Row } from "../state.js";
3
+ export declare function renderStateSnapshot(state: ExplorerState): string;
4
+ export declare function dumpScreen(screen: ScreenBuffer): string;
5
+ export declare function fixtureState(overrides?: Partial<ExplorerState>): ExplorerState;
6
+ export declare function fixtureRows(): Row[];
7
+ export declare function singleDetailItem(): DetailItem;
8
+ export declare function listDetailItems(): DetailItem[];
@@ -0,0 +1,156 @@
1
+ import { ScreenBuffer, cellToAnsi } from "../../dashboard/buffer.js";
2
+ import { filterRows } from "../filter.js";
3
+ import { computeExplorerLayout } from "../layout.js";
4
+ import { createInitialState, REGION_ALL } from "../state.js";
5
+ import { renderExplorer } from "./index.js";
6
+ export function renderStateSnapshot(state) {
7
+ const screen = new ScreenBuffer(state.size.cols, state.size.rows);
8
+ renderExplorer(state, screen);
9
+ return dumpScreen(screen);
10
+ }
11
+ export function dumpScreen(screen) {
12
+ const lines = [];
13
+ for (let y = 0; y < screen.height; y += 1) {
14
+ let line = "";
15
+ for (let x = 0; x < screen.width; x += 1) {
16
+ line += cellToAnsi(screen.get(x, y));
17
+ }
18
+ lines.push(line);
19
+ }
20
+ return lines.join("\n");
21
+ }
22
+ export function fixtureState(overrides = {}) {
23
+ const rows = overrides.rows ?? fixtureRows();
24
+ const filter = overrides.filter ?? "";
25
+ const size = overrides.size ?? { cols: 100, rows: 14 };
26
+ const config = {
27
+ title: overrides.title ?? "Plans",
28
+ rows: async () => rows,
29
+ detail: {
30
+ items: async () => []
31
+ },
32
+ actions: fixtureActions(),
33
+ multiSelect: true,
34
+ emptyHint: "No plans"
35
+ };
36
+ const state = createInitialState(config, size);
37
+ state.rows = rows;
38
+ state.filter = filter;
39
+ const matches = filterRows(filter, rows);
40
+ state.filtered = matches.map((match) => match.index);
41
+ state.matchPositions = new Map(matches.map((match) => [match.index, match.positions]));
42
+ state.cursor = overrides.cursor ?? 0;
43
+ state.detail = overrides.detail ?? {
44
+ rowId: rows[0]?.id ?? null,
45
+ items: [singleDetailItem()],
46
+ cursor: 0,
47
+ scroll: 0,
48
+ token: 1,
49
+ loading: false
50
+ };
51
+ state.selected = overrides.selected ?? new Set(["27", "24"]);
52
+ state.focused = overrides.focused ?? "list";
53
+ state.modal = overrides.modal ?? null;
54
+ state.toast = overrides.toast ?? null;
55
+ state.dirty = overrides.dirty ?? REGION_ALL;
56
+ state.layout = computeExplorerLayout(size).mode;
57
+ for (const [key, value] of Object.entries(overrides)) {
58
+ state[key] = value;
59
+ }
60
+ return state;
61
+ }
62
+ export function fixtureRows() {
63
+ return [
64
+ {
65
+ id: "27",
66
+ title: "Explorer TUI library",
67
+ subtitle: "2d · kjopek · design-system",
68
+ badge: { text: "active", tone: "success" },
69
+ group: "Current"
70
+ },
71
+ {
72
+ id: "26",
73
+ title: "ACP telemetry converters",
74
+ subtitle: "3d · kjopek · acp",
75
+ badge: { text: "draft", tone: "warning" },
76
+ group: "Current"
77
+ },
78
+ {
79
+ id: "25",
80
+ title: "Maestro",
81
+ subtitle: "5d · kjopek · pipeline",
82
+ group: "Backlog"
83
+ },
84
+ {
85
+ id: "24",
86
+ title: "Tasks board sync",
87
+ subtitle: "1w · kjopek · superintendent",
88
+ badge: { text: "blocked", tone: "error" },
89
+ group: "Backlog"
90
+ }
91
+ ];
92
+ }
93
+ export function singleDetailItem() {
94
+ return {
95
+ id: "body",
96
+ render: () => [
97
+ "# Explorer TUI library",
98
+ "",
99
+ "A reusable list + detail + actions explorer component.",
100
+ "",
101
+ "## What we're building",
102
+ "A generic three-region explorer TUI."
103
+ ].join("\n")
104
+ };
105
+ }
106
+ export function listDetailItems() {
107
+ return [
108
+ {
109
+ id: "thread-1",
110
+ title: "packages/auth/src/refresh.ts:42",
111
+ subtitle: "kjopek · requested changes",
112
+ badge: { text: "fix", tone: "error" },
113
+ render: () => "The lock is released before the await."
114
+ },
115
+ {
116
+ id: "thread-2",
117
+ title: "packages/auth/src/refresh.ts:88",
118
+ subtitle: "nit",
119
+ badge: { text: "nit", tone: "info" },
120
+ render: () => "Rename `t` to `token` for readability."
121
+ }
122
+ ];
123
+ }
124
+ function fixtureActions() {
125
+ return [
126
+ {
127
+ id: "edit",
128
+ label: "edit",
129
+ key: "e",
130
+ handler: () => undefined,
131
+ showInFooter: true
132
+ },
133
+ {
134
+ id: "archive",
135
+ label: "archive",
136
+ key: "a",
137
+ handler: () => undefined,
138
+ showInFooter: true
139
+ },
140
+ {
141
+ id: "delete",
142
+ label: "delete",
143
+ key: "d",
144
+ handler: () => undefined,
145
+ destructive: true,
146
+ showInFooter: true
147
+ },
148
+ {
149
+ id: "open",
150
+ label: "open",
151
+ handler: () => undefined,
152
+ primary: true,
153
+ showInFooter: true
154
+ }
155
+ ];
156
+ }
@@ -0,0 +1,2 @@
1
+ import { type ExplorerConfig } from "./state.js";
2
+ export declare function runExplorer<R = void>(config: ExplorerConfig<R>): Promise<R | null>;