toolcraft 0.0.170 → 0.0.172

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/composition.json CHANGED
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.170",
116
+ "version": "0.0.172",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "name": "toolcraft-schema",
126
- "version": "0.0.170",
126
+ "version": "0.0.172",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.170",
116
+ "version": "0.0.172",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "name": "toolcraft-schema",
126
- "version": "0.0.170",
126
+ "version": "0.0.172",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.170",
11
+ "version": "0.0.172",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -0,0 +1,6 @@
1
+ import { ansiToCells } from "../screen/ansi-text.js";
2
+ export interface PreparedDetailContent {
3
+ text: string;
4
+ lines: ReturnType<typeof ansiToCells>[];
5
+ }
6
+ export declare function prepareDetailContent(content: string, width: number): PreparedDetailContent;
@@ -0,0 +1,32 @@
1
+ import { ansiToCells } from "../screen/ansi-text.js";
2
+ import { renderMarkdown } from "../terminal-markdown/index.js";
3
+ const markdownCache = new Map();
4
+ export function prepareDetailContent(content, width) {
5
+ if (content.trim().length === 0) {
6
+ return { text: "", lines: [[]] };
7
+ }
8
+ width = Math.max(1, width);
9
+ const key = `${contentHash(content)}:${width}`;
10
+ const cached = markdownCache.get(key);
11
+ if (cached !== undefined)
12
+ return cached;
13
+ const text = renderMarkdown(content, { width }).trimEnd();
14
+ const lines = [[]];
15
+ for (const cell of ansiToCells(text)) {
16
+ if (cell.ch === "\n")
17
+ lines.push([]);
18
+ else
19
+ lines.at(-1).push(cell);
20
+ }
21
+ const prepared = { text, lines };
22
+ markdownCache.set(key, prepared);
23
+ return prepared;
24
+ }
25
+ function contentHash(content) {
26
+ let hash = 2166136261;
27
+ for (let index = 0; index < content.length; index += 1) {
28
+ hash ^= content.charCodeAt(index);
29
+ hash = Math.imul(hash, 16777619);
30
+ }
31
+ return hash >>> 0;
32
+ }
@@ -19,3 +19,4 @@ export interface ExplorerLayout {
19
19
  footer: Rect;
20
20
  }
21
21
  export declare function computeExplorerLayout(opts: ExplorerLayoutOptions): ExplorerLayout;
22
+ export declare function paneBodyRect(rect: Rect): Rect;
@@ -62,6 +62,14 @@ export function computeExplorerLayout(opts) {
62
62
  footer
63
63
  };
64
64
  }
