mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
* Hide projects from the sidebar without stopping them
Adds Repo.hiddenAt, a per-host, view-only flag. A hidden project's
workspaces, empty header and project-filter entry leave the desktop
sidebar, the paired phone's list, the new-workspace picker and the
palette's project filter. Its terminals, agents and automations keep
running, and repo.list / worktree.ps still list it.
- Hide Project in the project menu (toast with Undo), a collapsed
"N hidden projects" list under the workspace list with Show buttons,
and a Hide from sidebar switch in Settings > Projects.
- repo.setHidden RPC ({repo, repoHostId?, hidden} -> {repo, hostId,
hidden}) and `orca repo hide|unhide [--host]`; `repo list` marks
hidden rows.
- Opening a hidden project's workspace (notification, CLI, deep link)
focuses it without unhiding it, lifting sidebar filters, or leaving a
pending sidebar reveal.
- repo.update echo check: a host that drops hiddenAt reports failure
instead of reading as hidden.
* Address hidden-projects review: multi-host hide, picker scope, mute, stale filters, shared predicate, restore list styling
* Hidden projects re-review: keep saved filters, Agents filter, composer seed, per-toast Undo, shared default host
* Hidden projects: address review threads (pre-feed, palette search, notices, dialog default, seed remap, row keys, a11y)
* Hidden projects: no implicit dialog default when every project is hidden
95 lines
4.0 KiB
TypeScript
95 lines
4.0 KiB
TypeScript
import * as z from 'zod'
|
|
import { normalizeExecutionHostId } from './execution-host'
|
|
import type { ProjectHostSetupMethod } from './project-types'
|
|
import type { ProjectGroup, ProjectGroupCreatedFrom } from './project-group-types'
|
|
import type { RepoKind } from './repo-types'
|
|
import type { ProjectOrderBy } from './ui-chrome-types'
|
|
import { hostUnionArms, salvagedOptional, salvagingArray } from './zod-salvage'
|
|
|
|
// Read sidebar-feed/mobileRelay.projectSections catalogs; MobileProjectSectionCatalog is the sender shape.
|
|
|
|
const SETUP_METHOD_ARMS = hostUnionArms<ProjectHostSetupMethod>({
|
|
'legacy-repo': true,
|
|
'imported-existing-folder': true,
|
|
cloned: true,
|
|
provisioned: true
|
|
})
|
|
const REPO_KIND_ARMS = hostUnionArms<RepoKind>({ git: true, folder: true })
|
|
const PROJECT_ORDER_BY_ARMS = hostUnionArms<ProjectOrderBy>({ manual: true, recent: true })
|
|
|
|
// A host id the desktop spells; normalized, so a spelling this build cannot read drops.
|
|
const hostIdSchema = z.string().transform((value, ctx) => {
|
|
const hostId = normalizeExecutionHostId(value)
|
|
if (!hostId) {
|
|
ctx.addIssue({ code: 'custom', message: 'unknown host id' })
|
|
return z.NEVER
|
|
}
|
|
return hostId
|
|
})
|
|
|
|
const repoSchema = z.object({
|
|
id: z.string(),
|
|
displayName: z.string(),
|
|
path: z.string(),
|
|
badgeColor: salvagedOptional('badgeColor', z.string()),
|
|
addedAt: salvagedOptional('addedAt', z.number()),
|
|
connectionId: salvagedOptional('connectionId', z.string().nullable()),
|
|
executionHostId: salvagedOptional('executionHostId', hostIdSchema),
|
|
// Older desktops omit both; the project then lists ungrouped, as it did.
|
|
projectGroupId: salvagedOptional('projectGroupId', z.string().nullable()),
|
|
projectGroupOrder: salvagedOptional('projectGroupOrder', z.number()),
|
|
// Older desktops omit it; the project then stays listed, as it did.
|
|
hiddenAt: salvagedOptional('hiddenAt', z.number()),
|
|
// Decoded where drawn (readMobileRepoIcon), so an arm this build lacks still keeps the row.
|
|
repoIcon: z.unknown().optional()
|
|
})
|
|
|
|
const setupSchema = z.object({
|
|
id: z.string(),
|
|
projectId: z.string(),
|
|
hostId: hostIdSchema,
|
|
repoId: z.string(),
|
|
path: z.string(),
|
|
displayName: z.string(),
|
|
// Why the fallback: only 'provisioned' changes grouping, so a newer arm groups as a checkout.
|
|
setupMethod: z.enum(SETUP_METHOD_ARMS).catch('legacy-repo'),
|
|
kind: salvagedOptional('kind', z.enum(REPO_KIND_ARMS)),
|
|
connectionId: salvagedOptional('connectionId', z.string().nullable()),
|
|
executionHostId: salvagedOptional('executionHostId', hostIdSchema)
|
|
})
|
|
|
|
const PROJECT_GROUP_CREATED_FROM_ARMS = hostUnionArms<ProjectGroupCreatedFrom>({
|
|
manual: true,
|
|
'folder-scan': true,
|
|
migration: true
|
|
})
|
|
|
|
export const ProjectGroupSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
parentPath: z.string().nullable(),
|
|
connectionId: salvagedOptional('connectionId', z.string().nullable()),
|
|
executionHostId: salvagedOptional('executionHostId', z.string().nullable()),
|
|
parentGroupId: z.string().nullable(),
|
|
createdFrom: z.enum(PROJECT_GROUP_CREATED_FROM_ARMS).catch('manual'),
|
|
tabOrder: z.number(),
|
|
rootProjectOrder: salvagedOptional('rootProjectOrder', z.number().finite()),
|
|
isCollapsed: z.boolean(),
|
|
color: z.string().nullable(),
|
|
createdAt: z.number(),
|
|
updatedAt: z.number()
|
|
}) satisfies z.ZodType<ProjectGroup, unknown>
|
|
|
|
export const ProjectSectionCatalogSchema = z.object({
|
|
repos: salvagingArray(repoSchema),
|
|
projects: salvagingArray(z.object({ id: z.string(), displayName: z.string() })),
|
|
setups: salvagingArray(setupSchema),
|
|
// Until the phone has read the shared UI value (projectOrderBy, ui.get), it orders by this.
|
|
projectOrderBy: z.enum(PROJECT_ORDER_BY_ARMS).catch('manual'),
|
|
// Every host's project groups, for a list built from the sidebar feed; older desktops omit it.
|
|
projectGroups: salvagedOptional('projectGroups', salvagingArray(ProjectGroupSchema))
|
|
})
|
|
|
|
/** The desktop sidebar's Project grouping inputs, as a reader holds them. */
|
|
export type ServedProjectSections = z.output<typeof ProjectSectionCatalogSchema>
|