Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions apps/desktop/src/renderer/styles/task-ledger.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,24 @@
min-height: 0;
overflow-y: auto;
padding: var(--space-2);
/* The compact row layout keys off THIS panel's width, not the viewport: the
right workbar resizes down to SESSION_WORKBAR_MIN_WIDTH (320px) inside an
otherwise wide window, where a viewport media query would never fire. */
container: maka-task-ledger / inline-size;
}

.maka-task-ledger-tree {
display: grid;
gap: var(--border-width-hairline);
min-width: 0;
}

.maka-task-ledger-row {
display: grid;
min-width: 0;
}

.maka-task-ledger-row-content {
font: var(--maka-text-supporting);
display: grid;
grid-template-columns: 14px minmax(34px, auto) minmax(100px, 1fr) auto;
Comment thread
liuxiaocs7 marked this conversation as resolved.
Expand All @@ -45,18 +55,19 @@
display: grid;
grid-column: 1 / -1;
gap: var(--border-width-hairline);
min-width: 0;
}

.maka-task-ledger-row:hover { background: var(--state-hover-bg); }
.maka-task-ledger-row:hover > .maka-task-ledger-row-content { background: var(--state-hover-bg); }

.maka-task-ledger-row:focus-visible {
.maka-task-ledger-row:focus-visible > .maka-task-ledger-row-content {
outline: none;
box-shadow: inset 0 0 0 var(--focus-ring-width) var(--focus-ring);
}
.maka-task-ledger-row[data-status="in_progress"] > svg { color: var(--status-running); }
.maka-task-ledger-row[data-status="blocked"] > svg { color: var(--warning); }
.maka-task-ledger-row[data-status="completed"] > svg { color: var(--success); }
.maka-task-ledger-row[data-status="failed"] > svg { color: var(--destructive); }
.maka-task-ledger-row[data-status="in_progress"] > .maka-task-ledger-row-content > svg { color: var(--status-running); }
.maka-task-ledger-row[data-status="blocked"] > .maka-task-ledger-row-content > svg { color: var(--warning); }
.maka-task-ledger-row[data-status="completed"] > .maka-task-ledger-row-content > svg { color: var(--success); }
.maka-task-ledger-row[data-status="failed"] > .maka-task-ledger-row-content > svg { color: var(--destructive); }

.maka-task-ledger-key {
font: var(--maka-text-supporting);
Expand Down Expand Up @@ -128,8 +139,12 @@
outline-offset: 1px;
}

@media (max-width: 620px) {
.maka-task-ledger-row { grid-template-columns: 14px minmax(32px, auto) minmax(0, 1fr); }
/* Below ~420px of panel width the four-column grid can no longer hold the
deepest clamped indent (128px at depth 6) plus key + subject + status/meta,
so status/detail drop under the subject and the subject column may shrink to
zero. 420px keeps the 480px default wide while covering the 320px floor. */
@container maka-task-ledger (max-width: 420px) {
.maka-task-ledger-row-content { grid-template-columns: 14px minmax(32px, auto) minmax(0, 1fr); }
.maka-task-ledger-meta { grid-column: 3; justify-content: flex-start; }
.maka-task-ledger-detail { grid-column: 3; white-space: normal; overflow-wrap: anywhere; }
}
115 changes: 115 additions & 0 deletions apps/desktop/stories/session-workbar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,20 @@ const tasks: Task[] = [
}),
];

// An eight-level parent→child chain, all still active, to exercise the task
// panel's depth clamp (`Math.min(depth, 6)`): a level-8 row indents no further
// than level 7.
const deepTaskChain: Task[] = Array.from({ length: 8 }, (_, index) => {
const level = index + 1;
return task({
id: `td-${level}`,
key: `T${Array.from({ length: level }, () => '1').join('.')}`,
subject: `第 ${level} 层子任务,验证深层缩进仍可完整显示`,
parentId: level === 1 ? undefined : `td-${level - 1}`,
status: level === 8 ? 'in_progress' : 'pending',
});
});