65
+ export function paneBodyRect(rect) {
66
+ return {
67
+ x: rect.x + 2,
68
+ y: rect.y + 1,
69
+ width: Math.max(0, rect.width - 4),
70
+ height: Math.max(0, rect.height - 2)
71
+ };
72
+ }
65
73
  function resolveMode(cols, rows) {
66
74
  if (cols < 60 || rows < 8) {
67
75
  return "too-narrow";
@@ -1,6 +1,8 @@
1
1
  import { graphemes } from "../dashboard/terminal-width.js";
2
2
  import { buildActionContext, resolveAction } from "./actions.js";
3
+ import { prepareDetailContent } from "./detail-content.js";
3
4
  import { filterRows } from "./filter.js";
5
+ import { computeExplorerLayout, paneBodyRect } from "./layout.js";
4
6
  import { REGION_ALL, REGION_DETAIL, REGION_FOOTER, REGION_HEADER, REGION_LIST, REGION_MODAL, resolveExplorerLayoutMode } from "./state.js";
5
7
  const NO_EFFECTS = [];
6
8
  const DEFAULT_ACTION_HANDLES = {
@@ -640,31 +642,18 @@ function maxDetailScroll(state) {
640
642
  return 0;
641
643
  }
642
644
  if (items.length === 1 && items[0]?.title === undefined) {
643
- const visibleHeight = detailBodyHeight(state);
644
- if (visibleHeight <= 0) {
645
+ const body = paneBodyRect(computeExplorerLayout({ ...state.size, focused: state.focused }).detail);
646
+ const content = items[0]?.renderedContent;
647
+ if (body.width <= 0 || body.height <= 0 || content === undefined) {
645
648
  return 0;
646
649
  }
647
- return Math.max(0, detailContentLineCount(items[0]) - visibleHeight);
650
+ return Math.max(0, prepareDetailContent(content, body.width).lines.length - body.height);
648
651
  }
649
652
  return Math.max(0, items.length - 1);
650
653
  }
651
- function detailContentLineCount(item) {
652
- return (item.renderedContent ?? "").split("\n").length;
653
- }
654
654
  function detailBodyHeight(state) {
655
- if (state.layout === "too-narrow" || state.layout === "narrow-list-only") {
656
- return 0;
657
- }
658
- const rows = normalizeSize(state.size.rows);
659
- const footerHeight = rows > 0 ? Math.min(1, rows) : 0;
660
- const headerHeight = Math.min(3, Math.max(0, rows - footerHeight));
661
- const contentHeight = Math.max(0, rows - headerHeight - footerHeight);
662
- if (state.layout === "narrow-vertical") {
663
- const listHeight = Math.ceil(contentHeight / 2);
664
- const detailHeight = contentHeight - listHeight;
665
- return Math.max(0, detailHeight - 2);
666
- }
667
- return Math.max(0, contentHeight - 2);
655
+ const body = paneBodyRect(computeExplorerLayout({ ...state.size, focused: state.focused }).detail);
656
+ return body.width > 0 ? body.height : 0;
668
657
  }
669
658
  function extendSelection(state, delta) {
670
659
  if (!state.multiSelect) {
@@ -1,4 +1,4 @@
1
1
  import type { ScreenSurface as ScreenBuffer } from "../../screen/screen.js";
2
- import type { ExplorerLayout } from "../layout.js";
2
+ import { type ExplorerLayout } from "../layout.js";
3
3
  import type { ExplorerState } from "../state.js";
4
4
  export declare function renderDetail(state: ExplorerState, screen: ScreenBuffer, layout: ExplorerLayout): void;
@@ -1,9 +1,8 @@
1
- import { ansiToCells } from "../../screen/ansi-text.js";
2
- import { renderMarkdown } from "../../terminal-markdown/index.js";
1
+ import { prepareDetailContent } from "../detail-content.js";
2
+ import { paneBodyRect } from "../layout.js";
3
3
  import { getExplorerStyles } from "../theme.js";
4
- import { drawPaneFrame, paneBodyRect } from "./pane.js";
4
+ import { drawPaneFrame } from "./pane.js";
5
5
  import { fitToWidth } from "./text.js";
6
- const markdownCache = new Map();
7
6
  export function renderDetail(state, screen, layout) {
8
7
  const rect = layout.detail;
9
8
  const styles = getExplorerStyles();
@@ -14,38 +13,34 @@ export function renderDetail(state, screen, layout) {
14
13
  const row = state.rows.find((r) => r.id === state.detail.rowId) ?? null;
15
14
  const pane = state.paneDefinitions[1];
16
15
  const title = pane?.titleForRow?.(row ?? undefined) ?? pane?.title ?? "Preview";
17
- if (layout.mode === "narrow-vertical") {
18
- drawPaneFrame(screen, rect, title, state.focused === "detail" ? styles.borderFocused : styles.border, { focused: state.focused === "detail", indicator: scrollIndicator(state) });
19
- renderDetailBody(state, screen, paneBodyRect(rect), row);
20
- return;
21
- }
22
- drawPaneFrame(screen, rect, title, state.focused === "detail" ? styles.borderFocused : styles.border, { focused: state.focused === "detail", indicator: scrollIndicator(state) });
23
- renderDetailBody(state, screen, paneBodyRect(rect), row);
16
+ const { start, max } = renderDetailBody(state, screen, paneBodyRect(rect), row);
17
+ drawPaneFrame(screen, rect, title, state.focused === "detail" ? styles.borderFocused : styles.border, { focused: state.focused === "detail", indicator: scrollIndicator(state, start, max) });
24
18
  }
25
19
  function renderDetailBody(state, screen, rect, row) {
26
20
  const styles = getExplorerStyles();
27
21
  const items = state.detail.items;
28
22
  if (rect.width <= 0 || rect.height <= 0) {
29
- return;
23
+ return { start: 0, max: 0 };
30
24
  }
31
25
  if (items === null) {
32
26
  writeLine(screen, rect, 0, row === null ? state.emptyHint : "Loading detail...", styles.muted);
33
- return;
27
+ return { start: 0, max: 0 };
34
28
  }
35
29
  if (items.length === 0) {
36
30
  writeLine(screen, rect, 0, state.emptyHint, styles.muted);
37
- return;
31
+ return { start: 0, max: 0 };
38
32
  }
39
33
  if (items.length === 1 && items[0]?.title === undefined) {
40
- renderBlob(screen, rect, renderItemMarkdown(items[0], rect, row), state.detail.scroll);
41
- return;
34
+ const content = prepareDetailContent(renderItem(items[0], rect, row), rect.width);
35
+ return renderBlob(screen, rect, content.lines, state.detail.scroll);
42
36
  }
43
- renderListMode(state, screen, rect, items, row);
37
+ return renderListMode(state, screen, rect, items, row);
44
38
  }
45
39
  function renderListMode(state, screen, rect, items, row) {
46
40
  const styles = getExplorerStyles();
47
41
  let y = 0;
48
- const start = clamp(state.detail.scroll, 0, Math.max(0, items.length - 1));
42
+ const max = Math.max(0, items.length - 1);
43
+ const start = clamp(state.detail.scroll, 0, max);
49
44
  for (let index = start; index < items.length && y < rect.height; index += 1) {
50
45
  const item = items[index];
51
46
  const cursor = index === state.detail.cursor;
@@ -59,7 +54,8 @@ function renderListMode(state, screen, rect, items, row) {
59
54
  writeLine(screen, rect, y, ` ${item.subtitle}`, styles.muted);
60
55
  y += 1;
61
56
  }
62
- for (const line of renderItemMarkdown(item, rect, row).split("\n")) {
57
+ const content = prepareDetailContent(renderItem(item, rect, row), rect.width);
58
+ for (const line of content.text.split("\n")) {
63
59
  if (y >= rect.height) {
64
60
  break;
65
61
  }
@@ -70,16 +66,11 @@ function renderListMode(state, screen, rect, items, row) {
70
66
  y += 1;
71
67
  }
72
68
  }
69
+ return { start, max };
73
70
  }
74
- function renderBlob(screen, rect, text, scroll) {
75
- const allLines = [[]];
76
- for (const cell of ansiToCells(text)) {
77
- if (cell.ch === "\n")
78
- allLines.push([]);
79
- else
80
- allLines.at(-1).push(cell);
81
- }
82
- const start = clamp(scroll, 0, Math.max(0, allLines.length - rect.height));
71
+ function renderBlob(screen, rect, allLines, scroll) {
72
+ const max = Math.max(0, allLines.length - rect.height);
73
+ const start = clamp(scroll, 0, max);
83
74
  const lines = allLines.slice(start);
84
75
  for (let row = 0; row < rect.height; row += 1) {
85
76
  let x = rect.x;
@@ -90,20 +81,7 @@ function renderBlob(screen, rect, text, scroll) {
90
81
  x += cell.width;
91
82
  }
92
83
  }
93
- }
94
- function renderItemMarkdown(item, rect, row) {
95
- const content = renderItem(item, rect, row);
96
- if (content.trim().length === 0) {
97
- return "";
98
- }
99
- const width = Math.max(1, rect.width);
100
- const key = `${contentHash(content)}:${width}`;
101
- const cached = markdownCache.get(key);
102
- if (cached !== undefined)
103
- return cached;
104
- const rendered = renderMarkdown(content, { width }).trimEnd();
105
- markdownCache.set(key, rendered);
106
- return rendered;
84
+ return { start, max };
107
85
  }
108
86
  function renderItem(item, rect, row) {
109
87
  if (item.renderedContent !== undefined) {
@@ -131,18 +109,8 @@ function writeLine(screen, rect, row, text, style = {}) {
131
109
  function clamp(value, min, max) {
132
110
  return Math.min(max, Math.max(min, value));
133
111
  }
134
- function scrollIndicator(state) {
112
+ function scrollIndicator(state, start, max) {
135
113
  if (state.detail.loading)
136
114
  return "⠋";
137
- const content = state.detail.items?.[state.detail.cursor]?.renderedContent ?? "";
138
- const total = Math.max(1, content.split("\n").length);
139
- return `${Math.min(100, Math.round((state.detail.scroll / Math.max(1, total - 1)) * 100))}%`;
140
- }
141
- function contentHash(content) {
142
- let hash = 2166136261;
143
- for (let index = 0; index < content.length; index += 1) {
144
- hash ^= content.charCodeAt(index);
145
- hash = Math.imul(hash, 16777619);
146
- }
147
- return hash >>> 0;
115
+ return `${max === 0 ? 0 : Math.round((start / max) * 100)}%`;
148
116
  }
@@ -5,4 +5,4 @@ export declare function drawPaneFrame(screen: ScreenBuffer, rect: Rect, title: s
5
5
  focused?: boolean;
6
6
  indicator?: string;
7
7
  }): void;
8
- export declare function paneBodyRect(rect: Rect): Rect;
8
+ export { paneBodyRect } from "../layout.js";
@@ -23,11 +23,4 @@ export function drawPaneFrame(screen, rect, title, style = {}, options = {}) {
23
23
  screen.put(rect.x, rect.y + rect.height - 1, `${options.focused ? "┗" : "└"}${horizontal.repeat(innerWidth)}${options.focused ? "┛" : "┘"}`, style);
24
24
  }
25
25
  }
26
- export function paneBodyRect(rect) {
27
- return {
28
- x: rect.x + 2,
29
- y: rect.y + 1,
30
- width: Math.max(0, rect.width - 4),
31
- height: Math.max(0, rect.height - 2)
32
- };
33
- }
26
+ export { paneBodyRect } from "../layout.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.170",
3
+ "version": "0.0.172",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.170",
3
+ "version": "0.0.172",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -158,7 +158,7 @@
158
158
  "yaml"
159
159
  ],
160
160
  "optionalDependencies": {
161
- "toolcraft-schema": "0.0.170",
161
+ "toolcraft-schema": "0.0.172",
162
162
  "toolcraft-design": "*",
163
163
  "@poe-code/frontmatter": "*",
164
164
  "@poe-code/agent-mcp-config": "*",