Skip to content

Commit faa9542

Browse files
authored
Polish Tasks panel UI/UX and remove experimental feature flag (#815)
- Refine user-facing strings for clarity and consistency - Add snapshot date display with relative-time tooltip - Fix collapsible section resize when content children are swapped - Remove the coder.experimental.tasks feature flag so the Tasks panel is visible to all authenticated users.
1 parent 8668d7b commit faa9542

25 files changed

+310
-94
lines changed

package.json

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,6 @@
139139
"experimental"
140140
]
141141
},
142-
"coder.experimental.tasks": {
143-
"markdownDescription": "Enable the experimental [Tasks](https://coder.com/docs/ai-coder/tasks) panel in VS Code. When enabled, a sidebar panel lets you run and manage AI coding agents in Coder workspaces. This feature is under active development and may change. Requires a Coder deployment with Tasks support.",
144-
"type": "boolean",
145-
"default": false,
146-
"tags": [
147-
"experimental"
148-
]
149-
},
150142
"coder.sshFlags": {
151143
"markdownDescription": "Additional flags to pass to the `coder ssh` command when establishing SSH connections. Enter each flag as a separate array item; values are passed verbatim and in order. See the [CLI ssh reference](https://coder.com/docs/reference/cli/ssh) for available flags.\n\nNote: `--network-info-dir` and `--ssh-host-prefix` are ignored (managed internally). Prefer `#coder.proxyLogDirectory#` over `--log-dir`/`-l` for full functionality.",
152144
"type": "array",
@@ -219,7 +211,7 @@
219211
"id": "coder.tasksPanel",
220212
"name": "Coder Tasks",
221213
"icon": "media/tasks-logo.svg",
222-
"when": "coder.authenticated && coder.tasksEnabled"
214+
"when": "coder.authenticated"
223215
}
224216
]
225217
},
@@ -426,7 +418,7 @@
426418
},
427419
{
428420
"command": "coder.tasks.refresh",
429-
"when": "coder.authenticated && coder.tasksEnabled && view == coder.tasksPanel",
421+
"when": "coder.authenticated && view == coder.tasksPanel",
430422
"group": "navigation@1"
431423
}
432424
],
@@ -474,7 +466,7 @@
474466
"@peculiar/x509": "^1.14.3",
475467
"@repo/shared": "workspace:*",
476468
"axios": "1.13.6",
477-
"date-fns": "^4.1.0",
469+
"date-fns": "catalog:",
478470
"eventsource": "^4.1.0",
479471
"find-process": "^2.1.0",
480472
"jsonc-parser": "^3.3.1",

