@thegitai/cli 1.0.0-preview.14 → 1.0.0-preview.15

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.
@@ -61,6 +61,13 @@ function normalizeChildMessage(raw) {
61
61
  deltaLines: Number(raw.deltaLines ?? raw.delta_lines ?? 0),
62
62
  };
63
63
  }
64
+ if (raw.kind === 'transcriptScrollTo') {
65
+ return {
66
+ op: 'event',
67
+ kind: 'transcriptScrollTo',
68
+ offset: Number(raw.offset ?? 0),
69
+ };
70
+ }
64
71
  if (raw.kind !== 'key') {
65
72
  return null;
66
73
  }
@@ -341,6 +341,26 @@ export function renderTranscriptEntryLines(entry, width) {
341
341
  }
342
342
  return lines;
343
343
  }
344
+ const transcriptEntryLineCache = new WeakMap();
345
+ const MAX_CACHED_TRANSCRIPT_WIDTHS = 2;
346
+ function cachedTranscriptEntryLines(entry, width) {
347
+ let byWidth = transcriptEntryLineCache.get(entry);
348
+ if (!byWidth) {
349
+ byWidth = new Map();
350
+ transcriptEntryLineCache.set(entry, byWidth);
351
+ }
352
+ const cached = byWidth.get(width);
353
+ if (cached)
354
+ return cached;
355
+ const lines = renderTranscriptEntryLines(entry, width);
356
+ if (byWidth.size >= MAX_CACHED_TRANSCRIPT_WIDTHS) {
357
+ const oldest = byWidth.keys().next().value;
358
+ if (oldest !== undefined)
359
+ byWidth.delete(oldest);
360
+ }
361
+ byWidth.set(width, lines);
362
+ return lines;
363
+ }
344
364
  function renderDiffPreviewLines(preview, width, maxDiffLines = TRANSCRIPT_DIFF_PREVIEW_LINES) {
345
365
  return [
346
366
  plainLine(` Added ${preview.added} line${preview.added === 1 ? '' : 's'}, removed ${preview.removed} line${preview.removed === 1 ? '' : 's'}`, { color: 'gray' }),
@@ -1154,14 +1174,18 @@ function sliceTranscriptLines(lines, maxLines, scrollOffset) {
1154
1174
  export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, nowMs = 0) {
1155
1175
  const contentWidth = Math.max(20, Math.floor(cols * 0.95) - 2);
1156
1176
  const gutter = Math.max(Math.floor((cols - contentWidth) / 2), 0);
1157
- const transcriptBlocks = state.transcript.map((entry) => renderTranscriptEntryLines(entry, contentWidth));
1158
- const transcriptLines = [];
1159
- transcriptBlocks.forEach((block, index) => {
1160
- if (index > 0) {
1161
- transcriptLines.push(plainLine(''));
1162
- }
1163
- transcriptLines.push(...block);
1164
- });
1177
+ const buildTranscriptLines = (wrapWidth) => {
1178
+ const blocks = state.transcript.map((entry) => cachedTranscriptEntryLines(entry, wrapWidth));
1179
+ const lines = [];
1180
+ blocks.forEach((block, index) => {
1181
+ if (index > 0) {
1182
+ lines.push(plainLine(''));
1183
+ }
1184
+ lines.push(...block);
1185
+ });
1186
+ return lines;
1187
+ };
1188
+ let transcriptLines = buildTranscriptLines(contentWidth);
1165
1189
  const sections = [];
1166
1190
  const liveLines = buildLiveLines(state, contentWidth, spinnerFrame, elapsedSeconds, nowMs);
1167
1191
  if (liveLines.length > 0) {
@@ -1213,7 +1237,11 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
1213
1237
  ? 0
1214
1238
  : 4;
1215
1239
  const transcriptBudget = Math.max(1, rows - reservedLines - composerReserve - 1);
1216
- const transcriptScrollLimit = Math.max(0, transcriptLines.length - transcriptBudget);
1240
+ let transcriptScrollLimit = Math.max(0, transcriptLines.length - transcriptBudget);
1241
+ if (transcriptScrollLimit > 0 && contentWidth > 2) {
1242
+ transcriptLines = buildTranscriptLines(contentWidth - 2);
1243
+ transcriptScrollLimit = Math.max(0, transcriptLines.length - transcriptBudget);
1244
+ }
1217
1245
  const transcriptScrollOffset = Math.min(Math.max(state.transcriptScrollOffset, 0), transcriptScrollLimit);
1218
1246
  sections.unshift({
1219
1247
  kind: 'transcript',
@@ -275,9 +275,9 @@ function wrapInlineToLines(text, width, bodyColor, prefix = '') {
275
275
  };
276
276
  const appendSpan = (part) => {
277
277
  let current = rows[rows.length - 1];
278
- const limit = safeWidth - (rows.length === 1 && indent ? displayWidth(prefix) : 0);
279
278
  let remaining = part.text;
280
279
  while (remaining.length > 0) {
280
+ const limit = safeWidth - (rows.length === 1 && indent ? displayWidth(prefix) : 0);
281
281
  const room = limit - rowWidth;
282
282
  if (room <= 0) {
283
283
  startRow();
@@ -291,8 +291,17 @@ function wrapInlineToLines(text, width, bodyColor, prefix = '') {
291
291
  remaining = '';
292
292
  break;
293
293
  }
294
+ if (rowWidth > 0) {
295
+ if (/^\s+$/.test(remaining)) {
296
+ remaining = '';
297
+ break;
298
+ }
299
+ startRow();
300
+ current = rows[rows.length - 1];
301
+ continue;
302
+ }
294
303
  const head = sliceToWidth(remaining, room);
295
- if (displayWidth(head) > room && current.length > 0) {
304
+ if (!head || (displayWidth(head) > room && current.length > 0)) {
296
305
  startRow();
297
306
  current = rows[rows.length - 1];
298
307
  continue;
@@ -114,6 +114,16 @@ function scrollTranscript(store, handlers, delta) {
114
114
  };
115
115
  });
116
116
  }
117
+ function scrollTranscriptTo(store, handlers, offset) {
118
+ store.update((current) => {
119
+ const limit = handlers.getTranscriptScrollLimit?.() ??
120
+ current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
121
+ return {
122
+ ...current,
123
+ transcriptScrollOffset: Math.max(0, Math.min(Math.trunc(offset), limit)),
124
+ };
125
+ });
126
+ }
117
127
  function scrollApprovalPreview(store, handlers, delta) {
118
128
  if (delta === 0)
119
129
  return;
@@ -182,6 +192,10 @@ export function handleShellKeyEvent(store, handlers, event) {
182
192
  scrollTranscript(store, handlers, Math.trunc(event.deltaLines));
183
193
  return;
184
194
  }
195
+ if (event.kind === 'transcriptScrollTo') {
196
+ scrollTranscriptTo(store, handlers, Number(event.offset ?? 0));
197
+ return;
198
+ }
185
199
  if (event.kind !== 'key')
186
200
  return;
187
201
  const key = event;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thegitai/cli",
3
- "version": "1.0.0-preview.14",
3
+ "version": "1.0.0-preview.15",
4
4
  "description": "TheGitAI is an AI coding agent for your terminal. It indexes your repository, writes and edits files, runs commands, and builds features with you.",
5
5
  "keywords": [
6
6
  "ai",
@@ -37,10 +37,10 @@
37
37
  "@lydell/node-pty-linux-x64": "1.1.0",
38
38
  "@lydell/node-pty-win32-arm64": "1.1.0",
39
39
  "@lydell/node-pty-win32-x64": "1.1.0",
40
- "@thegitai/tui-darwin-arm64": "1.0.0-preview.14",
41
- "@thegitai/tui-darwin-x64": "1.0.0-preview.14",
42
- "@thegitai/tui-linux-x64": "1.0.0-preview.14",
43
- "@thegitai/tui-win32-x64": "1.0.0-preview.14",
40
+ "@thegitai/tui-darwin-arm64": "1.0.0-preview.15",
41
+ "@thegitai/tui-darwin-x64": "1.0.0-preview.15",
42
+ "@thegitai/tui-linux-x64": "1.0.0-preview.15",
43
+ "@thegitai/tui-win32-x64": "1.0.0-preview.15",
44
44
  "@vscode/ripgrep": "1.18.0"
45
45
  },
46
46
  "publishConfig": {