praxis-agent 0.21.4 → 0.21.5

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.
@@ -16,7 +16,7 @@ import { loadTuiMemoryFiles, openTuiMemoryFolder, } from './tui/memory-files.js'
16
16
  import { loadClaudeReleaseNotes } from './tui/release-notes.js';
17
17
  import { StreamingFrameBuffer } from './tui/streaming-frame-buffer.js';
18
18
  import { createClaudeStatusLineInput, StatusLine } from './tui/status-line.js';
19
- import { FULLSCREEN_TRANSCRIPT_RESERVED_ROWS, projectTranscriptTail, } from './tui/transcript-viewport.js';
19
+ import { FULLSCREEN_TRANSCRIPT_RESERVED_ROWS, projectTranscriptTail, projectTranscriptWindow, transcriptLineCount, } from './tui/transcript-viewport.js';
20
20
  import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
21
21
  import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
22
22
  import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
@@ -431,6 +431,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
431
431
  const [costUsd, setCostUsd] = useState();
432
432
  const [contextWindowTokens, setContextWindowTokens] = useState(display.contextWindowTokens);
433
433
  const [history, setHistory] = useState([...initialHistory]);
434
+ const [transcriptScrollOffset, setTranscriptScrollOffset] = useState(0);
434
435
  // Startup diagnostics are useful before the first prompt, but they are not
435
436
  // conversation history and must not suppress the new-session welcome panel.
436
437
  // Only real user/assistant transcript entries start a conversation; every
@@ -453,7 +454,9 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
453
454
  // stream visible. Classic and screen-reader modes always render the full
454
455
  // history exactly as before.
455
456
  const projectedHistory = fixedViewport && !axScreenReader
456
- ? projectTranscriptTail(history, Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS), width)
457
+ ? transcriptScrollOffset > 0
458
+ ? projectTranscriptWindow(history, Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS), width, transcriptScrollOffset)
459
+ : projectTranscriptTail(history, Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS), width)
457
460
  : history;
458
461
  const sessionLoadRef = useRef(0);
459
462
  const [turnDiffs, setTurnDiffs] = useState([]);
@@ -721,6 +724,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
721
724
  // transcript state so the active stream never renders below a tool,
722
725
  // thinking, or completion entry that it textually precedes.
723
726
  streamingFrameRef.current?.flush();
727
+ setTranscriptScrollOffset(0);
724
728
  setHistory((current) => [...current, line]);
725
729
  };
726
730
  useEffect(() => {
@@ -2590,6 +2594,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2590
2594
  void restoring.finally(() => onTurnChange?.(null));
2591
2595
  };
2592
2596
  const submit = async (prompt, shellCommand, images = []) => {
2597
+ setTranscriptScrollOffset(0);
2593
2598
  const turnNumber = turnNumberRef.current + 1;
2594
2599
  const turnStartedAt = Date.now();
2595
2600
  turnNumberRef.current = turnNumber;
@@ -2898,6 +2903,33 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2898
2903
  return;
2899
2904
  }
2900
2905
  const isKeybinding = (action) => keybindingAction === action;
2906
+ if (fixedViewport &&
2907
+ menuRef.current === null &&
2908
+ !permission &&
2909
+ !planApproval &&
2910
+ !question &&
2911
+ !elicitation &&
2912
+ !selectingSession) {
2913
+ const page = Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS);
2914
+ const scrollDelta = key.pageUp
2915
+ ? page
2916
+ : key.pageDown
2917
+ ? -page
2918
+ : controlKey('u') || controlKey('b')
2919
+ ? page / 2
2920
+ : controlKey('f') || controlKey('n')
2921
+ ? -page / 2
2922
+ : inputRef.current.length === 0 && key.upArrow
2923
+ ? 1
2924
+ : inputRef.current.length === 0 && key.downArrow
2925
+ ? -1
2926
+ : 0;
2927
+ if (scrollDelta !== 0) {
2928
+ setTranscriptScrollOffset((current) => Math.min(Math.max(0, transcriptLineCount(history, width) -
2929
+ Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS)), Math.max(0, current + Math.trunc(scrollDelta))));
2930
+ return;
2931
+ }
2932
+ }
2901
2933
  if (runtimeSettingsRef.current.editor === 'vim' &&
2902
2934
  !permission &&
2903
2935
  !planApproval &&
@@ -19,6 +19,7 @@ export declare const TRANSCRIPT_TRUNCATION_MARKER = "\u2026";
19
19
  * rather than dropping items that would actually render.
20
20
  */
21
21
  export declare function estimateTranscriptLines(item: TranscriptItem, width: number): number;
22
+ export declare function transcriptLineCount(items: readonly TranscriptItem[], width: number): number;
22
23
  /**
23
24
  * Pure, deterministic suffix projector for the fullscreen transcript region.
24
25
  *
@@ -31,4 +32,6 @@ export declare function estimateTranscriptLines(item: TranscriptItem, width: num
31
32
  * that fit and the input is never mutated.
32
33
  */
33
34
  export declare function projectTranscriptTail(items: readonly TranscriptItem[], budget: number, width: number): readonly TranscriptItem[];
35
+ /** Projects a fixed-size transcript window, measured upward from the newest row. */
36
+ export declare function projectTranscriptWindow(items: readonly TranscriptItem[], budget: number, width: number, scrollOffset: number): readonly TranscriptItem[];
34
37
  //# sourceMappingURL=transcript-viewport.d.ts.map
@@ -117,6 +117,9 @@ export function estimateTranscriptLines(item, width) {
117
117
  return wrappedLineCount(bounded(`${item.stdout}\n${item.stderr}`, 500), width);
118
118
  }
119
119
  }
120
+ export function transcriptLineCount(items, width) {
121
+ return items.reduce((total, item) => total + estimateTranscriptLines(item, width), 0);
122
+ }
120
123
  /**
121
124
  * Pure, deterministic suffix projector for the fullscreen transcript region.
122
125
  *
@@ -153,4 +156,29 @@ export function projectTranscriptTail(items, budget, width) {
153
156
  }
154
157
  return items.slice(start);
155
158
  }
159
+ /** Projects a fixed-size transcript window, measured upward from the newest row. */
160
+ export function projectTranscriptWindow(items, budget, width, scrollOffset) {
161
+ if (scrollOffset <= 0)
162
+ return projectTranscriptTail(items, budget, width);
163
+ const endRows = Math.max(0, transcriptLineCount(items, width) - scrollOffset);
164
+ const startRows = Math.max(0, endRows - budget);
165
+ let rows = 0;
166
+ let start = 0;
167
+ let end = 0;
168
+ for (let index = 0; index < items.length; index += 1) {
169
+ const item = items[index];
170
+ if (!item)
171
+ break;
172
+ const nextRows = rows + estimateTranscriptLines(item, width);
173
+ if (nextRows <= startRows) {
174
+ start = index + 1;
175
+ }
176
+ if (rows < endRows)
177
+ end = index + 1;
178
+ rows = nextRows;
179
+ if (rows >= endRows)
180
+ break;
181
+ }
182
+ return items.slice(start, end);
183
+ }
156
184
  //# sourceMappingURL=transcript-viewport.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.21.4",
3
+ "version": "0.21.5",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",