packages/shared/src/tasks/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export interface TaskPreset {
3131

3232
/** Result of fetching task logs: either logs or an error/unavailable state. */
3333
export type TaskLogs =
34-
| { status: "ok"; logs: readonly TaskLogEntry[] }
34+
| {
35+
status: "ok";
36+
logs: readonly TaskLogEntry[];
37+
snapshot?: boolean;
38+
snapshotAt?: string;
39+
}
3540
| { status: "not_available" }
3641
| { status: "error" };
3742

packages/shared/src/tasks/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,14 @@ export function isAgentStarting(task: Task): boolean {
7979
export function isWorkspaceStarting(task: Task): boolean {
8080
return isBuildingWorkspace(task) || isAgentStarting(task);
8181
}
82+
83+
/** Label for the log preview header, matching the Coder dashboard pattern. */
84+
export function logPreviewLabel(count: number): string {
85+
if (count === 0) {
86+
return "AI chat messages";
87+
}
88+
if (count === 1) {
89+
return "Last message of AI chat";
90+
}
91+
return `Last ${count} messages of AI chat`;
92+
}

packages/tasks/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@tanstack/react-query": "catalog:",
1515
"@vscode-elements/react-elements": "catalog:",
1616
"@vscode/codicons": "catalog:",
17+
"date-fns": "catalog:",
1718
"react": "catalog:",
1819
"react-dom": "catalog:"
1920
},

packages/tasks/src/components/AgentChatHistory.tsx

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import {
2+
logPreviewLabel,
3+
type TaskLogEntry,
4+
type TaskLogs,
5+
} from "@repo/shared";
6+
import { formatDistanceToNowStrict } from "date-fns";
7+
18
import { LogViewer, LogViewerPlaceholder } from "./LogViewer";
29

3-
import type { TaskLogEntry, TaskLogs } from "@repo/shared";
10+
import type { ReactNode } from "react";
411

512
interface AgentChatHistoryProps {
613
taskLogs: TaskLogs;
@@ -18,22 +25,47 @@ function LogEntry({
1825
<div className={`log-entry log-entry-${log.type}`}>
1926
{isGroupStart && (
2027
<div className="log-entry-role">
21-
{log.type === "input" ? "You" : "Agent"}
28+
{log.type === "input" ? "[User]" : "[Agent]"}
2229
</div>
2330
)}
2431
{log.content}
2532
</div>
2633
);
2734
}
2835

36+
function chatHistoryHeader(taskLogs: TaskLogs): ReactNode {
37+
if (taskLogs.status !== "ok" || taskLogs.snapshot !== true) {
38+
return "Chat history";
39+
}
40+
const label = logPreviewLabel(taskLogs.logs.length);
41+
if (taskLogs.snapshotAt === undefined) {
42+
return label;
43+
}
44+
const relativeTime = formatDistanceToNowStrict(
45+
new Date(taskLogs.snapshotAt),
46+
{ addSuffix: true },
47+
);
48+
return (
49+
<>
50+
{label}{" "}
51+
<span className="snapshot-info">
52+
<span className="codicon codicon-info" />
53+
<span className="snapshot-info-tooltip">
54+
Snapshot taken {relativeTime}
55+
</span>
56+
</span>
57+
</>
58+
);
59+
}
60+
2961
export function AgentChatHistory({
3062
taskLogs,
3163
isThinking,
3264
}: AgentChatHistoryProps) {
3365
const logs = taskLogs.status === "ok" ? taskLogs.logs : [];
3466

3567
return (
36-
<LogViewer header="Agent chat history">
68+
<LogViewer header={chatHistoryHeader(taskLogs)}>
3769
{logs.length === 0 ? (
3870
<LogViewerPlaceholder error={taskLogs.status === "error"}>
3971
{getEmptyMessage(taskLogs.status)}
@@ -57,9 +89,9 @@ export function AgentChatHistory({
5789
function getEmptyMessage(status: TaskLogs["status"]): string {
5890
switch (status) {
5991
case "not_available":
60-
return "Logs not available in current task state";
92+
return "Messages are not available yet";
6193
case "error":
62-
return "Failed to load logs";
94+
return "Failed to load messages";
6395
case "ok":
6496
return "No messages yet";
6597
}

packages/tasks/src/components/CreateTaskSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function CreateTaskSection({ templates }: CreateTaskSectionProps) {
5353
onSubmit={handleSubmit}
5454
loading={isPending}
5555
actionIcon="send"
56-
actionLabel="Send"
56+
actionLabel="Create task"
5757
actionEnabled={canSubmit === true}
5858
/>
5959
{error && <div className="create-task-error">{error.message}</div>}

packages/tasks/src/components/LogViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useFollowScroll } from "../hooks/useFollowScroll";
55
import type { ReactNode } from "react";
66

77
interface LogViewerProps {
8-
header: string;
8+
header: ReactNode;
99
children: ReactNode;
1010
}
1111

packages/tasks/src/components/NoTemplateState.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const DOCS_URL = "https://coder.com/docs/admin/templates";
77
export function NoTemplateState() {
88
return (
99
<StatePanel
10-
title="No Task template found"
10+
title="No task templates found"
1111
action={
1212
<a href={DOCS_URL} className="text-link">
1313
Learn how to create a template <VscodeIcon name="link-external" />

packages/tasks/src/components/TaskMessageInput.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,20 @@ function getPlaceholder(task: Task): string {
3131
return "Waiting for the agent to start...";
3232
case "error":
3333
case "unknown":
34-
return "Task is in an error state and cannot receive messages";
34+
return "This task encountered an error";
3535
case "active":
3636
break;
3737
}
3838

3939
switch (task.current_state?.state) {
4040
case "working":
41-
return "Agent is working — you can pause or wait for it to finish...";
41+
return "Agent is working...";
4242
case "complete":
43-
return "Task completed — send a follow-up to continue...";
43+
return "Send a follow-up to continue...";
4444
case "failed":
45-
return "Task failed — send a message to retry...";
45+
return "Send a message to retry...";
4646
default:
47-
return "Send a message to the agent...";
47+
return "Send a message...";
4848
}
4949
}
5050

packages/tasks/src/components/useTaskMenuItems.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function useTaskMenuItems({
8787
menuItems.push({ separator: true });
8888

8989
menuItems.push({
90-
label: "Delete",
90+
label: "Delete Task",
9191
icon: "trash",
9292
onClick: () =>
9393
run("deleting", () => api.deleteTask({ taskId: task.id, taskName })),

0 commit comments

Comments
 (0)