| author | |
| committer | |
| log | 82451eeb7277b0b7fb5a7cb53156c83f948f32c1 |
| tree | 04282ea510f41494a4a699ff5a70924bea9eb463 |
| parent | 83b865fec704b177804aa6b9b695aefea4ccd04a |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>3 files changed, 57 insertions(+), 9 deletions(-)
apps/web/src/components/CommandPalette.logic.test.ts+38-1| ... | @@ -1,8 +1,9 @@ | ... | @@ -1,8 +1,9 @@ |
| 1 | import { describe, expect, it, vi } from "vite-plus/test"; | 1 | import { describe, expect, it, vi } from "vite-plus/test"; |
| 2 | import { EnvironmentId, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; | 2 | import { EnvironmentId, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; |
| 3 | import type { Thread } from "../types"; | 3 | import type { Project, Thread } from "../types"; |
| 4 | import { | 4 | import { |
| 5 | buildBrowseGroups, | 5 | buildBrowseGroups, |
| 6 | buildProjectActionItems, | ||
| 6 | buildThreadActionItems, | 7 | buildThreadActionItems, |
| 7 | enumerateCommandPaletteItems, | 8 | enumerateCommandPaletteItems, |
| 8 | filterPinnedBrowseEntries, | 9 | filterPinnedBrowseEntries, |
| ... | @@ -112,6 +113,20 @@ describe("enumerateCommandPaletteItems", () => { | ... | @@ -112,6 +113,20 @@ describe("enumerateCommandPaletteItems", () => { |
| 112 | const LOCAL_ENVIRONMENT_ID = EnvironmentId.make("environment-local"); | 113 | const LOCAL_ENVIRONMENT_ID = EnvironmentId.make("environment-local"); |
| 113 | const PROJECT_ID = ProjectId.make("project-1"); | 114 | const PROJECT_ID = ProjectId.make("project-1"); |
| 114 | 115 | ||
| 116 | function makeProject(overrides: Partial<Project> = {}): Project { | ||
| 117 | return { | ||
| 118 | id: PROJECT_ID, | ||
| 119 | environmentId: LOCAL_ENVIRONMENT_ID, | ||
| 120 | title: "Project", | ||
| 121 | workspaceRoot: "/workspace/project", | ||
| 122 | defaultModelSelection: null, | ||
| 123 | scripts: [], | ||
| 124 | createdAt: "2026-03-01T00:00:00.000Z", | ||
| 125 | updatedAt: "2026-03-01T00:00:00.000Z", | ||
| 126 | ...overrides, | ||
| 127 | }; | ||
| 128 | } | ||
| 129 | |||
| 115 | function makeThread(overrides: Partial<Thread> = {}): Thread { | 130 | function makeThread(overrides: Partial<Thread> = {}): Thread { |
| 116 | return { | 131 | return { |
| 117 | id: ThreadId.make("thread-1"), | 132 | id: ThreadId.make("thread-1"), |
| ... | @@ -139,6 +154,28 @@ function makeThread(overrides: Partial<Thread> = {}): Thread { | ... | @@ -139,6 +154,28 @@ function makeThread(overrides: Partial<Thread> = {}): Thread { |
| 139 | }; | 154 | }; |
| 140 | } | 155 | } |
| 141 | 156 | ||
| 157 | describe("buildProjectActionItems", () => { | ||
| 158 | it("shows the grouped display name but keeps the real title for icons", () => { | ||
| 159 | const project = makeProject({ title: "fleet", workspaceRoot: "/Users/theo/Code/p/fleet" }); | ||
| 160 | const iconTitles: string[] = []; | ||
| 161 | const [item] = buildProjectActionItems({ | ||
| 162 | projects: [{ ...project, displayName: "t3dotgg/fleet" }], | ||
| 163 | valuePrefix: "project", | ||
| 164 | icon: (candidate) => { | ||
| 165 | iconTitles.push(candidate.title); | ||
| 166 | return null; | ||
| 167 | }, | ||
| 168 | runProject: async () => undefined, | ||
| 169 | }); | ||
| 170 | |||
| 171 | expect(item?.title).toBe("t3dotgg/fleet"); | ||
| 172 | expect(item?.searchTerms).toEqual( | ||
| 173 | expect.arrayContaining(["t3dotgg/fleet", "fleet", "/Users/theo/Code/p/fleet"]), | ||
| 174 | ); | ||
| 175 | expect(iconTitles).toEqual(["fleet"]); | ||
| 176 | }); | ||
| 177 | }); | ||
| 178 | |||
| 142 | describe("buildThreadActionItems", () => { | 179 | describe("buildThreadActionItems", () => { |
| 143 | it("orders threads by most recent activity and formats timestamps from updatedAt", () => { | 180 | it("orders threads by most recent activity and formats timestamps from updatedAt", () => { |
| 144 | vi.useFakeTimers(); | 181 | vi.useFakeTimers(); |
apps/web/src/components/CommandPalette.logic.ts+18-7| ... | @@ -139,20 +139,31 @@ export function enumerateCommandPaletteItems( | ... | @@ -139,20 +139,31 @@ export function enumerateCommandPaletteItems( |
| 139 | 139 | ||
| 140 | export type CommandPaletteMode = "root" | "root-browse" | "submenu" | "submenu-browse"; | 140 | export type CommandPaletteMode = "root" | "root-browse" | "submenu" | "submenu-browse"; |
| 141 | 141 | ||
| 142 | // A project as the palette shows it. `displayName` is the grouped label (for | ||
| 143 | // example "owner/repo" when projects are merged across machines). Keep `title` | ||
| 144 | // as the real project title: the automatic project icon is derived from it, and | ||
| 145 | // every other surface uses the real title, so overriding it desyncs the icon. | ||
| 146 | export type CommandPaletteProject = Project & { readonly displayName: string }; | ||
| 147 | |||
| 142 | export function buildProjectActionItems(input: { | 148 | export function buildProjectActionItems(input: { |
| 143 | projects: ReadonlyArray<Project>; | 149 | projects: ReadonlyArray<CommandPaletteProject>; |
| 144 | valuePrefix: string; | 150 | valuePrefix: string; |
| 145 | icon: (project: Project) => ReactNode; | 151 | icon: (project: CommandPaletteProject) => ReactNode; |
| 146 | runProject: (project: Project) => Promise<void>; | 152 | runProject: (project: CommandPaletteProject) => Promise<void>; |
| 147 | searchTerms?: (project: Project) => ReadonlyArray<string>; | 153 | searchTerms?: (project: CommandPaletteProject) => ReadonlyArray<string>; |
| 148 | renderDescription?: (project: Project) => ReactNode; | 154 | renderDescription?: (project: CommandPaletteProject) => ReactNode; |
| 149 | shortcutCommand?: KeybindingCommand; | 155 | shortcutCommand?: KeybindingCommand; |
| 150 | }): CommandPaletteActionItem[] { | 156 | }): CommandPaletteActionItem[] { |
| 151 | return input.projects.map((project) => ({ | 157 | return input.projects.map((project) => ({ |
| 152 | kind: "action", | 158 | kind: "action", |
| 153 | value: `${input.valuePrefix}:${project.environmentId}:${project.id}`, | 159 | value: `${input.valuePrefix}:${project.environmentId}:${project.id}`, |
| 154 | searchTerms: [project.title, project.workspaceRoot, ...(input.searchTerms?.(project) ?? [])], | 160 | searchTerms: [ |
| 155 | title: project.title, | 161 | project.displayName, |
| 162 | project.title, | ||
| 163 | project.workspaceRoot, | ||
| 164 | ...(input.searchTerms?.(project) ?? []), | ||
| 165 | ], | ||
| 166 | title: project.displayName, | ||
| 156 | description: input.renderDescription?.(project) ?? project.workspaceRoot, | 167 | description: input.renderDescription?.(project) ?? project.workspaceRoot, |
| 157 | icon: input.icon(project), | 168 | icon: input.icon(project), |
| 158 | ...(input.shortcutCommand !== undefined ? { shortcutCommand: input.shortcutCommand } : {}), | 169 | ...(input.shortcutCommand !== undefined ? { shortcutCommand: input.shortcutCommand } : {}), |
apps/web/src/components/CommandPalette.tsx+1-1| ... | @@ -794,7 +794,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -794,7 +794,7 @@ function OpenCommandPaletteDialog(props: { |
| 794 | () => | 794 | () => |
| 795 | projectPickerEntries.map(({ group, targetProject }) => ({ | 795 | projectPickerEntries.map(({ group, targetProject }) => ({ |
| 796 | ...targetProject, | 796 | ...targetProject, |
| 797 | title: group.displayName, | 797 | displayName: group.displayName, |
| 798 | })), | 798 | })), |
| 799 | [projectPickerEntries], | 799 | [projectPickerEntries], |
| 800 | ); | 800 | ); |