diff --git a/apps/portal/src/components/ProfilePicker.stories.tsx b/apps/portal/src/components/ProfilePicker.stories.tsx new file mode 100644 index 000000000..2c6c0b542 --- /dev/null +++ b/apps/portal/src/components/ProfilePicker.stories.tsx @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, userEvent } from "storybook/test"; + +import { ProfilePicker } from "./ProfilePicker"; +import type { ProfileWithVersion } from "@/types"; + +const longName = "A descriptive profile name that should remain visible whenever the picker has enough room to show it in full"; +const profile: ProfileWithVersion = { + _id: "profile-1", + name: longName, + latestVersion: 1, + createdAt: "2026-01-01T00:00:00.000Z", + version: { + _id: "profile-version-1", + profileId: "profile-1", + version: 1, + workerType: "coder-acp-copilot", + model: "gpt-5.6-sol", + createdAt: "2026-01-01T00:00:00.000Z", + }, +}; + +const meta = { + component: ProfilePicker, + args: { + profiles: [profile], + selectedProfileId: null, + onSelect: () => {}, + }, + decorators: [(Story) => ( +
+ +
+ )], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const LongName: Story = { + play: async ({ canvas }) => { + await userEvent.click(canvas.getByPlaceholderText("Search existing profiles…")); + await expect(canvas.getByText(longName)).toHaveAttribute("title", longName); + }, +}; diff --git a/apps/portal/src/components/ProfilePicker.tsx b/apps/portal/src/components/ProfilePicker.tsx index f2ea003b8..8064b8b7c 100644 --- a/apps/portal/src/components/ProfilePicker.tsx +++ b/apps/portal/src/components/ProfilePicker.tsx @@ -6,7 +6,6 @@ import { Check, Search } from "lucide-react"; import type { ProfileWithVersion } from "@/types"; import { Input } from "@/components/ui/input"; -import { truncate } from "@/lib/utils"; import { AgentBadge } from "@/components/AgentBadge"; interface ProfilePickerProps { @@ -139,7 +138,9 @@ export function ProfilePicker({ onClick={() => handleSelect(profile._id)} >

- {truncate(profile.name, 80)} + + {profile.name} + {isSelected && }

diff --git a/docs/architecture/app-design.md b/docs/architecture/app-design.md index f1a243262..b49c45da5 100644 --- a/docs/architecture/app-design.md +++ b/docs/architecture/app-design.md @@ -461,6 +461,9 @@ resubmission resolves a currently active version unless a version is explicitly pinned. Invalid historical pending targets stay pending and produce scheduler telemetry rather than being sent to a guessed queue. +The Portal profile picker shows full profile names when space permits and clips +only at the available width; hovering a clipped name reveals the full name. + When a codebase is selected, the API resolves the submitted spec (`codebaseRevisionId`, `{slug}@r{N}`, or bare `{slug}`) before enqueueing. Bare archive slugs resolve to the latest existing revision; bare Git slugs resolve the default branch at submit time and create a new immutable revision. The resolved revision UUID is stored as `RequestDocument.codebaseRevisionId`, and workers seed the workspace from that revision after setup and before skills extraction. Profile fan-out mode is also supported for comparative runs: