Files
orca/src/shared/rpc-contract/client-ui-params.ts
T
Neil 3cb4853187 Keep dropped-macOS updaters on their last compatible build and explain why (#38777)
* Keep dropped-macOS updaters on their last compatible build and explain why

Stamp minimumSystemVersion into every mac update manifest from the built
app's LSMinimumSystemVersion, and refuse to publish an ungated release.
When the updater withholds a newer release for this macOS, show a one-time
notice and say so in Settings and the update card.

* Trim the unsupported-Mac notice copy
2026-11-05 18:29:16 -08:00

277 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from 'zod'
import { isFeatureTipId } from '../feature-tips'
import {
WORKTREE_CARD_PROPERTIES,
normalizeWorktreeCardProperties
} from '../worktree/card-properties'
import { isPluginPanelTabKey } from '../plugins/manifest/plugin-manifest'
import { isFeatureInteractionId } from '../feature-interactions'
import type { FeatureInteractionId } from '../feature-interactions'
import { ACTIVITY_GROUP_BY_VALUES, THREAD_READ_FILTER_VALUES } from '../agents-view-thread-filters'
import { isReleaseChannel } from '../release-channel'
import type { ReleaseChannel } from '../release-channel'
import { ClientUiWorkspaceFilterFields } from './client-ui-workspace-filter-fields-params'
import { TaskResumeState } from './task-resume-state-params'
import { WorkspaceCleanup } from './workspace-cleanup-ui-params'
import { omitUndefinedValues, tolerateUnknownValues } from './ui-update-value-tolerance-params'
export const NullableString = z.string().nullable()
export const StringArray = z.array(z.string())
export const FeatureTipIds = z.array(
z.custom(isFeatureTipId, { message: 'Unknown feature tip id' })
)
export const UnknownRecord = z.record(z.string(), z.unknown())
export const UnknownRecordArray = z.array(UnknownRecord)
export type StaticRightSidebarTab = (typeof STATIC_RIGHT_SIDEBAR_TABS)[number]
// Derive from the shared union; drift previously omitted cli and rejected whole client payloads.
export const WorktreeCardPropertyParam = z.enum(WORKTREE_CARD_PROPERTIES)
export const WorktreeCardProperties = z
.array(WorktreeCardPropertyParam)
.transform((value) => normalizeWorktreeCardProperties(value))
export const STATIC_RIGHT_SIDEBAR_TABS = [
'explorer',
'search',
'vault',
'workspaces',
'pr-checks',
'source-control',
'checks',
'ports'
] as const
// Plugin panels are open-ended `plugin:<publisher>.<id>/<panel>` keys, so the
// schema validates their shape rather than enumerating them.
export const RightSidebarTabParam = z.custom<StaticRightSidebarTab | `plugin:${string}`>(
(value) =>
typeof value === 'string' &&
(STATIC_RIGHT_SIDEBAR_TABS.includes(value as StaticRightSidebarTab) ||
isPluginPanelTabKey(value)),
{ message: 'Unknown right sidebar tab' }
)
export const AgentActivityDisplayMode = z.enum(['compact', 'full'])
export const StatusBarItem = z.enum([
'claude',
'codex',
'gemini',
'antigravity',
'opencode-go',
'kimi',
'minimax',
'grok',
'cursor',
'zcode',
'ssh',
'resource-usage',
'ports'
])
export const WorkspaceStatusDefinition = z.object({
id: z.string(),
label: z.string(),
color: z.string().optional(),
icon: z.string().optional()
})
export const FeatureInteractionRecord = z
.object({
firstInteractedAt: z.number().finite().nonnegative(),
interactionCount: z.number().int().positive().optional()
})
.strict()
export 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]
})
}
}
})
export const FeatureInteractionIdParam = z.custom<FeatureInteractionId>(isFeatureInteractionId, {
message: 'Unknown feature interaction id'
})
export const TopLevelViewSchema = z.enum([
'terminal',
'settings',
'tasks',
'activity',
'automations',
'space',
'skills',
'artifacts',
'mobile'
])
export const UiUpdateFields = z
.object({
lastActiveRepoId: NullableString.optional(),
lastActiveWorktreeId: NullableString.optional(),
// Why: sync hydration ignores this persisted startup view, so paired windows stay put.
activeView: TopLevelViewSchema.optional(),
sidebarWidth: z.number().finite().optional(),
sidebarOpen: z.boolean().optional(),
rightSidebarOpen: z.boolean().optional(),
rightSidebarTab: RightSidebarTabParam.optional(),
rightSidebarExplorerView: z.enum(['files', 'search']).optional(),
rightSidebarWidth: z.number().finite().optional(),
markdownTocPanelWidth: z.number().finite().optional(),
combinedDiffFileTreeWidth: 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(),
agentsVisibleHostIds: z.array(z.string()).nullable().optional(),
agentsFilterRepoIds: StringArray.optional(),
agentsHideWorkspacesFromOtherDevices: z.boolean().optional(),
agentsHideAutomationGeneratedWorkspaces: z.boolean().optional(),
agentsHideCliCreatedWorkspaces: z.boolean().optional(),
agentsShowChildAgents: z.boolean().optional(),
agentsCompactMode: z.boolean().optional(),
agentsShowSearch: z.boolean().optional(),
agentsReadFilter: z.enum(THREAD_READ_FILTER_VALUES).optional(),
agentsGroupBy: z.enum(ACTIVITY_GROUP_BY_VALUES).optional(),
workspaceHostOrder: z.array(z.string()).optional(),
automationHostFilter: z
.union([
z.object({ kind: z.literal('all') }).strict(),
z.object({ kind: z.literal('host'), hostKey: z.string().min(1) }).strict()
])
.optional(),
manualRepoOrder: z
.array(z.object({ hostId: z.string(), repoId: z.string() }).strict())
.optional(),
...ClientUiWorkspaceFilterFields,
// Part of App.tsx’s debounced writer; missing fields reject the whole paired-client payload.
explorerDisplayRootByWorktree: z.record(z.string(), z.string()).optional(),
showDotfilesByWorktree: z.record(z.string(), z.boolean()).optional(),
collapsedGroups: StringArray.optional(),
uiZoomLevel: z.number().finite().optional(),
editorFontZoomLevel: z.number().finite().optional(),
worktreeCardProperties: WorktreeCardProperties.optional(),
_worktreeCardModeDefaulted: z.boolean().optional(),
showAgentModels: z.boolean().optional(),
agentActivityDisplayMode: AgentActivityDisplayMode.optional(),
workspaceStatuses: z.array(WorkspaceStatusDefinition).optional(),
workspaceBoardOpacity: z.number().finite().optional(),
workspaceBoardColumnWidth: z.number().finite().optional(),
syncTaskStatusFromWorkspaceBoard: z.boolean().optional(),
_workspaceStatusesDefaultOrderMigrated: z.boolean().optional(),
_workspaceStatusesReorderedDefaultRepaired: z.boolean().optional(),
_workspaceStatusesDefaultWorkflowMigrated: z.boolean().optional(),
_workspaceStatusesDefaultVisualsMigrated: z.boolean().optional(),
statusBarItems: z.array(StatusBarItem).optional(),
_portsStatusBarDefaultAdded: z.boolean().optional(),
_kimiStatusBarDefaultAdded: z.boolean().optional(),
_minimaxStatusBarDefaultAdded: z.boolean().optional(),
_antigravityStatusBarDefaultAdded: z.boolean().optional(),
_grokStatusBarDefaultAdded: z.boolean().optional(),
_cursorStatusBarDefaultAdded: z.boolean().optional(),
_zcodeStatusBarDefaultAdded: z.boolean().optional(),
statusBarVisible: z.boolean().optional(),
usagePercentageDisplay: z.enum(['used', 'remaining']).optional(),
statusBarUsageMode: z.enum(['verbose', 'compact']).optional(),
dismissedUpdateVersion: NullableString.optional(),
dismissedUnexpectedSignoutVersion: NullableString.optional(),
lastUpdateCheckAt: z.number().finite().nullable().optional(),
pendingUpdateNudgeId: NullableString.optional(),
dismissedUpdateNudgeId: NullableString.optional(),
// Share the release-channel predicate so new channel overrides cannot silently fail parsing.
releaseChannelOverride: z.custom<ReleaseChannel>(isReleaseChannel).nullable().optional(),
notificationPermissionRequested: z.boolean().optional(),
updateReassuranceSeen: z.boolean().optional(),
seenMacosUpdateRequirement: NullableString.optional(),
osc52ClipboardDefaultOnNoticePending: z.boolean().optional(),
acknowledgedAgentsByPaneKey: z.record(z.string(), z.number().finite()).optional(),
activityClearedAtByPaneKey: z.record(z.string(), z.number().finite()).optional(),
manuallyUnreadTurnsByPaneKey: 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(),
browserDevToolsDock: z
.object({
side: z.enum(['bottom', 'right']),
bottomFraction: z.number().finite(),
rightFraction: z.number().finite()
})
.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(),
trustedOrcaHooks: z.record(z.string(), z.unknown()).optional(),
setupScriptPromptDismissedRepoIds: StringArray.optional(),
// Declare renderer one-shot dismissals to avoid whole-payload rejections for paired clients.
setupGuideSidebarDismissed: z.boolean().optional(),
setupGuideBrowserMilestoneMigrated: z.boolean().optional(),
setupGuideBrowserMilestoneLegacyComplete: z.boolean().optional(),
browserImportHintHidden: z.boolean().optional(),
mobileEmulatorTabIntroDismissed: z.boolean().optional(),
mobileEmulatorAgentSetupDismissed: z.boolean().optional(),
projectOrderManualDefaultNoticeDismissed: z.boolean().optional(),
usagePercentageDisplayChangeNoticeDismissed: z.boolean().optional(),
statusBarCompactChangeNoticeDismissed: z.boolean().optional(),
usageEmptyStateDismissed: z.boolean().optional(),
pluginsAlphaNoticeDismissed: z.boolean().optional(),
codexTerminalServerIsolationNoticeSeen: z.boolean().optional(),
codexSharedSettingsNoticeSeen: z.boolean().optional(),
claudeDiffPanelNoticeSeen: z.boolean().optional(),
claudeAccountSignInNoticeSeen: 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()
export const UiUpdate = z
.object(tolerateUnknownValues(UiUpdateFields.shape))
.strict()
.default({})
.transform(omitUndefinedValues)