mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 08:02:38 +00:00
fix(terminal): publish the gutter-trim setting to paired clients
settings.get is an explicit allowlist projection, not the whole settings object, so terminalCopyTrimsGutter never reached mobile: the client read the key as absent, which means "older host", which means on. Mobile therefore always trimmed and the desktop opt-out was inert. Adds the field to the projection and a test that fails if it is ever dropped again — absence is indistinguishable on the client from an old host, so a silent regression here has no other signal.
This commit is contained in:
+1
-1
@@ -53,7 +53,7 @@ allowBuilds:
|
||||
node-pty: true
|
||||
sherpa-onnx: true
|
||||
ssh2: false
|
||||
"@orca/windows-registry": false
|
||||
'@orca/windows-registry': false
|
||||
|
||||
overrides:
|
||||
monaco-editor>dompurify: 3.4.14
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { RuntimeClientSettingsController } from './runtime-client-settings'
|
||||
import { createGlobalSettingsFixture } from '../../shared/global-settings-test-fixture'
|
||||
import type { GlobalSettings } from '../../shared/global-settings-types'
|
||||
|
||||
// Why: `settings.get` is an explicit allowlist, not the whole settings object.
|
||||
// Mobile's terminal Copy reads terminalCopyTrimsGutter from it (#19770), and a
|
||||
// field missing here is indistinguishable on the client from an older host —
|
||||
// so the opt-out would silently never arrive.
|
||||
function projectionOf(settings: Partial<GlobalSettings>) {
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: get() reads nothing but store.getSettings(); every other RuntimeStore member is unreachable from that path.
|
||||
return new RuntimeClientSettingsController({ getSettings: () => settings } as never).get()
|
||||
}
|
||||
|
||||
function hostSettings(overrides: Partial<GlobalSettings>): Partial<GlobalSettings> {
|
||||
return { ...createGlobalSettingsFixture({ workspaceDir: '/w' }), ...overrides }
|
||||
}
|
||||
|
||||
describe('RuntimeClientSettingsController terminal copy projection', () => {
|
||||
it('publishes the gutter-trim opt-out to paired clients', () => {
|
||||
expect(
|
||||
projectionOf(hostSettings({ terminalCopyTrimsGutter: false })).terminalCopyTrimsGutter
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('publishes the gutter-trim opt-in to paired clients', () => {
|
||||
expect(
|
||||
projectionOf(hostSettings({ terminalCopyTrimsGutter: true })).terminalCopyTrimsGutter
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('reports on when the host has no persisted preference', () => {
|
||||
const settings = hostSettings({})
|
||||
delete settings.terminalCopyTrimsGutter
|
||||
expect(projectionOf(settings).terminalCopyTrimsGutter).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -27,6 +27,7 @@ export type RuntimeClientSettings = Pick<
|
||||
| 'agentDefaultArgs'
|
||||
| 'agentDefaultEnv'
|
||||
| 'agentStatusHooksEnabled'
|
||||
| 'terminalCopyTrimsGutter'
|
||||
| 'defaultTaskSource'
|
||||
| 'defaultTaskViewPreset'
|
||||
| 'visibleTaskProviders'
|
||||
@@ -97,6 +98,9 @@ export class RuntimeClientSettingsController {
|
||||
agentDefaultArgs: settings.agentDefaultArgs ?? {},
|
||||
agentDefaultEnv: settings.agentDefaultEnv ?? {},
|
||||
agentStatusHooksEnabled: settings.agentStatusHooksEnabled !== false,
|
||||
// Why projected: mobile's terminal Copy honours this, and a host predating
|
||||
// the setting sends no key, which the client reads as on (#19770).
|
||||
terminalCopyTrimsGutter: settings.terminalCopyTrimsGutter !== false,
|
||||
defaultTaskSource: settings.defaultTaskSource ?? 'github',
|
||||
defaultTaskViewPreset: settings.defaultTaskViewPreset ?? 'issues',
|
||||
visibleTaskProviders: settings.visibleTaskProviders ?? [...TASK_PROVIDERS],
|
||||
|
||||
@@ -87,6 +87,7 @@ export type RuntimeStore = {
|
||||
terminalWindowsShell?: GlobalSettings['terminalWindowsShell']
|
||||
floatingTerminalEnabled?: GlobalSettings['floatingTerminalEnabled']
|
||||
agentStatusHooksEnabled?: GlobalSettings['agentStatusHooksEnabled']
|
||||
terminalCopyTrimsGutter?: GlobalSettings['terminalCopyTrimsGutter']
|
||||
experimentalNativeChat?: GlobalSettings['experimentalNativeChat']
|
||||
openAgentTabsInChatByDefault?: GlobalSettings['openAgentTabsInChatByDefault']
|
||||
experimentalStructuredNativeChat?: GlobalSettings['experimentalStructuredNativeChat']
|
||||
|
||||
@@ -108,7 +108,6 @@ describe('scanSourceTree filesystem traversal', () => {
|
||||
expect(statSync).toHaveBeenCalledExactlyOnceWith(join(root, 'alias'))
|
||||
})
|
||||
|
||||
|
||||
it('still reports a broken link instead of silently dropping it', () => {
|
||||
const target = join(root, '.target')
|
||||
mkdirSync(target)
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
// selection that starts mid-line, or that covers any column-0 line, shares a
|
||||
// run of zero and comes back untouched.
|
||||
|
||||
// Terminal cells never hold tabs (the emulator expands them) and xterm folds
|
||||
// non-breaking spaces into plain ones, so spaces are the whole alphabet here.
|
||||
// Spaces are the whole alphabet here: terminal cells never hold tabs (the
|
||||
// emulator expands them), and xterm's selectionText getter already folds every
|
||||
// NBSP cell to a plain space on its way out (SelectionService.ts, the
|
||||
// ALL_NON_BREAKING_SPACE_REGEX replace) — that is the selection path, not the
|
||||
// input path.
|
||||
const LEADING_SPACES = /^ */
|
||||
|
||||
type SelectionLine = { indent: number; text: string; terminator: string }
|
||||
|
||||
Reference in New Issue
Block a user