Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions apps/portal/src/components/ProfilePicker.stories.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<div className="w-[800px] max-w-full">
<Story />
</div>
)],
} satisfies Meta<typeof ProfilePicker>;

export default meta;
type Story = StoryObj<typeof meta>;

export const LongName: Story = {
play: async ({ canvas }) => {
await userEvent.click(canvas.getByPlaceholderText("Search existing profiles…"));
await expect(canvas.getByText(longName)).toHaveAttribute("title", longName);
},
};
5 changes: 3 additions & 2 deletions apps/portal/src/components/ProfilePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -139,7 +138,9 @@ export function ProfilePicker({
onClick={() => handleSelect(profile._id)}
>
<p className="flex items-center justify-between gap-2 font-medium">
{truncate(profile.name, 80)}
<span className="min-w-0 flex-1 truncate" title={profile.name}>
{profile.name}
</span>
{isSelected && <Check className="h-4 w-4 shrink-0" />}
</p>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
Expand Down
3 changes: 3 additions & 0 deletions docs/architecture/app-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down