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: