klyro 1.0.12 → 1.0.13
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/dist/cli/repl.js +1 -1
- package/dist/tui/app.js +10 -4
- package/dist/tui/app.test.js +23 -12
- package/package.json +1 -1
package/dist/cli/repl.js
CHANGED
|
@@ -2689,7 +2689,7 @@ export async function startRepl(opts = {}) {
|
|
|
2689
2689
|
' Enter send · Shift+Enter newline · Tab complete slash · Esc drop queued / Esc×2 cancel run',
|
|
2690
2690
|
' Ctrl+C cancel (1st) / quit (2nd) · Ctrl+O expand last tool group · Ctrl+G jump bottom',
|
|
2691
2691
|
' PgUp/PgDn or Ctrl+U/Ctrl+D half-page · Ctrl+Home/End top/bottom · Home/End jump · Space jump to unread',
|
|
2692
|
-
' Ctrl+B/F page · Shift/Ctrl+↑/↓ line ·
|
|
2692
|
+
' Ctrl+B/F page · Shift/Ctrl+↑/↓ line · ↑ history at live tail, scrolls while reading above · PgUp/Dn scroll (KLYRO_MOUSE=1 adds wheel ±3 lines)',
|
|
2693
2693
|
' Text selection/copy and right-click paste work natively · Shift+Enter newline · /vim toggles vim input mode · /keymap <note> saves a display note',
|
|
2694
2694
|
].join('\n'),
|
|
2695
2695
|
});
|
package/dist/tui/app.js
CHANGED
|
@@ -1052,11 +1052,17 @@ export function App(props) {
|
|
|
1052
1052
|
void props.onSlash({ kind: 'quit' });
|
|
1053
1053
|
return;
|
|
1054
1054
|
}
|
|
1055
|
-
// Contextual ↑/↓ (§8.3):
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
// the
|
|
1055
|
+
// Contextual ↑/↓ (§8.3): both behaviors, split by where you are.
|
|
1056
|
+
// Scrolled up reading the transcript (not at the live tail, not already
|
|
1057
|
+
// browsing) → plain ↑ keeps scrolling so chat scroll is never lost.
|
|
1058
|
+
// At the live tail (or no scroll surface) → ↑ browses input history
|
|
1059
|
+
// newest-first, so send + ↑ restores the last prompt. ↓ is unchanged:
|
|
1060
|
+
// browse newer while browsing, else scroll toward the tail.
|
|
1059
1061
|
if (key.upArrow && !key.shift && !key.ctrl) {
|
|
1062
|
+
if (isFullscreen && maxTop > 0 && !atBottom && histIdx === null && input.trim() === '') {
|
|
1063
|
+
commands.lineUp();
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1060
1066
|
if (history.length > 0) {
|
|
1061
1067
|
const next = histIdx === null ? history.length - 1 : Math.max(0, histIdx - 1);
|
|
1062
1068
|
setHistIdx(next);
|
package/dist/tui/app.test.js
CHANGED
|
@@ -257,29 +257,40 @@ describe('App', () => {
|
|
|
257
257
|
await new Promise((r) => setTimeout(r, 30));
|
|
258
258
|
expect(lastFrame() ?? '').toMatch(/MSG-00-tag/);
|
|
259
259
|
});
|
|
260
|
-
it('↑
|
|
260
|
+
it('↑ at live tail recalls history; scrolled up it scrolls (both kept)', async () => {
|
|
261
261
|
const onPrompt = vi.fn(async () => { });
|
|
262
|
-
const { stdin, lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: onPrompt, onSlash: async () => { } }));
|
|
263
|
-
|
|
262
|
+
const { stdin, lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: onPrompt, onSlash: async () => { }, isFullscreen: true, initialTranscript: makeInitialTranscript(25) }));
|
|
263
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
264
|
+
stdin.write('zzrecallme');
|
|
264
265
|
await new Promise((r) => setTimeout(r, 20));
|
|
265
266
|
stdin.write('\x0d');
|
|
266
267
|
await new Promise((r) => setTimeout(r, 50));
|
|
267
|
-
|
|
268
|
+
expect(onPrompt).toHaveBeenCalledWith('zzrecallme');
|
|
269
|
+
// At the live tail, empty input + ↑ recalls into the input line: typing
|
|
270
|
+
// '!' must extend the recalled text (only the input holds 'zzrecallme!').
|
|
271
|
+
stdin.write('\x1b[A');
|
|
272
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
273
|
+
stdin.write('!');
|
|
274
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
275
|
+
expect(lastFrame() ?? '').toContain('zzrecallme!');
|
|
276
|
+
});
|
|
277
|
+
it('↑ while scrolled up keeps scrolling and leaves input empty', async () => {
|
|
278
|
+
const { stdin, lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, isFullscreen: true, initialTranscript: makeInitialTranscript(25) }));
|
|
279
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
280
|
+
stdin.write('zznoscroll');
|
|
268
281
|
await new Promise((r) => setTimeout(r, 20));
|
|
269
282
|
stdin.write('\x0d');
|
|
270
283
|
await new Promise((r) => setTimeout(r, 50));
|
|
271
|
-
|
|
272
|
-
// Input is empty after each submit; plain ↑ must recall, not scroll.
|
|
273
|
-
stdin.write('\x1b[A');
|
|
284
|
+
stdin.write(KEY_HOME);
|
|
274
285
|
await new Promise((r) => setTimeout(r, 30));
|
|
275
|
-
|
|
286
|
+
// Scrolled up + empty input + ↑ → scroll (MSG-00 stays), input untouched:
|
|
287
|
+
// typing '!' must NOT extend the submitted prompt.
|
|
276
288
|
stdin.write('\x1b[A');
|
|
277
289
|
await new Promise((r) => setTimeout(r, 30));
|
|
278
|
-
expect(lastFrame() ?? '').
|
|
279
|
-
|
|
280
|
-
stdin.write('\x1b[B');
|
|
290
|
+
expect(lastFrame() ?? '').toMatch(/MSG-00-tag/);
|
|
291
|
+
stdin.write('!');
|
|
281
292
|
await new Promise((r) => setTimeout(r, 30));
|
|
282
|
-
expect(lastFrame() ?? '').toContain('
|
|
293
|
+
expect(lastFrame() ?? '').not.toContain('zznoscroll!');
|
|
283
294
|
});
|
|
284
295
|
it('/c shows top-6 suggestions and Tab completes', async () => {
|
|
285
296
|
const { stdin, lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { } }));
|