Files
orca/src/main/runtime/rpc/methods/client-ui.ts
T
Jinjing 996284241d Implement resizable Markdown table of contents panel (#5444)
* Implement resizable Markdown table of contents panel

Introduce resizable behavior to the Markdown table of contents panel,
allowing users to adjust its width via a draggable handle.

- Persist the custom panel width in the UI state and main process store.
- Clamp the width to a safe range (200px to 600px) based on layout space.
- Ensure the remaining editor workspace does not shrink below 320px
  by computing constraints dynamically against the parent container width.

* Clone process env without undefined values for Windows preflight

- Filter out undefined values when cloning process.env to prevent
  potential serialization or typing issues.
- Update buildLocalPreflightEnv return type to Record<string, string>.
2026-06-15 19:59:44 -07:00

264 lines
9.6 KiB
TypeScript

import { z } from 'zod'
import {
isFeatureInteractionId,
type FeatureInteractionId
} from '../../../../shared/feature-interactions'
import { isFeatureTipId } from '../../../../shared/feature-tips'
import {
normalizeTuiAgentArgsRecord,
normalizeTuiAgentEnvRecord
} from '../../../../shared/tui-agent-launch-defaults'
import { isTuiAgent } from '../../../../shared/tui-agent-config'
import { isTaskProvider } from '../../../../shared/task-providers'
import { normalizeDisabledTuiAgents } from '../../../../shared/tui-agent-selection'
import type { PersistedUIState, TaskProvider } from '../../../../shared/types'
import { defineMethod, type RpcMethod } from '../core'
const NullableString = z.string().nullable()
const StringArray = z.array(z.string())
const TaskProviderParam = z.custom<TaskProvider>(isTaskProvider, {
message: 'Unknown task provider'
})
const FeatureTipIds = z.array(z.custom(isFeatureTipId, { message: 'Unknown feature tip id' }))
const UnknownRecord = z.record(z.string(), z.unknown())
const UnknownRecordArray = z.array(UnknownRecord)
const WorktreeCardProperty = z.enum([
'status',
'unread',
'ci',
'issue',
'linear-issue',
'pr',
'comment',
'ports',
'inline-agents'
])
const AgentActivityDisplayMode = z.enum(['compact', 'full'])
const StatusBarItem = z.enum(['claude', 'codex', 'gemini', 'opencode-go', 'ssh', 'resource-usage'])
const WorkspaceStatusDefinition = z.object({
id: z.string(),
label: z.string(),
color: z.string().optional(),
icon: z.string().optional()
})
const TaskResumeState = z
.object({
githubMode: z.enum(['items', 'project']).optional(),
githubItemsPreset: z.string().nullable().optional(),
githubItemsQuery: z.string().optional(),
githubProjectHiddenFieldIdsByView: z.record(z.string(), z.array(z.string())).optional(),
linearPreset: z.enum(['assigned', 'created', 'all', 'completed']).optional(),
linearQuery: z.string().optional()
})
.strict()
const WorkspaceCleanupDismissal = z
.object({
worktreeId: z.string(),
dismissedAt: z.number().finite(),
fingerprint: z.string(),
classifierVersion: z.number().finite()
})
.strict()
const WorkspaceCleanup = z
.object({
dismissals: z.record(z.string(), WorkspaceCleanupDismissal)
})
.strict()
const FeatureInteractionRecord = z
.object({
firstInteractedAt: z.number().finite().nonnegative(),
interactionCount: z.number().int().positive().optional()
})
.strict()
const FeatureInteractions = z
.record(z.string(), FeatureInteractionRecord)
.superRefine((value, ctx) => {
for (const id of Object.keys(value)) {
if (!isFeatureInteractionId(id)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unknown feature interaction id: ${id}`,
path: [id]
})
}
}
})
const FeatureInteractionIdParam = z.custom<FeatureInteractionId>(isFeatureInteractionId, {
message: 'Unknown feature interaction id'
})
const GitHubProjectRef = z
.object({
owner: z.string(),
ownerType: z.enum(['organization', 'user']),
number: z.number().int()
})
.strict()
const GitHubProjectSettings = z
.object({
pinned: z.array(GitHubProjectRef),
recent: z.array(
GitHubProjectRef.extend({
lastOpenedAt: z.string()
}).strict()
),
lastViewByProject: z.record(z.string(), z.object({ viewId: z.string() }).strict()),
activeProject: GitHubProjectRef.nullable()
})
.strict()
const SettingsUpdate = z
.object({
defaultTuiAgent: z
.unknown()
.transform((value) =>
value === null || value === 'blank' || isTuiAgent(value) ? value : undefined
)
.optional(),
disabledTuiAgents: z
.unknown()
.transform((value) => normalizeDisabledTuiAgents(value))
.optional(),
agentDefaultArgs: z
.unknown()
.transform((value) => normalizeTuiAgentArgsRecord(value))
.optional(),
agentDefaultEnv: z
.unknown()
.transform((value) => normalizeTuiAgentEnvRecord(value))
.optional(),
defaultTaskSource: TaskProviderParam.optional(),
visibleTaskProviders: z.array(TaskProviderParam).optional(),
defaultTaskViewPreset: z
.enum(['issues', 'my-issues', 'prs', 'my-prs', 'review', 'all'])
.optional(),
agentStatusHooksEnabled: z.boolean().optional(),
defaultRepoSelection: z.array(z.string()).nullable().optional(),
defaultLinearTeamSelection: z.array(z.string()).nullable().optional(),
githubProjects: GitHubProjectSettings.optional()
})
.strict()
.default({})
const UiUpdate = z
.object({
lastActiveRepoId: NullableString.optional(),
lastActiveWorktreeId: NullableString.optional(),
sidebarWidth: z.number().finite().optional(),
rightSidebarOpen: z.boolean().optional(),
rightSidebarTab: z
.enum(['explorer', 'search', 'vault', 'source-control', 'checks', 'ports'])
.optional(),
rightSidebarExplorerView: z.enum(['files', 'search']).optional(),
rightSidebarWidth: z.number().finite().optional(),
markdownTocPanelWidth: z.number().finite().optional(),
groupBy: z.enum(['none', 'workspace-status', 'repo', 'pr-status']).optional(),
showWorkspaceLineage: z.boolean().optional(),
sortBy: z.enum(['name', 'smart', 'recent', 'repo', 'manual']).optional(),
projectOrderBy: z.enum(['manual', 'recent']).optional(),
showActiveOnly: z.boolean().optional(),
hideSleepingWorkspaces: z.boolean().optional(),
showSleepingWorkspaces: z.boolean().optional(),
showInactiveWorkspaces: z.boolean().optional(),
workspaceHostScope: z.string().optional(),
visibleWorkspaceHostIds: z.array(z.string()).nullable().optional(),
workspaceHostOrder: z.array(z.string()).optional(),
hideDefaultBranchWorkspace: z.boolean().optional(),
filterRepoIds: StringArray.optional(),
collapsedGroups: StringArray.optional(),
uiZoomLevel: z.number().finite().optional(),
editorFontZoomLevel: z.number().finite().optional(),
worktreeCardProperties: z.array(WorktreeCardProperty).optional(),
agentActivityDisplayMode: AgentActivityDisplayMode.optional(),
workspaceStatuses: z.array(WorkspaceStatusDefinition).optional(),
workspaceBoardOpacity: z.number().finite().optional(),
workspaceBoardColumnWidth: z.number().finite().optional(),
_workspaceStatusesDefaultOrderMigrated: z.boolean().optional(),
_workspaceStatusesDefaultWorkflowMigrated: z.boolean().optional(),
_workspaceStatusesDefaultVisualsMigrated: z.boolean().optional(),
statusBarItems: z.array(StatusBarItem).optional(),
statusBarVisible: z.boolean().optional(),
dismissedUpdateVersion: NullableString.optional(),
lastUpdateCheckAt: z.number().finite().nullable().optional(),
pendingUpdateNudgeId: NullableString.optional(),
dismissedUpdateNudgeId: NullableString.optional(),
notificationPermissionRequested: z.boolean().optional(),
updateReassuranceSeen: z.boolean().optional(),
acknowledgedAgentsByPaneKey: z.record(z.string(), z.number().finite()).optional(),
browserDefaultUrl: NullableString.optional(),
browserDefaultSearchEngine: z
.enum(['google', 'duckduckgo', 'bing', 'kagi'])
.nullable()
.optional(),
browserDefaultZoomLevel: z.number().finite().optional(),
browserKagiSessionLink: NullableString.optional(),
windowBounds: z
.object({
x: z.number().finite(),
y: z.number().finite(),
width: z.number().finite(),
height: z.number().finite()
})
.nullable()
.optional(),
windowMaximized: z.boolean().optional(),
_sortBySmartMigrated: z.boolean().optional(),
_inlineAgentsDefaultedForExperiment: z.boolean().optional(),
_inlineAgentsDefaultedForAllUsers: z.boolean().optional(),
starNagBaselineAgents: z.number().finite().nullable().optional(),
starNagAppVersion: NullableString.optional(),
starNagNextThreshold: z.number().finite().optional(),
starNagCompleted: z.boolean().optional(),
trustedOrcaHooks: z.record(z.string(), z.unknown()).optional(),
setupScriptPromptDismissedRepoIds: StringArray.optional(),
projectOrderManualDefaultNoticeDismissed: z.boolean().optional(),
usageEmptyStateDismissed: z.boolean().optional(),
petVisible: z.boolean().optional(),
petId: z.string().optional(),
customPets: UnknownRecordArray.optional(),
petSize: z.number().finite().optional(),
sidekickVisible: z.boolean().optional(),
sidekickId: z.string().optional(),
customSidekicks: UnknownRecordArray.optional(),
sidekickSize: z.number().finite().optional(),
taskResumeState: TaskResumeState.optional(),
workspaceCleanup: WorkspaceCleanup.optional(),
featureTipsSeenIds: FeatureTipIds.optional(),
featureInteractions: FeatureInteractions.optional(),
contextualToursSeenIds: StringArray.optional(),
contextualToursAutoEligible: z.boolean().optional()
})
.strict()
.default({})
export const CLIENT_UI_METHODS: RpcMethod[] = [
defineMethod({
name: 'settings.get',
params: null,
handler: (_params, { runtime }) => ({ settings: runtime.getClientSettings() })
}),
defineMethod({
name: 'settings.update',
params: SettingsUpdate,
handler: (params, { runtime }) => ({ settings: runtime.updateClientSettings(params) })
}),
defineMethod({
name: 'ui.get',
params: null,
handler: (_params, { runtime }) => ({ ui: runtime.getUIState() })
}),
defineMethod({
name: 'ui.set',
params: UiUpdate,
handler: (params, { runtime }) => ({
ui: runtime.updateUIState(params as Partial<PersistedUIState>)
})
}),
defineMethod({
name: 'ui.recordFeatureInteraction',
params: FeatureInteractionIdParam,
handler: (params, { runtime }) => ({
ui: runtime.recordFeatureInteraction(params)
})
})
]