const artifacts: ArtifactRecord[] = [
{
id: 'artifact-patch',
Expand Down Expand Up @@ -1228,6 +1242,107 @@ export const TasksLoadFailed: Story = {
render: () => <Workbar tab="tasks" />,
};

// Real path: 任务工作栏 → 任务 after a run finished with mixed outcomes. The
// 最近结束 section (collapsed by default) holds the terminal tasks — a failed
// one with its reason and a cancelled one — and caps at three, so a fourth
// finished task is dropped rather than growing the list.
export const TasksRecentlyFinished: Story = {
decorators: [
bridge({
tasks: [
task({
id: 'trf-active',
key: 'T1',
subject: '巡检发布前检查项',
status: 'in_progress',
owner: { actor: 'main_agent', runId: 'run-trf' },
}),
task({
id: 'trf-old',
key: 'T2',
subject: '归档上一轮实验数据',
status: 'completed',
completionEvidence: '已归档到对象存储。',
endedAt: NOW - 300_000,
updatedAt: NOW - 300_000,
}),
task({
id: 'trf-notes',
key: 'T3',
subject: '生成变更说明',
status: 'completed',
endedAt: NOW - 120_000,
updatedAt: NOW - 120_000,
}),
task({
id: 'trf-smoke',
key: 'T4',
subject: '回归冒烟用例',
status: 'failed',
failureReason: '两个用例在 CI 上超时,任务标记为失败。',
endedAt: NOW - 90_000,
updatedAt: NOW - 90_000,
}),
task({
id: 'trf-dupe',
key: 'T5',
subject: '取消重复的部署任务',
status: 'cancelled',
endedAt: NOW - 30_000,
updatedAt: NOW - 30_000,
}),
],
}),
],
render: () => <Workbar tab="tasks" />,
play: async ({ canvasElement }) => {
// Four terminal tasks, but the recent section freezes its count at three.
const trigger = await waitFor(() => {
const el = canvasElement.querySelector<HTMLElement>('.maka-task-ledger-terminal-trigger');
if (!el) throw new Error('recent-finished trigger not rendered yet');
return el;
});
await expect(trigger.textContent).toContain('3');
await userEvent.click(trigger);
await waitFor(() => {
// Failed and cancelled outcomes both render, with the failed reason…
const failed = canvasElement.querySelector('.maka-task-ledger-row[data-status="failed"]');
expect(failed?.textContent).toContain('两个用例在 CI 上超时');
expect(
canvasElement.querySelector('.maka-task-ledger-row[data-status="cancelled"]'),
).not.toBeNull();
// …and the oldest finished task is the one the cap dropped.
expect(canvasElement.textContent).not.toContain('归档上一轮实验数据');
});
},
};

// Real path: 任务工作栏 → 任务 on a deeply decomposed task. The row indent is
// clamped at depth 6, so an eighth-level subtask stays legible instead of
// marching off the panel. Pinned at the 320px workbar floor
// (SESSION_WORKBAR_MIN_WIDTH), the width where the depth-6 indent would
// overflow a four-column row — the panel container query must collapse to the
// compact layout so the deepest row still fits.
export const TasksDeepNesting: Story = {
decorators: [bridge({ tasks: deepTaskChain })],
render: () => <Workbar tab="tasks" width={320} />,
play: async ({ canvasElement }) => {
await waitFor(() => {
const panel = canvasElement.querySelector<HTMLElement>('.maka-task-ledger-panel');
const deepestRowContent = canvasElement.querySelector<HTMLElement>(
'.maka-task-ledger-row[aria-level="8"] > .maka-task-ledger-row-content',
);
if (!panel || !deepestRowContent) throw new Error('deep task row not rendered yet');

const panelRect = panel.getBoundingClientRect();
const rowRect = deepestRowContent.getBoundingClientRect();
expect(panel.scrollWidth).toBeLessThanOrEqual(panel.clientWidth);
expect(rowRect.left).toBeGreaterThanOrEqual(panelRect.left);
expect(rowRect.right).toBeLessThanOrEqual(panelRect.right);
});
},
};

// Storybook cannot host the native WebContentsView, so these pin what the panel
// itself draws — chrome and empty state — inside the real workbar shell.
export const BrowserEmpty: Story = {
Expand Down
18 changes: 10 additions & 8 deletions packages/ui/src/task-ledger-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,16 @@ function TaskLedgerRow({ task, copy, level, position, setSize, children }: {
data-status={task.status}
style={{ '--task-depth': Math.min(depth, 6) } as CSSProperties}
>
<StatusIcon size={ICON_SIZE.control} aria-hidden="true" />
<code className="maka-task-ledger-key">{task.key}</code>
<span className="maka-task-ledger-subject" title={task.subject}>{task.subject}</span>
<span className="maka-task-ledger-meta">
<span>{copy.status[task.status]}</span>
{owner && <span title={owner}>{owner}</span>}
</span>
{detail && <span className="maka-task-ledger-detail" title={detail}>{detail}</span>}
<div className="maka-task-ledger-row-content">
<StatusIcon size={ICON_SIZE.control} aria-hidden="true" />
<code className="maka-task-ledger-key">{task.key}</code>
<span className="maka-task-ledger-subject" title={task.subject}>{task.subject}</span>
<span className="maka-task-ledger-meta">
<span>{copy.status[task.status]}</span>
{owner && <span title={owner}>{owner}</span>}
</span>
{detail && <span className="maka-task-ledger-detail" title={detail}>{detail}</span>}
</div>
{children}
</div>
);
Expand Down