fix(dashboard): restore step/comment fields on slim list and detail tabs
The previous slim listing dropped log, comments, steps, workflowStepResults, and steeringComments from the board task payload. Of those, only `log` is actually heavy (~60 MB across 1200 tasks); everything else combined is under 500 KB and is needed by the board UI: - TaskCard step progress badge reads task.steps - TaskCard comment count badge reads task.comments - Workflow status indicators read task.workflowStepResults Slim mode now drops *only* log. The other JSON columns stay in board payloads, so progress bars and badges render again without forcing a full per-task fetch. Also fix the TaskDetailModal regression where the Activity tab and the Step Progress section read task.log/task.steps from the slim board prop instead of workingTask (the full row loaded via fetchTaskDetail). The prop is the cached slim row from the board, so the activity tab was empty until the user scrolled — now both tabs read workingTask. Updated the slim listTasks regression test to assert the new contract: log is dropped, but steps/comments/workflowStepResults/steeringComments match the full row. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1323,7 +1323,7 @@ describe("TaskStore", () => {
|
|||||||
expect(paged[0].id).toBe("FN-002");
|
expect(paged[0].id).toBe("FN-002");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("slim mode returns metadata but drops heavy fields (log/comments/steps)", async () => {
|
it("slim mode drops the agent log but keeps board-visible fields (steps/comments)", async () => {
|
||||||
const task = await store.createTask({ description: "Slim test" });
|
const task = await store.createTask({ description: "Slim test" });
|
||||||
await store.logEntry(task.id, "heavy log entry that should not appear in slim list");
|
await store.logEntry(task.id, "heavy log entry that should not appear in slim list");
|
||||||
|
|
||||||
@@ -1333,13 +1333,22 @@ describe("TaskStore", () => {
|
|||||||
const full = fullList.find((t) => t.id === task.id)!;
|
const full = fullList.find((t) => t.id === task.id)!;
|
||||||
const slim = slimList.find((t) => t.id === task.id)!;
|
const slim = slimList.find((t) => t.id === task.id)!;
|
||||||
|
|
||||||
|
// Sanity: the full row really has the log we wrote.
|
||||||
expect(full.log.length).toBeGreaterThan(0);
|
expect(full.log.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Slim must drop the heavy log payload (the only field worth slimming).
|
||||||
expect(slim.id).toBe(task.id);
|
expect(slim.id).toBe(task.id);
|
||||||
expect(slim.description).toBe("Slim test");
|
expect(slim.description).toBe("Slim test");
|
||||||
expect(slim.column).toBe(full.column);
|
expect(slim.column).toBe(full.column);
|
||||||
expect(slim.log).toEqual([]);
|
expect(slim.log).toEqual([]);
|
||||||
expect(slim.steps).toEqual([]);
|
|
||||||
expect(slim.comments).toBeUndefined();
|
// Slim must STILL include the small JSON columns the board UI reads:
|
||||||
|
// step progress, comment counts, workflow status, steering badges.
|
||||||
|
// (Dropping them silently broke TaskCard progress bars and the comments tab.)
|
||||||
|
expect(slim.steps).toEqual(full.steps);
|
||||||
|
expect(slim.comments).toEqual(full.comments);
|
||||||
|
expect(slim.workflowStepResults).toEqual(full.workflowStepResults);
|
||||||
|
expect(slim.steeringComments).toEqual(full.steeringComments);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includeArchived=false excludes archived tasks; default includes them", async () => {
|
it("includeArchived=false excludes archived tasks; default includes them", async () => {
|
||||||
|
|||||||
@@ -1323,6 +1323,15 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
const slim = options?.slim ?? false;
|
const slim = options?.slim ?? false;
|
||||||
const columnFilter = options?.column;
|
const columnFilter = options?.column;
|
||||||
|
|
||||||
|
// Slim mode drops ONLY the agent log column. On busy boards `log` accounts
|
||||||
|
// for ~99% of the row payload (60+ MB across 1200 tasks); every other JSON
|
||||||
|
// column combined is under 500 KB and is needed by the board UI:
|
||||||
|
// - `steps` → step progress badge on TaskCard
|
||||||
|
// - `comments` → comment count badge on TaskCard
|
||||||
|
// - `workflowStepResults` → workflow status indicators
|
||||||
|
// - `steeringComments` → steering badge
|
||||||
|
// Use `getTask(id)` to load the full row (including `log`) for the
|
||||||
|
// TaskDetailModal's Activity tab and Agent Log subview.
|
||||||
const slimColumns = `
|
const slimColumns = `
|
||||||
id, title, description, "column", status, size, reviewLevel, currentStep,
|
id, title, description, "column", status, size, reviewLevel, currentStep,
|
||||||
worktree, blockedBy, paused, baseBranch, branch, baseCommitSha,
|
worktree, blockedBy, paused, baseBranch, branch, baseCommitSha,
|
||||||
@@ -1332,7 +1341,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
mergeRetries, stuckKillCount, recoveryRetryCount, nextRecoveryAt,
|
mergeRetries, stuckKillCount, recoveryRetryCount, nextRecoveryAt,
|
||||||
error, summary, thinkingLevel,
|
error, summary, thinkingLevel,
|
||||||
createdAt, updatedAt, columnMovedAt,
|
createdAt, updatedAt, columnMovedAt,
|
||||||
dependencies,
|
dependencies, steps, comments, workflowStepResults, steeringComments,
|
||||||
attachments, prInfo, issueInfo, mergeDetails,
|
attachments, prInfo, issueInfo, mergeDetails,
|
||||||
breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles,
|
breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles,
|
||||||
missionId, sliceId, assignedAgentId, assigneeUserId,
|
missionId, sliceId, assignedAgentId, assigneeUserId,
|
||||||
|
|||||||
@@ -1083,9 +1083,9 @@ export function TaskDetailModal({
|
|||||||
) : (
|
) : (
|
||||||
<div className="detail-activity">
|
<div className="detail-activity">
|
||||||
<h4>Activity</h4>
|
<h4>Activity</h4>
|
||||||
{task.log && task.log.length > 0 ? (
|
{workingTask.log && workingTask.log.length > 0 ? (
|
||||||
<div className="detail-activity-list">
|
<div className="detail-activity-list">
|
||||||
{[...task.log].reverse().map((entry, i) => (
|
{[...workingTask.log].reverse().map((entry, i) => (
|
||||||
<div key={i} className="detail-log-entry">
|
<div key={i} className="detail-log-entry">
|
||||||
<div className="detail-log-header">
|
<div className="detail-log-header">
|
||||||
<span className="detail-log-timestamp">
|
<span className="detail-log-timestamp">
|
||||||
@@ -1180,10 +1180,10 @@ export function TaskDetailModal({
|
|||||||
</div>
|
</div>
|
||||||
<div className="detail-section detail-step-progress">
|
<div className="detail-section detail-step-progress">
|
||||||
<h4>Progress</h4>
|
<h4>Progress</h4>
|
||||||
{task.steps && task.steps.length > 0 ? (
|
{workingTask.steps && workingTask.steps.length > 0 ? (
|
||||||
<div className="step-progress-wrapper">
|
<div className="step-progress-wrapper">
|
||||||
<div className="step-progress-bar">
|
<div className="step-progress-bar">
|
||||||
{task.steps.map((step, index) => (
|
{workingTask.steps.map((step, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`step-progress-segment step-progress-segment--${step.status}`}
|
className={`step-progress-segment step-progress-segment--${step.status}`}
|
||||||
@@ -1193,7 +1193,7 @@ export function TaskDetailModal({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<span className="step-progress-label">
|
<span className="step-progress-label">
|
||||||
{task.steps.filter(s => s.status === "done").length}/{task.steps.length} steps
|
{workingTask.steps.filter(s => s.status === "done").length}/{workingTask.steps.length} steps
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user