mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-16 15:15:35 +02:00
## Thinking Path > - Paperclip is the open source control plane people use to manage AI agents and their work > - Operators rely on task detail properties and header actions to scan linked resources and make quick decisions > - External GitHub objects used a long pull-request label and repeated mention counts that added visual noise without adding state > - The task-detail star action also rendered as a labelled outline button, unlike the compact star controls used elsewhere > - These inconsistencies made dense task-detail surfaces slower to scan and broke Paperclip's content-first visual language > - This pull request shortens the GitHub pull-request label, removes duplicate mention-count decoration, and aligns the detail star action with the icon-only control pattern > - The benefit is a calmer, more consistent task-detail experience with accessible labels preserved for assistive technology ## Linked Issues or Issue Description No matching public GitHub issue or open pull request was found. **What happened?** On the task detail surface, linked GitHub pull requests were labelled `Github Pull Request` and could show a repeated `×N` mention count. The detail-header star control used a labelled outline button rather than the compact icon-only star pattern. **Expected behavior** Linked pull requests should use the concise `Github PR` label without duplicate mention-count decoration, and the detail star action should render as an accessible icon-only ghost button consistent with neighboring controls. **Steps to reproduce** 1. Open a task with a linked GitHub pull request mentioned more than once. 2. Inspect the external-object property label and value row. 3. Inspect the star action in the task detail header. **Paperclip version or commit:** `230126d80b` (`master` at preparation time) **Deployment / installation:** Local development, built from source. **Scope:** Core UI; not adapter-specific, database-related, or configuration-related. ## What Changed - Render GitHub pull-request property labels as `Github PR`. - Remove repeated external-object mention-count decoration from property values. - Render detail-header star controls as icon-only ghost buttons while preserving `aria-label`, pressed, busy, error, and tooltip states. - Expand component coverage for concise labels, duplicate-count suppression, visual variants, and accessible star actions. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/components/IssueProperties.test.tsx src/components/StarToggle.test.tsx` — 54 tests passed. - `pnpm --filter @paperclipai/ui typecheck` — passed. - Visual review (before/after plus normal, starred, pending, and error states): https://htmlpreview.github.io/?https://gist.githubusercontent.com/cryppadotta/9688862a8826c1134aa2b2e8c16509d8/raw/45cb4921851f8968b21e7490e0d705161a9e165d/star-toggle-review.html - `pnpm check:token-gates` — reports five existing `#9627` violations in files unchanged by this PR; the same values are present on `master`. ## Risks - Low risk: changes are limited to task-detail presentation and tests. - The star action remains fully accessible through its existing ARIA label and tooltip, but it no longer displays visible text. - External-object mention counts remain available in data; only the redundant property-row decoration is removed. > 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 Codex, GPT-5.3 Codex, tool-enabled coding agent with repository and shell access. ## 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) - [x] 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>
131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import type { MouseEvent } from "react";
|
|
import { Loader2, Star } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { cn } from "../lib/utils";
|
|
|
|
export interface StarToggleProps {
|
|
/** Whether the resource is currently starred (post-optimistic value). */
|
|
starred: boolean;
|
|
/** Human-readable resource name, used for the accessible label. */
|
|
resourceName: string;
|
|
/** Optimistic mutation in flight — shows a spinner and blocks input. */
|
|
pending?: boolean;
|
|
/** Last mutation failed — surface a retry affordance (red star). */
|
|
error?: boolean;
|
|
/**
|
|
* "row" — quiet icon-only control for sidebar and browse-list rows.
|
|
* "button" — always-visible icon-only control for detail headers.
|
|
*/
|
|
size?: "row" | "button";
|
|
/** Called with the desired next starred value. */
|
|
onToggle: (nextStarred: boolean) => void;
|
|
/**
|
|
* Row variant only: keep the control hidden at rest even when starred, so it
|
|
* only appears on hover/focus. Sidebar rows are "intentionally quiet"; browse
|
|
* rows keep the starred control visible.
|
|
*/
|
|
quiet?: boolean;
|
|
/** Extra classes for the control itself. */
|
|
className?: string;
|
|
/**
|
|
* Row variant only: classes that control at-rest visibility when the resource
|
|
* is not starred (e.g. reveal on hover/focus). Ignored when starred (a starred
|
|
* control is always visible) or on the button variant. Defaults to the shared
|
|
* unnamed-`group` reveal used by browse rows; sidebar passes a named-group
|
|
* variant so it reveals with `group/project` / `group/agent`.
|
|
*/
|
|
revealClassName?: string;
|
|
}
|
|
|
|
const DEFAULT_ROW_REVEAL =
|
|
"opacity-100 sm:opacity-0 sm:transition-opacity sm:group-hover:opacity-100 sm:group-focus-within:opacity-100";
|
|
|
|
export function StarToggle({
|
|
starred,
|
|
resourceName,
|
|
pending = false,
|
|
error = false,
|
|
size = "row",
|
|
onToggle,
|
|
quiet = false,
|
|
className,
|
|
revealClassName,
|
|
}: StarToggleProps) {
|
|
const ariaLabel = starred ? `Unstar ${resourceName}` : `Star ${resourceName}`;
|
|
const Icon = pending ? Loader2 : Star;
|
|
|
|
function handleClick(event: MouseEvent<HTMLButtonElement>) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
if (pending) return;
|
|
// On error, retry the last intent (toggle toward the desired end state).
|
|
onToggle(!starred);
|
|
}
|
|
|
|
if (size === "button") {
|
|
return (
|
|
<Button
|
|
type="button"
|
|
size="icon-sm"
|
|
variant="ghost"
|
|
aria-label={ariaLabel}
|
|
aria-pressed={starred}
|
|
aria-busy={pending ? "true" : undefined}
|
|
disabled={pending}
|
|
onClick={handleClick}
|
|
title={error ? "Couldn't save — retry" : undefined}
|
|
className={cn(
|
|
error
|
|
? "text-red-500 hover:text-red-500"
|
|
: starred
|
|
? "text-amber-600 dark:text-amber-500"
|
|
: undefined,
|
|
className,
|
|
)}
|
|
>
|
|
<Icon
|
|
className={cn(
|
|
"h-4 w-4",
|
|
pending && "motion-safe:animate-spin",
|
|
!pending && starred && !error && "fill-amber-500 text-amber-500",
|
|
!pending && error && "text-red-500",
|
|
)}
|
|
/>
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
// Row variant: a starred (or errored) control is always visible; an unstarred
|
|
// one is quiet and revealed on hover/focus so the nav stays calm. When `quiet`
|
|
// (sidebar), even a starred control hides at rest and reveals on hover/focus.
|
|
const visible = error || pending || (starred && !quiet);
|
|
return (
|
|
<Button
|
|
type="button"
|
|
size="icon-xs"
|
|
variant="ghost"
|
|
aria-label={ariaLabel}
|
|
aria-pressed={starred}
|
|
aria-busy={pending ? "true" : undefined}
|
|
disabled={pending}
|
|
onClick={handleClick}
|
|
title={error ? "Couldn't save — retry" : undefined}
|
|
className={cn(
|
|
"h-6 w-6 shrink-0",
|
|
visible ? "opacity-100" : revealClassName ?? DEFAULT_ROW_REVEAL,
|
|
className,
|
|
)}
|
|
>
|
|
<Icon
|
|
className={cn(
|
|
"h-3.5 w-3.5",
|
|
pending && "motion-safe:animate-spin",
|
|
!pending && error && "text-red-500",
|
|
!pending && !error && starred && "fill-amber-500 text-amber-500",
|
|
!pending && !error && !starred && "text-muted-foreground",
|
|
)}
|
|
/>
|
|
</Button>
|
|
);
|
|
}
|