The board card says one thing: pills learn to stay in their box
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.
This commit is contained in:
1 parent
34ea33cb8d
commit
fb81505cb1
3 files changed
+18
-28
No files matched your search
@@ -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({
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
fontSize: 14,
|
||||
fontWeight: 480,
|
||||
lineHeight: 1.35,
|
||||
letterSpacing: "-0.005em",
|
||||
textDecoration: task.status === "done" ? "line-through" : "none",
|
||||
color: task.status === "done" ? "var(--text-3)" : "var(--text-1)",
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{task.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row" style={{ flexWrap: "wrap", gap: 6 }}>
|
||||
{project ? <Pill>{truncate(project.name, 22)}</Pill> : <Pill>{capitalize(task.risk)}</Pill>}
|
||||
{goal && <Pill tone="violet">{truncate(goal.title, 18)}</Pill>}
|
||||
{workspace && <Pill>{truncate(workspace.name, 18)}</Pill>}
|
||||
{latestRun && <Pill tone={runTone(latestRun.state)}>{prettyRunState(latestRun.state)}</Pill>}
|
||||
<div style={{ marginLeft: "auto", display: "flex", gap: 8, alignItems: "center" }} onClick={(event) => event.stopPropagation()}>
|
||||
<div className="row" style={{ flexWrap: "wrap", gap: 6, minWidth: 0 }}>
|
||||
{project ? <Pill>{project.name}</Pill> : <Pill>{capitalize(task.risk)}</Pill>}
|
||||
{task.status !== "done" && latestRun && (
|
||||
<span style={{ flexShrink: 0, display: "inline-flex" }}>
|
||||
<Pill tone={runTone(latestRun.state)}>{prettyRunState(latestRun.state)}</Pill>
|
||||
</span>
|
||||
)}
|
||||
<div style={{ marginLeft: "auto", display: "flex", gap: 8, alignItems: "center", flexShrink: 0 }} onClick={(event) => event.stopPropagation()}>
|
||||
{assignee && <Avatar name={assignee.name} size="sm" bot />}
|
||||
{assignee && <IconButton icon="send" label={canRun(provider) ? t("Run {title}", { title: task.title }) : t("Connect a provider to run")} onClick={onRun} disabled={!canRun(provider)} />}
|
||||
{nextStatus(task.status) && <IconButton icon="check" label={t("Advance")} onClick={onAdvance} />}
|
||||
@@ -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)}…`;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export function Pill({
|
||||
return (
|
||||
<span className={`pill ${tone ?? ""}`.trim()}>
|
||||
{dot && <span className="dot" />}
|
||||
{children}
|
||||
<span className="pill-text">{children}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in new issue
Block a user