Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/terminal-scroll-tuning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inkeep/open-knowledge": patch
---

Tune embedded-terminal wheel scrolling to feel closer to a native terminal (e.g. Ghostty). The smooth mouse-tracking scroll added previously was correct but sluggish: macOS bakes velocity acceleration into wheel deltas, and the per-event clamp was clipping the accelerated fast-flick range. The mouse-mode accumulator now uses a modest base sensitivity with a higher per-event cap so OS acceleration carries through — fast flicks travel far while slow drags stay gentle — and normal scrollback gets a faster per-notch travel. Desktop only.
Binary file modified assets/hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion packages/app/src/components/TerminalPanel.dom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,10 @@ describe('TerminalPanel', () => {
expect(terminal.input).toHaveBeenCalledTimes(1);
const [ptyId, payload] = terminal.input.mock.calls[0] as [string, string];
expect(ptyId).toBe('pty-1');
expect(payload).toBe('\x1b[<65;1;1M'.repeat(4));
const downTick = '\x1b[<65;1;1M';
expect(payload.length).toBeGreaterThan(0);
expect(payload.length % downTick.length).toBe(0);
expect(payload.replaceAll(downTick, '')).toBe('');

terminal.input.mockClear();
term.mouseEncoding = 'SGR_PIXELS';
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/components/TerminalPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function TerminalSession({
fontSize: 13,
scrollback: 10000,
smoothScrollDuration: 125,
scrollSensitivity: 3,
theme: xtermThemeForMode(initialResolvedThemeRef.current),
});
termRef.current = term;
Expand Down Expand Up @@ -202,7 +203,7 @@ function TerminalSession({
event.deltaY,
event.deltaMode,
wheelRowAccumulator,
{ cellHeight, sensitivity: 1, maxRowsPerEvent: 4, viewportRows: term.rows },
{ cellHeight, sensitivity: 1.5, maxRowsPerEvent: 20, viewportRows: term.rows },
);
wheelRowAccumulator = accumulator;
if (count > 0) bridge.terminal.input(ptyId, sgrWheelReport(button).repeat(count));
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/handoff/terminal-launch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe('buildCliLaunchCommand', () => {
expect(buildCliLaunchCommand('claude', 'hi')).toBe("claude 'hi'\r");
expect(buildCliLaunchCommand('codex', 'hi')).toBe("codex 'hi'\r");
expect(buildCliLaunchCommand('cursor', 'hi')).toBe("cursor-agent 'hi'\r");
expect(buildCliLaunchCommand('opencode', 'hi')).toBe("opencode 'hi'\r");
expect(buildCliLaunchCommand('opencode', 'hi')).toBe("opencode --prompt 'hi'\r");
});

it('escapes the prompt identically for every CLI regardless of fixed args', () => {
Expand Down Expand Up @@ -111,7 +111,7 @@ describe('claude MCP pre-approval', () => {
"cursor-agent 'hi'\r",
);
expect(buildCliLaunchCommand('opencode', 'hi', { mcpPreApprove: true })).toBe(
"opencode 'hi'\r",
"opencode --prompt 'hi'\r",
);
});

Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/handoff/terminal-launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface TerminalCliInfo {
* the deep-link path) and brand-icon rendering. Single source of truth so
* the renderer doesn't re-declare a parallel `cli → HandoffTarget` map. */
readonly handoffTarget: HandoffTarget;
/** Flag that carries the starting prompt for CLIs whose POSITIONAL argument is
* NOT the prompt. OpenCode's positional is the project directory, so its
* prompt must be passed as `--prompt '<text>'`; claude/codex/cursor take the
* prompt positionally (omit this). When set, {@link buildCliLaunchCommand}
* inserts it immediately before the quoted prompt. */
readonly promptFlag?: string;
}

const CLAUDE_MCP_PREAPPROVE_ARG = `--settings ${shellSingleQuote(
Expand Down Expand Up @@ -56,6 +62,7 @@ export const TERMINAL_CLIS = {
displayName: 'OpenCode',
docsUrl: 'https://opencode.ai/docs',
handoffTarget: 'opencode',
promptFlag: '--prompt',
},
} as const satisfies Record<TerminalCli, TerminalCliInfo>;

Expand All @@ -78,7 +85,8 @@ export function buildCliLaunchCommand(
const info: TerminalCliInfo = TERMINAL_CLIS[cli];
const preApprove =
opts.mcpPreApprove === true && info.mcpPreApproveArg ? `${info.mcpPreApproveArg} ` : '';
return `${info.bin} ${preApprove}${shellSingleQuote(prompt)}\r`;
const promptFlag = info.promptFlag ? `${info.promptFlag} ` : '';
return `${info.bin} ${preApprove}${promptFlag}${shellSingleQuote(prompt)}\r`;
}

export function buildClaudeLaunchCommand(prompt: string, opts: BuildCliLaunchOptions = {}): string {
Expand Down