mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-06 10:24:09 +02:00
fix(ui): use available space for composer labels (#15050)
## Thinking Path > - Paperclip helps people manage AI agents and their work. > - The task composer shows the next assignee and model before a message starts work. > - Fixed width limits shortened both labels when the composer had unused space. > - People could not read the selected agent and model even when the full text could fit. > - This pull request makes the capsule use the available composer width. > - It keeps the ellipsis when Plan mode or a narrow layout causes real space pressure. > - The benefit is clearer run settings without damage to the compact composer layout. ## Linked Issues or Issue Description **What happened?** The task composer truncated the assignee name at 6rem and the complete assignee and model capsule at 16rem. It did this even when the composer had more available space. **Expected behavior** The composer must show the complete assignee and model labels when they fit. It must use an ellipsis only when another control or a narrow viewport limits the available width. **Steps to reproduce** 1. Open a task composer with a long assignee name and a long model name. 2. Use a wide desktop layout. 3. Observe that the old capsule shortened both labels while unused space remained. **Paperclip version or commit** Current `master` before this change. **Deployment mode** Local dev (`pnpm dev`). **Installation method** Built from source. **Agent adapter(s) involved** Not adapter-specific. This is a core UI bug. **Database mode** Not database-related. **Access context** Board. **Additional context** The Storybook cases cover a wide composer and a narrow composer with Plan mode. ## What Changed - Removed the fixed maximum width from the assignee and model capsule. - Removed the fixed maximum width from the assignee label. - Kept overflow ellipsis behavior when the parent row has insufficient space. - Added stable label selectors and focused component coverage. - Added Storybook cases for complete labels and Plan-mode truncation. ## Verification - `pnpm --filter @paperclipai/plugin-sdk build` - `pnpm --filter @paperclipai/ui typecheck` - `vitest run ui/src/components/task-chat/ComposerRunSettingsPicker.test.tsx` - `node scripts/check-token-gates.mjs` - Storybook production build under Node.js 24.20.0 - Captured and inspected the two new Storybook cases. - The complete repository typecheck and build reach the Rust Runner step. This local environment does not have `cargo`. Hosted CI supplies the Rust toolchain. - The repository test suite reaches workspace-runtime tests. This local runner does not allow their required temporary home directories. Hosted CI supplies a writable test home. ## Risks - Low risk. The change only removes fixed width limits from one flex item. - A very narrow composer can still shorten both labels. This is the intended fallback. - The Storybook constrained case verifies that Plan mode and Send remain usable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI GPT-5 through the Paperclip Codex runner. The run used reasoning, tool use, code execution, browser automation, and image inspection. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
eb049aebf2
commit
1c3abf5075
4 files changed
+45
-3
No files matched your search
@@ -55,6 +55,21 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("composer assignee picker", () => {
|
||||
it("lets the assignee and model use the available composer width", () => {
|
||||
render(vi.fn(), vi.fn());
|
||||
const trigger = container!.querySelector<HTMLButtonElement>('[data-testid="task-chat-composer-assignee"]');
|
||||
const assignee = trigger!.querySelector('[data-testid="task-chat-composer-assignee-label"]');
|
||||
const model = trigger!.querySelector('[data-testid="task-chat-composer-model-label"]');
|
||||
|
||||
expect(trigger?.className).toContain("max-w-full");
|
||||
expect(trigger?.className).not.toContain("max-w-64");
|
||||
expect(assignee?.className).toContain("min-w-0");
|
||||
expect(assignee?.className).not.toContain("max-w-24");
|
||||
expect(model?.className).toContain("min-w-0");
|
||||
expect(assignee?.className).toContain("truncate");
|
||||
expect(model?.className).toContain("truncate");
|
||||
});
|
||||
|
||||
it("finds assignees by their displayed role and harness", async () => {
|
||||
render(vi.fn(), vi.fn());
|
||||
await click("Select assignee, model and effort");
|
||||
|
||||
@@ -167,10 +167,10 @@ export function ComposerRunSettingsPicker({
|
||||
const reset = () => onSettingsChange(DEFAULT_COMPOSER_RUN_SETTINGS);
|
||||
const closeButton = mobile ? <DialogClose asChild><button type="button" aria-label="Close picker" className="grid size-8 place-items-center rounded-md text-muted-foreground hover:bg-accent"><X className="size-4" /></button></DialogClose> : null;
|
||||
const trigger = <button ref={triggerRef} type="button" disabled={disabled} aria-label="Select assignee, model and effort" data-testid="task-chat-composer-assignee"
|
||||
className="flex h-8 min-w-0 max-w-64 items-center gap-1.5 rounded-full bg-muted px-2.5 text-xs font-medium hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50">
|
||||
className="flex h-8 min-w-0 max-w-full items-center gap-1.5 rounded-full bg-muted px-2.5 text-xs font-medium hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50">
|
||||
{renderAssigneeIdentity?.(assigneeValue, agent?.name ?? "Unassigned", "trigger")}
|
||||
<span className="max-w-24 truncate">{assigneeOptions.find((item) => item.id === assigneeValue)?.label ?? "Unassigned"}</span>
|
||||
<span className="text-muted-foreground" aria-hidden>·</span><span className="min-w-0 truncate text-muted-foreground">{modelSupported ? modelName || "Harness default" : "Harness default"}</span>
|
||||
<span data-testid="task-chat-composer-assignee-label" className="min-w-0 truncate">{assigneeOptions.find((item) => item.id === assigneeValue)?.label ?? "Unassigned"}</span>
|
||||
<span className="text-muted-foreground" aria-hidden>·</span><span data-testid="task-chat-composer-model-label" className="min-w-0 truncate text-muted-foreground">{modelSupported ? modelName || "Harness default" : "Harness default"}</span>
|
||||
{effort ? <span className="hidden shrink-0 text-muted-foreground sm:inline">{effortLabel}</span> : null}
|
||||
<ChevronDown className="size-3 shrink-0 text-muted-foreground" aria-hidden />
|
||||
</button>;
|
||||
|
||||
@@ -86,6 +86,9 @@ export const composerAgents: ComposerAgent[] = [
|
||||
{ id: "kimi-code/kimi-for-coding", label: "K2.8 Preview" },
|
||||
{ id: "kimi-code/k3", label: "K3" },
|
||||
], manualPattern: "kimi-code/model" },
|
||||
{ id: "long-labels", name: "Alexandra Engineering Coordinator", role: "Engineering", harness: "Codex", adapterType: "codex_local", defaultModel: "gpt-5.6-sol", models: [
|
||||
{ id: "gpt-5.6-sol", label: "GPT-5.6 Sol Extended Context Preview", detail: "Agent default" },
|
||||
], manualPattern: "Model ID, e.g. gpt-5.6-sol" },
|
||||
];
|
||||
|
||||
/** Share the capsule-avatar palettes used by the agent persona stories. */
|
||||
|
||||
@@ -414,3 +414,27 @@ export const ProductionIntermediateWidthPlan: Story = {
|
||||
await expect(screen.getByTestId("composer-model-popover")).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const ProductionLongLabelsWide: Story = {
|
||||
name: "25 · Long labels with available space",
|
||||
render: () => <ComposerRunSettingsLiveStory agentId="long-labels" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const screen = within(canvasElement.ownerDocument.body);
|
||||
const assignee = screen.getByTestId("task-chat-composer-assignee-label");
|
||||
const model = screen.getByTestId("task-chat-composer-model-label");
|
||||
await expect(assignee.scrollWidth).toBeLessThanOrEqual(assignee.clientWidth);
|
||||
await expect(model.scrollWidth).toBeLessThanOrEqual(model.clientWidth);
|
||||
},
|
||||
};
|
||||
|
||||
export const ProductionLongLabelsConstrained: Story = {
|
||||
name: "25b · Long labels constrained by Plan mode",
|
||||
render: () => <ComposerRunSettingsLiveStory agentId="long-labels" compact initialMode="planning" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const screen = within(canvasElement.ownerDocument.body);
|
||||
const assignee = screen.getByTestId("task-chat-composer-assignee-label");
|
||||
const model = screen.getByTestId("task-chat-composer-model-label");
|
||||
await expect(assignee.scrollWidth > assignee.clientWidth || model.scrollWidth > model.clientWidth).toBe(true);
|
||||
await expect(screen.getByRole("button", { name: "Remove Plan mode" })).toBeVisible();
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user