From fb81505cb144f693d42e10dbd7a43e286c506ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 9 Jul 2026 20:59:41 -0400 Subject: [PATCH] The board card says one thing: pills learn to stay in their box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cards on the Work board overflowed their frame and shouted six facts at once. Two root causes, both fixed at the source: - Pills truncated by counting characters in JS (truncate(title, 18)) while the CSS said white-space: nowrap with no max-width — CJK glyphs are twice as wide, so 18 Chinese characters punched straight through the card edge. The Pill component now owns truncation: max-width + an inner .pill-text with a real ellipsis. Every pill in the app inherits the fix; the JS truncation helper is gone. - The card repeated what every other card already said: identical goal, workspace, and (in the Done column) status pills on all 280 cards carried zero information. A card now states what is particular to it: a title clamped to three lines, the project, the run state while the task is live, the assignee, and the actions. Goal and workspace stay in the task detail dialog where they belong. - Done cards are no longer struck through — a six-line Chinese title with a line through it reads as vandalism, not completion. Done cards just go quiet. Verified visually against the rebuilt app's board screenshot; full suite plus e2e green. --- src/renderer/src/pages/WorkPage.tsx | 39 +++++++++----------------- src/renderer/src/shared/ui.tsx | 2 +- src/renderer/src/styles/components.css | 5 +++- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/renderer/src/pages/WorkPage.tsx b/src/renderer/src/pages/WorkPage.tsx index 78ce99e4..d8d6152c 100644 --- a/src/renderer/src/pages/WorkPage.tsx +++ b/src/renderer/src/pages/WorkPage.tsx @@ -6,14 +6,12 @@ import { TASK_PRIORITY, TASK_STATUS, type AgentRecord, - type GoalRecord, type ProviderRecord, type ProjectRecord, type TaskPriority, type TaskRecord, type TaskRunRecord, type TaskStatus, - type WorkspaceRecord, } from "../../../shared/domain"; import { NewTaskDialog } from "./NewTaskDialog"; import { GoalDialog } from "./work/GoalDialog"; @@ -111,8 +109,6 @@ export function WorkPage({ snapshot, actions }: PageProps) { tasks={tasks} agents={agents} projects={projects} - goals={goals} - workspaces={workspaces} providers={providers} taskRuns={taskRuns} onAdvance={advance} @@ -216,8 +212,6 @@ function BoardView({ tasks, agents, projects, - goals, - workspaces, providers, taskRuns, onAdvance, @@ -228,8 +222,6 @@ function BoardView({ tasks: TaskRecord[]; agents: AgentRecord[]; projects: ProjectRecord[]; - goals: GoalRecord[]; - workspaces: WorkspaceRecord[]; providers: ProviderRecord[]; taskRuns: TaskRunRecord[]; onAdvance: (task: TaskRecord) => void; @@ -255,8 +247,6 @@ function BoardView({ task={task} assignee={agents.find((agent) => agent.id === task.assigneeAgentId) ?? null} project={projects.find((project) => project.id === task.projectId) ?? null} - goal={goals.find((goal) => goal.id === task.goalId) ?? null} - workspace={workspaces.find((workspace) => workspace.id === task.workspaceId) ?? null} provider={providerFor(agents.find((agent) => agent.id === task.assigneeAgentId) ?? null, providers)} latestRun={latestTaskRun(task.id, taskRuns)} onAdvance={() => onAdvance(task)} @@ -275,8 +265,6 @@ function TaskCard({ task, assignee, project, - goal, - workspace, provider, latestRun, onAdvance, @@ -286,8 +274,6 @@ function TaskCard({ task: TaskRecord; assignee: AgentRecord | null; project: ProjectRecord | null; - goal: GoalRecord | null; - workspace: WorkspaceRecord | null; provider: ProviderRecord | null; latestRun: TaskRunRecord | null; onAdvance: () => void; @@ -317,23 +303,29 @@ function TaskCard({
{task.title}
-
- {project ? {truncate(project.name, 22)} : {capitalize(task.risk)}} - {goal && {truncate(goal.title, 18)}} - {workspace && {truncate(workspace.name, 18)}} - {latestRun && {prettyRunState(latestRun.state)}} -
event.stopPropagation()}> +
+ {project ? {project.name} : {capitalize(task.risk)}} + {task.status !== "done" && latestRun && ( + + {prettyRunState(latestRun.state)} + + )} +
event.stopPropagation()}> {assignee && } {assignee && } {nextStatus(task.status) && } @@ -346,8 +338,3 @@ function TaskCard({ function capitalize(value: string): string { return value.charAt(0).toUpperCase() + value.slice(1); } - -function truncate(value: string, max: number): string { - if (value.length <= max) return value; - return `${value.slice(0, max - 1)}…`; -} diff --git a/src/renderer/src/shared/ui.tsx b/src/renderer/src/shared/ui.tsx index 71d2f49f..6efd4831 100644 --- a/src/renderer/src/shared/ui.tsx +++ b/src/renderer/src/shared/ui.tsx @@ -108,7 +108,7 @@ export function Pill({ return ( {dot && } - {children} + {children} ); } diff --git a/src/renderer/src/styles/components.css b/src/renderer/src/styles/components.css index 19627e81..787aeb6c 100644 --- a/src/renderer/src/styles/components.css +++ b/src/renderer/src/styles/components.css @@ -100,9 +100,12 @@ font-size: var(--fs-caption); font-weight: 500; white-space: nowrap; + max-width: 100%; + min-width: 0; transition: background var(--motion-base) var(--ease-out), color var(--motion-base) var(--ease-out), border-color var(--motion-base) var(--ease-out); } -.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-3); transition: background var(--motion-base) var(--ease-out); } +.pill .pill-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-3); flex-shrink: 0; transition: background var(--motion-base) var(--ease-out); } .pill.amber { background: var(--amber-soft); color: var(--amber); border-color: transparent; } .pill.amber .dot { background: var(--amber); } .pill.green { background: var(--green-soft); color: var(--green); border-color: transparent; }