toolcraft 0.0.70 → 0.0.72

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.
@@ -40,13 +40,15 @@ export function computeExplorerLayout(opts) {
40
40
  footer
41
41
  };
42
42
  }
43
- const listWidth = mode === "wide" ? Math.floor((cols * 5) / 12) : Math.floor((cols * 2) / 5);
44
- const detailWidth = cols - listWidth;
43
+ const gutterWidth = 1;
44
+ const availableWidth = Math.max(0, cols - gutterWidth);
45
+ const listWidth = mode === "wide" ? Math.floor((availableWidth * 5) / 12) : Math.floor((availableWidth * 2) / 5);
46
+ const detailWidth = availableWidth - listWidth;
45
47
  return {
46
48
  mode,
47
49
  header,
48
50
  list: { x: 0, y: contentY, width: listWidth, height: contentHeight },
49
- detail: { x: listWidth, y: contentY, width: detailWidth, height: contentHeight },
51
+ detail: { x: listWidth + gutterWidth, y: contentY, width: detailWidth, height: contentHeight },
50
52
  footer
51
53
  };
52
54
  }
@@ -238,7 +238,9 @@ function detailLoaded(state, rowId, token, items) {
238
238
  };
239
239
  }
240
240
  function detailItemRendered(state, rowId, token, itemIndex, content) {
241
- if (state.detail.rowId !== rowId || state.detail.token !== token || state.detail.items?.[itemIndex] === undefined) {
241
+ if (state.detail.rowId !== rowId ||
242
+ state.detail.token !== token ||
243
+ state.detail.items?.[itemIndex] === undefined) {
242
244
  return mark(state, 0);
243
245
  }
244
246
  const items = state.detail.items.map((item, index) => index === itemIndex ? { ...item, renderedContent: content } : item);
@@ -354,7 +356,14 @@ function updateFilter(state, filter) {
354
356
  matchPositions,
355
357
  cursor,
356
358
  detail,
357
- actionState: recomputeActionState({ ...state, filter, filtered, matchPositions, cursor, detail }),
359
+ actionState: recomputeActionState({
360
+ ...state,
361
+ filter,
362
+ filtered,
363
+ matchPositions,
364
+ cursor,
365
+ detail
366
+ }),
358
367
  dirty: REGION_HEADER | REGION_LIST | REGION_DETAIL | REGION_FOOTER
359
368
  };
360
369
  const effect = detailEffect(next);
@@ -385,7 +394,11 @@ function escape(state, runtimeHandles) {
385
394
  if (state.filterFocused || state.filter.length > 0) {
386
395
  const cleared = updateFilter({ ...state, filterFocused: false }, "");
387
396
  return {
388
- state: { ...cleared.state, filterFocused: false, dirty: cleared.state.dirty | REGION_HEADER | REGION_FOOTER },
397
+ state: {
398
+ ...cleared.state,
399
+ filterFocused: false,
400
+ dirty: cleared.state.dirty | REGION_HEADER | REGION_FOOTER
401
+ },
389
402
  effects: cleared.effects
390
403
  };
391
404
  }
@@ -500,9 +513,9 @@ function detailBodyHeight(state) {
500
513
  if (state.layout === "narrow-vertical") {
501
514
  const listHeight = Math.ceil(contentHeight / 2);
502
515
  const detailHeight = contentHeight - listHeight;
503
- return Math.max(0, detailHeight - 1);
516
+ return Math.max(0, detailHeight - 2);
504
517
  }
505
- return contentHeight;
518
+ return Math.max(0, contentHeight - 2);
506
519
  }
507
520
  function extendSelection(state, delta) {
508
521
  if (!state.multiSelect) {
@@ -557,7 +570,10 @@ function reorder(state, delta) {
557
570
  actionState: recomputeActionState({ ...state, rows, filtered, matchPositions, cursor }),
558
571
  dirty: REGION_LIST | REGION_FOOTER
559
572
  };
560
- return { state: next, effects: [{ type: "persistOrder", orderedIds: rows.map((row) => row.id) }] };
573
+ return {
574
+ state: next,
575
+ effects: [{ type: "persistOrder", orderedIds: rows.map((row) => row.id) }]
576
+ };
561
577
  }
562
578
  function paletteInput(state, key) {
563
579
  if (state.modal?.kind !== "palette") {
@@ -1,4 +1,7 @@
1
+ import { stripAnsi } from "../../internal/strip-ansi.js";
2
+ import { renderMarkdown } from "../../terminal-markdown/index.js";
1
3
  import { getExplorerStyles } from "../theme.js";
4
+ import { drawPaneFrame, paneBodyRect } from "./pane.js";
2
5
  import { fitToWidth } from "./text.js";
3
6
  export function renderDetail(state, screen, layout) {
4
7
  const rect = layout.detail;
@@ -9,16 +12,19 @@ export function renderDetail(state, screen, layout) {
9
12
  }
10
13
  const row = state.rows.find((r) => r.id === state.detail.rowId) ?? null;
11
14
  if (layout.mode === "narrow-vertical") {
12
- screen.put(rect.x, rect.y, `├─ Detail ${"".repeat(Math.max(0, rect.width - 11))}┤`, styles.border);
13
- renderDetailBody(state, screen, { ...rect, y: rect.y + 1, height: rect.height - 1 }, row);
15
+ drawPaneFrame(screen, rect, "Preview", state.focused === "detail" ? styles.borderFocused : styles.border);
16
+ renderDetailBody(state, screen, paneBodyRect(rect), row);
14
17
  return;
15
18
  }
16
- screen.put(rect.x, rect.y, "", styles.border);
17
- renderDetailBody(state, screen, { ...rect, x: rect.x + 1, width: rect.width - 1 }, row);
19
+ drawPaneFrame(screen, rect, "Preview", state.focused === "detail" ? styles.borderFocused : styles.border);
20
+ renderDetailBody(state, screen, paneBodyRect(rect), row);
18
21
  }
19
22
  function renderDetailBody(state, screen, rect, row) {
20
23
  const styles = getExplorerStyles();
21
24
  const items = state.detail.items;
25
+ if (rect.width <= 0 || rect.height <= 0) {
26
+ return;
27
+ }
22
28
  if (items === null) {
23
29
  writeLine(screen, rect, 0, state.detail.loading ? "Loading detail..." : state.emptyHint, styles.muted);
24
30
  return;
@@ -28,7 +34,7 @@ function renderDetailBody(state, screen, rect, row) {
28
34
  return;
29
35
  }
30
36
  if (items.length === 1 && items[0]?.title === undefined) {
31
- renderBlob(screen, rect, renderItem(items[0], rect, row), state.detail.scroll);
37
+ renderBlob(screen, rect, renderItemMarkdown(items[0], rect, row), state.detail.scroll);
32
38
  return;
33
39
  }
34
40
  renderListMode(state, screen, rect, items, row);
@@ -49,7 +55,7 @@ function renderListMode(state, screen, rect, items, row) {
49
55
  writeLine(screen, rect, y, ` ${item.subtitle}`, styles.muted);
50
56
  y += 1;
51
57
  }
52
- for (const line of renderItem(item, rect, row).split("\n")) {
58
+ for (const line of renderItemMarkdown(item, rect, row).split("\n")) {
53
59
  if (y >= rect.height) {
54
60
  break;
55
61
  }
@@ -69,6 +75,13 @@ function renderBlob(screen, rect, text, scroll) {
69
75
  writeLine(screen, rect, row, lines[row] ?? "");
70
76
  }
71
77
  }
78
+ function renderItemMarkdown(item, rect, row) {
79
+ const content = renderItem(item, rect, row);
80
+ if (content.trim().length === 0) {
81
+ return "";
82
+ }
83
+ return stripAnsi(renderMarkdown(content, { width: Math.max(1, rect.width) }).trimEnd());
84
+ }
72
85
  function renderItem(item, rect, row) {
73
86
  if (item.renderedContent !== undefined) {
74
87
  return item.renderedContent;
@@ -1,4 +1,5 @@
1
1
  import { getExplorerStyles } from "../theme.js";
2
+ import { drawPaneFrame, paneBodyRect } from "./pane.js";
2
3
  import { cellWidth, centerCells, fitToWidth, splitGraphemeCells, stripAnsi } from "./text.js";
3
4
  const listLineCache = new WeakMap();
4
5
  export function renderList(state, screen, layout) {
@@ -12,36 +13,41 @@ export function renderList(state, screen, layout) {
12
13
  writeLine(screen, rect, 0, "Terminal too narrow", styles.muted);
13
14
  return;
14
15
  }
15
- const rectKey = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`;
16
+ drawPaneFrame(screen, rect, "Plans", state.focused === "list" ? styles.borderFocused : styles.border);
17
+ const bodyRect = paneBodyRect(rect);
18
+ if (bodyRect.width <= 0 || bodyRect.height <= 0) {
19
+ return;
20
+ }
21
+ const rectKey = `${bodyRect.x}:${bodyRect.y}:${bodyRect.width}:${bodyRect.height}`;
16
22
  const cached = listLineCache.get(screen);
17
23
  const cache = cached?.rectKey === rectKey ? cached.lines : new Map();
18
24
  listLineCache.set(screen, { rectKey, lines: cache });
19
25
  if (state.filtered.length === 0) {
20
26
  const hint = state.emptyHint;
21
- writeLine(screen, rect, Math.floor(rect.height / 2), centerCells(hint, rect.width, rect.x), styles.muted);
27
+ writeLine(screen, bodyRect, Math.floor(bodyRect.height / 2), centerCells(hint, bodyRect.width, bodyRect.x), styles.muted);
22
28
  cache.clear();
23
29
  return;
24
30
  }
25
31
  let lastGroup;
26
32
  let y = 0;
27
33
  for (const rowIndex of state.filtered) {
28
- if (y >= rect.height) {
34
+ if (y >= bodyRect.height) {
29
35
  break;
30
36
  }
31
37
  const row = state.rows[rowIndex];
32
38
  if (!row) {
33
39
  continue;
34
40
  }
35
- if (row.group && row.group !== lastGroup && y < rect.height) {
41
+ if (row.group && row.group !== lastGroup && y < bodyRect.height) {
36
42
  const hash = `group:${row.group}`;
37
43
  if (cache.get(y) !== hash) {
38
- writeLine(screen, rect, y, row.group, styles.muted);
44
+ writeLine(screen, bodyRect, y, formatGroupHeader(row.group, bodyRect), styles.muted);
39
45
  cache.set(y, hash);
40
46
  }
41
47
  y += 1;
42
48
  lastGroup = row.group;
43
49
  }
44
- if (y >= rect.height) {
50
+ if (y >= bodyRect.height) {
45
51
  break;
46
52
  }
47
53
  const selected = state.multiSelect && state.selected.has(row.id);
@@ -49,20 +55,33 @@ export function renderList(state, screen, layout) {
49
55
  const positions = state.matchPositions.get(rowIndex) ?? [];
50
56
  const hash = lineHash(row, selected, cursor, positions);
51
57
  if (cache.get(y) !== hash) {
52
- renderRow(screen, rect, y, row, { selected, cursor, focused: state.focused === "list", positions });
58
+ renderRow(screen, bodyRect, y, row, {
59
+ selected,
60
+ cursor,
61
+ focused: state.focused === "list",
62
+ positions
63
+ });
53
64
  cache.set(y, hash);
54
65
  }
55
66
  y += 1;
56
- if (row.subtitle && y < rect.height) {
67
+ if (row.subtitle && y < bodyRect.height) {
57
68
  const subtitleHash = `${hash}:subtitle:${row.subtitle}`;
58
69
  if (cache.get(y) !== subtitleHash) {
59
- writeLine(screen, rect, y, ` ${row.subtitle}`, styles.muted);
70
+ writeLine(screen, bodyRect, y, ` ${row.subtitle}`, styles.muted);
60
71
  cache.set(y, subtitleHash);
61
72
  }
62
73
  y += 1;
63
74
  }
64
75
  }
65
76
  }
77
+ function formatGroupHeader(label, rect) {
78
+ const text = ` ${label} `;
79
+ const labelWidth = cellWidth(text, rect.x);
80
+ if (labelWidth >= rect.width) {
81
+ return label;
82
+ }
83
+ return `${text}${"─".repeat(rect.width - labelWidth)}`;
84
+ }
66
85
  function renderRow(screen, rect, rowY, row, opts) {
67
86
  const styles = getExplorerStyles();
68
87
  const marker = opts.selected ? "┃" : " ";
@@ -0,0 +1,5 @@
1
+ import { ScreenBuffer } from "../../dashboard/buffer.js";
2
+ import type { CellStyle } from "../../dashboard/types.js";
3
+ import type { Rect } from "../layout.js";
4
+ export declare function drawPaneFrame(screen: ScreenBuffer, rect: Rect, title: string, style?: CellStyle): void;
5
+ export declare function paneBodyRect(rect: Rect): Rect;
@@ -0,0 +1,30 @@
1
+ import { fitToWidth, padEndCells } from "./text.js";
2
+ export function drawPaneFrame(screen, rect, title, style = {}) {
3
+ if (rect.width <= 0 || rect.height <= 0) {
4
+ return;
5
+ }
6
+ if (rect.width === 1) {
7
+ for (let y = 0; y < rect.height; y += 1) {
8
+ screen.put(rect.x, rect.y + y, "│", style);
9
+ }
10
+ return;
11
+ }
12
+ const innerWidth = Math.max(0, rect.width - 2);
13
+ const titleSegment = padEndCells(fitToWidth(`─ ${title} `, innerWidth, rect.x + 1), innerWidth, "─", rect.x + 1);
14
+ screen.put(rect.x, rect.y, `┌${titleSegment}┐`, style);
15
+ for (let y = 1; y < rect.height - 1; y += 1) {
16
+ screen.put(rect.x, rect.y + y, "│", style);
17
+ screen.put(rect.x + rect.width - 1, rect.y + y, "│", style);
18
+ }
19
+ if (rect.height > 1) {
20
+ screen.put(rect.x, rect.y + rect.height - 1, `└${"─".repeat(innerWidth)}┘`, style);
21
+ }
22
+ }
23
+ export function paneBodyRect(rect) {
24
+ return {
25
+ x: rect.x + 2,
26
+ y: rect.y + 1,
27
+ width: Math.max(0, rect.width - 4),
28
+ height: Math.max(0, rect.height - 2)
29
+ };
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.70",
3
+ "version": "0.0.72",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-design @poe-code/frontmatter @poe-code/agent-mcp-config @poe-code/agent-human-in-loop @poe-code/task-list @poe-code/agent-defs @poe-code/config-mutations @poe-code/process-runner tiny-mcp-client mcp-oauth auth-store"
48
48
  },
49
49
  "dependencies": {
50
- "toolcraft-schema": "0.0.70",
50
+ "toolcraft-schema": "0.0.72",
51
51
  "commander": "^13.1.0",
52
52
  "fast-string-width": "^3.0.2",
53
53
  "fast-wrap-ansi": "^0.2.0",