mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-02 02:07:25 +08:00
Align task property icons with assignee avatars (#13319)
## Thinking Path > - Paperclip is the open source app that people use to manage AI agents for work. > - The task properties panel helps operators inspect and update task data. > - The status, assignee, and project rows used different leading icon sizes. > - The mixed sizes made the rows look uneven. > - The assignee avatar already provides the correct 24px visual size. > - This pull request sets the status and project visuals to that same size. > - The benefit is a tidy and consistent properties panel. ## Linked Issues or Issue Description **What happened?** The task properties panel showed a 12px status icon, a 24px assignee avatar, and a 16px project tile. **Expected behavior** The status, assignee, and project rows must use the same 24px leading visual size. **Steps to reproduce** 1. Open a task. 2. Open the Properties panel. 3. Compare the Status, Assignee, and Project rows. ## What Changed - Keep the Status glyph at 16px and use spacing to give it the 24px avatar footprint. - Set the Project row tile to the 24px small tile size. - Add a regression check for the Status row size. ## Verification - `pnpm exec vitest run ui/src/components/IssueProperties.test.tsx` (72 tests passed) - `pnpm check:token-gates` (all gates clean) - `pnpm --filter @paperclipai/ui typecheck` (passed) - Full repository typecheck and build reached the Rust runner step, but this environment does not contain `cargo`. - The full test command was stopped after unrelated chat integration tests did not finish. The focused UI suite passed. ## Risks - Low risk. This change only changes visual sizes in three property rows. - The wider status footprint can use slightly more horizontal space in a narrow panel. > This fix does not overlap with planned core work in `ROADMAP.md`. ## Model Used - OpenAI Codex, GPT-5.6, tool use and code execution enabled. ## 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 - [ ] All Paperclip CI gates are green - [ ] 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:
@@ -130,8 +130,8 @@ vi.mock("../lib/assignees", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./StatusIcon", () => ({
|
||||
StatusIcon: ({ status, blockerAttention }: { status: string; blockerAttention?: Issue["blockerAttention"] }) => (
|
||||
<span data-status-icon-state={blockerAttention?.state}>{status}</span>
|
||||
StatusIcon: ({ status, blockerAttention, className, glyphContainerClassName, size }: { status: string; blockerAttention?: Issue["blockerAttention"]; className?: string; glyphContainerClassName?: string; size?: string }) => (
|
||||
<span className={className} data-glyph-container-class={glyphContainerClassName} data-testid="status-icon" data-size={size} data-status-icon-state={blockerAttention?.state}>{status}</span>
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -527,6 +527,12 @@ describe("IssueProperties", () => {
|
||||
expect(surface?.classList).toContain("pl-4");
|
||||
expect(surface?.querySelectorAll('[data-property-section="true"]').length).toBeGreaterThan(1);
|
||||
expect(surface?.querySelector('[data-property-value="true"]')).not.toBeNull();
|
||||
const statusVisual = surface?.querySelector(
|
||||
'[data-property-label="Status"] + [data-property-value="true"] [data-testid="status-icon"]',
|
||||
);
|
||||
expect(statusVisual).not.toBeNull();
|
||||
expect(statusVisual?.getAttribute("data-size")).toBeNull();
|
||||
expect(statusVisual?.getAttribute("data-glyph-container-class")).toContain("size-6");
|
||||
expect(surface?.querySelector('[data-property-section="true"] > div')?.classList)
|
||||
.toContain("text-muted-foreground/70");
|
||||
const projectLabel = surface?.querySelector('[data-property-label="Project"]');
|
||||
|
||||
@@ -108,6 +108,16 @@ describe("StatusIcon — glyph size (PAP-243a)", () => {
|
||||
expect(html).toContain('width="16"');
|
||||
expect(html).toContain('height="16"');
|
||||
});
|
||||
|
||||
it("can place the default glyph in a larger layout footprint without resizing it", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<StatusIcon status="todo" glyphContainerClassName="size-6" showLabel />,
|
||||
);
|
||||
expect(html).toContain('data-status-glyph-container="true"');
|
||||
expect(html).toContain('class="size-6"');
|
||||
expect(html).toContain('width="16"');
|
||||
expect(html).toContain('height="16"');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ interface StatusIconProps {
|
||||
blockerAttention?: IssueBlockerAttention | null;
|
||||
onChange?: (status: string) => void;
|
||||
className?: string;
|
||||
/** Optional layout wrapper around the glyph. Does not change glyph dimensions. */
|
||||
glyphContainerClassName?: string;
|
||||
showLabel?: boolean;
|
||||
/** Glyph size (PAP-243a). Default `md` (16px); lists/detail/mentions use `lg` (20px). */
|
||||
size?: StatusGlyphSize;
|
||||
@@ -76,14 +78,14 @@ function blockedAttentionLabel(blockerAttention: IssueBlockerAttention | null |
|
||||
* glyph — the blocked shape recoloured blue — while the full blocked reason
|
||||
* still rides on the accessible label.
|
||||
*/
|
||||
export function StatusIcon({ status, externalConversationState, blockerAttention, onChange, className, showLabel, size = "md" }: StatusIconProps) {
|
||||
export function StatusIcon({ status, externalConversationState, blockerAttention, onChange, className, glyphContainerClassName, showLabel, size = "md" }: StatusIconProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const displayStatus = status === "in_review" && externalConversationState === "waiting" ? "idle" : status;
|
||||
const isCoveredBlocked = status === "blocked" && blockerAttention?.state === "covered";
|
||||
const ariaLabel = status === "blocked" ? blockedAttentionLabel(blockerAttention) : statusLabel(displayStatus);
|
||||
const glyphStatus = isCoveredBlocked ? "in_queue" : displayStatus;
|
||||
|
||||
const glyph = (
|
||||
const glyphIcon = (
|
||||
<StatusGlyph
|
||||
status={glyphStatus}
|
||||
size={size}
|
||||
@@ -91,6 +93,11 @@ export function StatusIcon({ status, externalConversationState, blockerAttention
|
||||
title={ariaLabel}
|
||||
/>
|
||||
);
|
||||
const glyph = glyphContainerClassName ? (
|
||||
<span className={glyphContainerClassName} data-status-glyph-container="true">
|
||||
{glyphIcon}
|
||||
</span>
|
||||
) : glyphIcon;
|
||||
|
||||
if (!onChange) {
|
||||
return showLabel ? (
|
||||
|
||||
@@ -1947,7 +1947,7 @@ export function IssueProperties({
|
||||
<ProjectTile
|
||||
color={issueProject?.color ?? null}
|
||||
icon={issueProject?.icon ?? null}
|
||||
size="xs"
|
||||
size="sm"
|
||||
/>
|
||||
<span className="text-sm truncate min-w-0" title={projectName(issue.projectId)}>{projectName(issue.projectId)}</span>
|
||||
</>
|
||||
@@ -2334,8 +2334,9 @@ export function IssueProperties({
|
||||
>
|
||||
<PropertyRow label="Status">
|
||||
<StatusIcon
|
||||
status={issue.status} externalConversationState={issue.externalConversationState}
|
||||
className="size-3"
|
||||
status={issue.status}
|
||||
externalConversationState={issue.externalConversationState}
|
||||
glyphContainerClassName="inline-flex size-6 shrink-0 items-center justify-center"
|
||||
blockerAttention={issue.blockerAttention}
|
||||
onChange={(status) => onUpdate({ status })}
|
||||
showLabel
|
||||
|
||||
Reference in New Issue
Block a user