stagent 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +44 -50
  2. package/package.json +1 -1
  3. package/public/readme/cost-usage-list.png +0 -0
  4. package/public/readme/dashboard-bulk-select.png +0 -0
  5. package/public/readme/dashboard-card-edit.png +0 -0
  6. package/public/readme/dashboard-create-form-ai-applied.png +0 -0
  7. package/public/readme/dashboard-create-form-ai-assist.png +0 -0
  8. package/public/readme/dashboard-create-form-empty.png +0 -0
  9. package/public/readme/dashboard-create-form-filled.png +0 -0
  10. package/public/readme/dashboard-filtered.png +0 -0
  11. package/public/readme/dashboard-list.png +0 -0
  12. package/public/readme/dashboard-workflow-confirm.png +0 -0
  13. package/public/readme/home-below-fold.png +0 -0
  14. package/public/readme/home-list.png +0 -0
  15. package/public/readme/inbox-list.png +0 -0
  16. package/public/readme/playbook-list.png +0 -0
  17. package/public/readme/profiles-list.png +0 -0
  18. package/public/readme/settings-list.png +0 -0
  19. package/public/readme/workflows-list.png +0 -0
  20. package/src/app/api/tasks/[id]/route.ts +54 -3
  21. package/src/app/api/workflows/[id]/route.ts +43 -4
  22. package/src/app/api/workflows/[id]/status/route.ts +70 -2
  23. package/src/app/api/workflows/from-assist/route.ts +6 -32
  24. package/src/app/dashboard/page.tsx +59 -21
  25. package/src/app/documents/[id]/page.tsx +10 -8
  26. package/src/app/globals.css +11 -0
  27. package/src/app/page.tsx +60 -3
  28. package/src/app/tasks/[id]/page.tsx +22 -2
  29. package/src/components/costs/cost-dashboard.tsx +1 -1
  30. package/src/components/dashboard/greeting.tsx +3 -1
  31. package/src/components/dashboard/priority-queue.tsx +58 -9
  32. package/src/components/dashboard/stats-cards.tsx +16 -2
  33. package/src/components/documents/document-chip-bar.tsx +183 -0
  34. package/src/components/documents/document-content-renderer.tsx +146 -0
  35. package/src/components/documents/document-detail-view.tsx +16 -239
  36. package/src/components/documents/image-zoom-view.tsx +60 -0
  37. package/src/components/documents/smart-extracted-text.tsx +47 -0
  38. package/src/components/documents/utils.ts +70 -0
  39. package/src/components/notifications/inbox-list.tsx +4 -5
  40. package/src/components/notifications/notification-item.tsx +72 -8
  41. package/src/components/notifications/pending-approval-host.tsx +7 -4
  42. package/src/components/playbook/playbook-detail-view.tsx +6 -4
  43. package/src/components/profiles/profile-browser.tsx +1 -0
  44. package/src/components/profiles/profile-card.tsx +16 -8
  45. package/src/components/profiles/profile-detail-view.tsx +6 -1
  46. package/src/components/shared/app-sidebar.tsx +2 -2
  47. package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
  48. package/src/components/tasks/ai-assist-panel.tsx +108 -78
  49. package/src/components/tasks/content-preview.tsx +2 -1
  50. package/src/components/tasks/kanban-board.tsx +57 -5
  51. package/src/components/tasks/kanban-column.tsx +34 -23
  52. package/src/components/tasks/task-bento-cell.tsx +50 -0
  53. package/src/components/tasks/task-bento-grid.tsx +155 -0
  54. package/src/components/tasks/task-card.tsx +14 -16
  55. package/src/components/tasks/task-chip-bar.tsx +207 -0
  56. package/src/components/tasks/task-detail-view.tsx +42 -190
  57. package/src/components/tasks/task-result-renderer.tsx +33 -0
  58. package/src/components/workflows/blueprint-gallery.tsx +19 -12
  59. package/src/components/workflows/blueprint-preview.tsx +8 -1
  60. package/src/components/workflows/loop-status-view.tsx +2 -8
  61. package/src/components/workflows/swarm-dashboard.tsx +2 -3
  62. package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
  63. package/src/components/workflows/workflow-full-output.tsx +80 -0
  64. package/src/components/workflows/workflow-kanban-card.tsx +121 -0
  65. package/src/components/workflows/workflow-list.tsx +47 -42
  66. package/src/components/workflows/workflow-status-view.tsx +160 -20
  67. package/src/lib/agents/learning-session.ts +138 -18
  68. package/src/lib/constants/card-icons.tsx +202 -0
  69. package/src/lib/constants/prose-styles.ts +7 -0
  70. package/src/lib/constants/task-status.ts +3 -0
  71. package/src/lib/docs/reader.ts +8 -3
  72. package/src/lib/documents/context-builder.ts +41 -0
  73. package/src/lib/queries/chart-data.ts +20 -1
  74. package/src/lib/workflows/engine.ts +57 -61
  75. package/src/lib/workflows/types.ts +2 -0
  76. package/tsconfig.json +2 -1
  77. package/src/components/documents/document-preview.tsx +0 -68
@@ -2,10 +2,11 @@
2
2
 
3
3
  import { Badge } from "@/components/ui/badge";
4
4
  import { Card } from "@/components/ui/card";
5
- import { Shield, MessageCircle, CheckCircle, XCircle, Eye, EyeOff, Trash2, Wallet } from "lucide-react";
5
+ import { Shield, MessageCircle, CheckCircle, XCircle, Eye, EyeOff, Trash2, Wallet, Brain, ChevronRight, ChevronDown } from "lucide-react";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { LightMarkdown } from "@/components/shared/light-markdown";
8
8
  import { useState } from "react";
9
+ import { useRouter } from "next/navigation";
9
10
  import { PermissionAction } from "./permission-action";
10
11
  import { MessageResponse, type Question } from "./message-response";
11
12
  import { FailureAction } from "./failure-action";
@@ -41,6 +42,8 @@ const typeIcons: Record<string, React.ReactNode> = {
41
42
  task_completed: <CheckCircle className="h-4 w-4 text-chart-2" aria-hidden="true" />,
42
43
  task_failed: <XCircle className="h-4 w-4 text-destructive" aria-hidden="true" />,
43
44
  budget_alert: <Wallet className="h-4 w-4 text-status-warning" aria-hidden="true" />,
45
+ context_proposal: <Brain className="h-4 w-4 text-chart-4" aria-hidden="true" />,
46
+ context_proposal_batch: <Brain className="h-4 w-4 text-chart-4" aria-hidden="true" />,
44
47
  };
45
48
 
46
49
  const typeLabels: Record<string, string> = {
@@ -49,6 +52,8 @@ const typeLabels: Record<string, string> = {
49
52
  task_completed: "Task completed",
50
53
  task_failed: "Task failed",
51
54
  budget_alert: "Budget alert",
55
+ context_proposal: "Self-learning",
56
+ context_proposal_batch: "Self-learning batch",
52
57
  };
53
58
 
54
59
  function formatToolInput(
@@ -95,12 +100,31 @@ function formatToolInput(
95
100
  );
96
101
  }
97
102
 
103
+ const navigableTypes = new Set(["task_completed", "task_failed", "permission_required", "agent_message"]);
104
+
98
105
  export function NotificationItem({ notification, onUpdated }: NotificationItemProps) {
106
+ const router = useRouter();
99
107
  const [toggling, setToggling] = useState(false);
100
108
  const [dismissing, setDismissing] = useState(false);
109
+ const [expanded, setExpanded] = useState(false);
101
110
  const isUnread = !notification.read;
102
111
  const hasResponse = !!notification.response;
103
112
  const parsedToolInput = parseNotificationToolInput(notification.toolInput);
113
+ const isNavigable = !!notification.taskId && navigableTypes.has(notification.type);
114
+
115
+ async function handleNavigate() {
116
+ if (!isNavigable) return;
117
+ // Mark as read on click-through
118
+ if (isUnread) {
119
+ await fetch(`/api/notifications/${notification.id}`, {
120
+ method: "PATCH",
121
+ headers: { "Content-Type": "application/json" },
122
+ body: JSON.stringify({ read: true }),
123
+ });
124
+ onUpdated();
125
+ }
126
+ router.push(`/tasks/${notification.taskId}`);
127
+ }
104
128
 
105
129
  async function toggleRead() {
106
130
  setToggling(true);
@@ -134,9 +158,17 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
134
158
  isUnread
135
159
  ? "surface-card border-l-4 border-l-primary"
136
160
  : "surface-card-muted"
137
- }`}
161
+ }${isNavigable ? " cursor-pointer hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" : ""}`}
138
162
  role="article"
139
163
  aria-label={`${typeLabels[notification.type] ?? "Notification"}: ${notification.title}${isUnread ? " (unread)" : ""}`}
164
+ tabIndex={isNavigable ? 0 : undefined}
165
+ onClick={isNavigable ? handleNavigate : undefined}
166
+ onKeyDown={isNavigable ? (e: React.KeyboardEvent) => {
167
+ if (e.key === "Enter" || e.key === " ") {
168
+ e.preventDefault();
169
+ handleNavigate();
170
+ }
171
+ } : undefined}
140
172
  >
141
173
  <div className="flex items-start gap-3">
142
174
  <div className="mt-0.5">{typeIcons[notification.type]}</div>
@@ -175,11 +207,43 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
175
207
  {notification.body &&
176
208
  notification.type !== "permission_required" &&
177
209
  notification.type !== "agent_message" && (
178
- <LightMarkdown
179
- content={notification.body}
180
- lineClamp={3}
181
- textSize="sm"
182
- />
210
+ <div className="mt-1" onClick={(e) => e.stopPropagation()}>
211
+ {expanded ? (
212
+ <>
213
+ <div className="prose prose-sm dark:prose-invert max-w-none max-h-96 overflow-auto rounded-md border bg-muted/30 p-3">
214
+ <LightMarkdown content={notification.body} textSize="sm" />
215
+ </div>
216
+ <Button
217
+ variant="ghost"
218
+ size="sm"
219
+ className="mt-1 h-6 text-xs text-muted-foreground"
220
+ onClick={(e) => { e.stopPropagation(); setExpanded(false); }}
221
+ >
222
+ <ChevronDown className="h-3 w-3 mr-1" />
223
+ Collapse
224
+ </Button>
225
+ </>
226
+ ) : (
227
+ <>
228
+ <LightMarkdown
229
+ content={notification.body}
230
+ lineClamp={2}
231
+ textSize="sm"
232
+ />
233
+ {notification.body.length > 200 && (
234
+ <Button
235
+ variant="ghost"
236
+ size="sm"
237
+ className="mt-1 h-6 text-xs text-muted-foreground"
238
+ onClick={(e) => { e.stopPropagation(); setExpanded(true); }}
239
+ >
240
+ <ChevronRight className="h-3 w-3 mr-1" />
241
+ Expand
242
+ </Button>
243
+ )}
244
+ </>
245
+ )}
246
+ </div>
183
247
  )}
184
248
 
185
249
  {/* Actions based on type */}
@@ -225,7 +289,7 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
225
289
  )}
226
290
  </p>
227
291
  </div>
228
- <div className="flex flex-col gap-1 shrink-0">
292
+ <div className="flex flex-col gap-1 shrink-0" onClick={(e) => e.stopPropagation()}>
229
293
  <Button
230
294
  variant="ghost"
231
295
  size="icon"
@@ -14,6 +14,7 @@ import {
14
14
  import { PermissionResponseActions } from "@/components/notifications/permission-response-actions";
15
15
  import { ContextProposalReview } from "@/components/profiles/context-proposal-review";
16
16
  import { BatchProposalReview } from "@/components/notifications/batch-proposal-review";
17
+ import { LightMarkdown } from "@/components/shared/light-markdown";
17
18
  import { Badge } from "@/components/ui/badge";
18
19
  import {
19
20
  Dialog,
@@ -144,10 +145,12 @@ function PendingApprovalDetail({
144
145
  <p className="mt-2 text-sm text-muted-foreground">
145
146
  {selected.compactSummary}
146
147
  </p>
147
- {selected.body && selected.notificationType !== "context_proposal" && (
148
- <p className="mt-3 text-sm leading-6 text-muted-foreground">
149
- {selected.body}
150
- </p>
148
+ {selected.body &&
149
+ selected.notificationType !== "context_proposal" &&
150
+ selected.notificationType !== "context_proposal_batch" && (
151
+ <div className="mt-3">
152
+ <LightMarkdown content={selected.body} textSize="sm" />
153
+ </div>
151
154
  )}
152
155
  <p className="mt-3 text-xs text-muted-foreground">
153
156
  Requested {formatTimestamp(selected.createdAt)}
@@ -7,6 +7,7 @@ import remarkGfm from "remark-gfm";
7
7
  import { ArrowLeft, ArrowRight, ExternalLink } from "lucide-react";
8
8
  import { Button } from "@/components/ui/button";
9
9
  import { Badge } from "@/components/ui/badge";
10
+ import { PROSE_READER_FULL } from "@/lib/constants/prose-styles";
10
11
  import { PlaybookToc } from "./playbook-toc";
11
12
  import { RelatedDocs } from "./related-docs";
12
13
  import type { ParsedDoc, DocSection, AdoptionEntry } from "@/lib/docs/types";
@@ -34,7 +35,8 @@ export function PlaybookDetailView({
34
35
  }: PlaybookDetailViewProps) {
35
36
  const title =
36
37
  (doc.frontmatter.title as string) || doc.slug.replace(/-/g, " ");
37
- const tags = (doc.frontmatter.tags as string[]) || [];
38
+ const rawTags = doc.frontmatter.tags;
39
+ const tags = Array.isArray(rawTags) ? (rawTags as string[]) : [];
38
40
  const route = doc.frontmatter.route as string | undefined;
39
41
  const category = doc.frontmatter.category as string | undefined;
40
42
  const difficulty = doc.frontmatter.difficulty as string | undefined;
@@ -126,8 +128,8 @@ export function PlaybookDetailView({
126
128
 
127
129
  // Resolve ../screengrabs/ paths to /readme/ (images live in public/readme/)
128
130
  let resolvedSrc = src;
129
- if (src.startsWith("../screengrabs/")) {
130
- resolvedSrc = `/readme/${src.replace("../screengrabs/", "")}`;
131
+ if (src.includes("screengrabs/")) {
132
+ resolvedSrc = `/readme/${src.split("screengrabs/").pop()}`;
131
133
  } else if (src.startsWith("./")) {
132
134
  resolvedSrc = `/docs/${src.replace("./", "")}`;
133
135
  }
@@ -200,7 +202,7 @@ export function PlaybookDetailView({
200
202
 
201
203
  {/* Prose content */}
202
204
  <div className="flex-1 min-w-0">
203
- <div className="prose dark:prose-invert max-w-none prose-headings:scroll-mt-20 prose-img:rounded-xl prose-img:border prose-img:border-border/50">
205
+ <div className={PROSE_READER_FULL}>
204
206
  <ReactMarkdown
205
207
  remarkPlugins={[remarkGfm]}
206
208
  components={{
@@ -107,6 +107,7 @@ export function ProfileBrowser({ initialProfiles }: ProfileBrowserProps) {
107
107
  <ProfileCard
108
108
  key={profile.id}
109
109
  profile={profile}
110
+ isBuiltin={profile.isBuiltin}
110
111
  onClick={() => router.push(`/profiles/${profile.id}`)}
111
112
  />
112
113
  ))}
@@ -4,14 +4,16 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
4
4
  import { Badge } from "@/components/ui/badge";
5
5
  import { listRuntimeCatalog } from "@/lib/agents/runtime/catalog";
6
6
  import { getSupportedRuntimes } from "@/lib/agents/profiles/compatibility";
7
+ import { IconCircle, getProfileIcon, getDomainColors } from "@/lib/constants/card-icons";
7
8
  import type { AgentProfile } from "@/lib/agents/profiles/types";
8
9
 
9
10
  interface ProfileCardProps {
10
11
  profile: AgentProfile;
12
+ isBuiltin?: boolean;
11
13
  onClick: () => void;
12
14
  }
13
15
 
14
- export function ProfileCard({ profile, onClick }: ProfileCardProps) {
16
+ export function ProfileCard({ profile, isBuiltin = true, onClick }: ProfileCardProps) {
15
17
  const runtimeLabelMap = new Map(
16
18
  listRuntimeCatalog().map((runtime) => [
17
19
  runtime.id,
@@ -32,13 +34,19 @@ export function ProfileCard({ profile, onClick }: ProfileCardProps) {
32
34
  }
33
35
  }}
34
36
  >
35
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
36
- <CardTitle className="text-base font-medium">{profile.name}</CardTitle>
37
- <Badge
38
- variant={profile.domain === "work" ? "default" : "secondary"}
39
- >
40
- {profile.domain}
41
- </Badge>
37
+ <CardHeader className="flex flex-row items-center gap-3 space-y-0 pb-2">
38
+ <IconCircle
39
+ icon={getProfileIcon(profile.id)}
40
+ colors={getDomainColors(profile.domain, isBuiltin)}
41
+ />
42
+ <div className="flex min-w-0 flex-1 items-center justify-between">
43
+ <CardTitle className="truncate text-base font-medium">{profile.name}</CardTitle>
44
+ <Badge
45
+ variant={profile.domain === "work" ? "default" : "secondary"}
46
+ >
47
+ {profile.domain}
48
+ </Badge>
49
+ </div>
42
50
  </CardHeader>
43
51
  <CardContent className="space-y-3">
44
52
  <p className="line-clamp-2 text-sm text-muted-foreground">
@@ -40,6 +40,7 @@ import {
40
40
  getSupportedRuntimes,
41
41
  } from "@/lib/agents/profiles/compatibility";
42
42
  import type { AgentProfile } from "@/lib/agents/profiles/types";
43
+ import { IconCircle, getProfileIcon, getDomainColors } from "@/lib/constants/card-icons";
43
44
 
44
45
  interface TestResult {
45
46
  task: string;
@@ -179,7 +180,11 @@ export function ProfileDetailView({ profileId, isBuiltin, initialProfile }: Prof
179
180
  <div className="space-y-6" aria-live="polite">
180
181
  {/* Header */}
181
182
  <div className="flex items-center justify-between">
182
- <div className="flex items-center gap-2">
183
+ <div className="flex items-center gap-3">
184
+ <IconCircle
185
+ icon={getProfileIcon(profile.id)}
186
+ colors={getDomainColors(profile.domain, isBuiltin)}
187
+ />
183
188
  <h1 className="text-2xl font-bold">{profile.name}</h1>
184
189
  <Badge variant={profile.domain === "work" ? "default" : "secondary"}>
185
190
  {profile.domain}
@@ -8,7 +8,7 @@ import {
8
8
  Inbox,
9
9
  Activity,
10
10
  FolderKanban,
11
- GitBranch,
11
+ Workflow,
12
12
  FileText,
13
13
  Bot,
14
14
  Clock,
@@ -40,7 +40,7 @@ const navItems = [
40
40
  { title: "Inbox", href: "/inbox", icon: Inbox, badge: true },
41
41
  { title: "Monitor", href: "/monitor", icon: Activity, badge: false },
42
42
  { title: "Projects", href: "/projects", icon: FolderKanban, badge: false },
43
- { title: "Workflows", href: "/workflows", icon: GitBranch, badge: false },
43
+ { title: "Workflows", href: "/workflows", icon: Workflow, badge: false },
44
44
  { title: "Documents", href: "/documents", icon: FileText, badge: false },
45
45
  { title: "Profiles", href: "/profiles", icon: Bot, badge: false },
46
46
  { title: "Schedules", href: "/schedules", icon: Clock, badge: false },
@@ -49,7 +49,7 @@ describe("kanban board accessibility", () => {
49
49
  />
50
50
  );
51
51
 
52
- const announcement = screen.getByText("Showing 1 task on the kanban board.");
52
+ const announcement = screen.getByText("Showing 1 item on the kanban board.");
53
53
  const board = screen.getByRole("region", { name: "Kanban board" });
54
54
 
55
55
  expect(announcement).toHaveAttribute("aria-live", "polite");
@@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
4
4
  import { Button } from "@/components/ui/button";
5
5
  import { Card, CardContent } from "@/components/ui/card";
6
6
  import { Badge } from "@/components/ui/badge";
7
- import { Sparkles, Check, X, GitBranch } from "lucide-react";
7
+ import { Sparkles, Check, X, GitBranch, Info } from "lucide-react";
8
8
 
9
9
  interface TaskSuggestion {
10
10
  title: string;
@@ -154,9 +154,14 @@ export function AIAssistPanel({
154
154
  );
155
155
  }
156
156
 
157
+ const showWorkflowCTA =
158
+ onCreateWorkflow &&
159
+ result.breakdown.length >= 2 &&
160
+ result.recommendedPattern !== "single";
161
+
157
162
  return (
158
- <div className="pt-2 space-y-3">
159
- {/* Header row — full width */}
163
+ <div className="p-4 space-y-4">
164
+ {/* Header row */}
160
165
  <div className="flex items-center gap-2 flex-wrap">
161
166
  <Sparkles className="h-4 w-4 text-primary" />
162
167
  <span className="text-sm font-medium">AI Suggestions</span>
@@ -171,86 +176,111 @@ export function AIAssistPanel({
171
176
  )}
172
177
  </div>
173
178
 
174
- {/* Two-column grid for cards */}
175
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
176
- {/* Improved description */}
177
- <Card>
178
- <CardContent className="p-3">
179
- <div className="flex items-center justify-between mb-1">
180
- <span className="text-xs font-medium text-muted-foreground">
181
- Improved Description
182
- </span>
183
- <Button
184
- type="button"
185
- variant="ghost"
186
- size="sm"
187
- className="h-6 text-xs"
188
- onClick={() => {
189
- onApplyDescription(result.improvedDescription);
190
- setDescriptionApplied(true);
191
- }}
192
- disabled={descriptionApplied}
193
- >
194
- {descriptionApplied ? (
195
- <><Check className="h-3 w-3 mr-1" /> Applied</>
196
- ) : (
197
- "Apply"
198
- )}
199
- </Button>
179
+ {/* Improved description full width */}
180
+ <Card>
181
+ <CardContent className="p-3">
182
+ <div className="flex items-center justify-between mb-1">
183
+ <span className="text-xs font-medium text-muted-foreground">
184
+ Improved Description
185
+ </span>
186
+ <Button
187
+ type="button"
188
+ variant="ghost"
189
+ size="sm"
190
+ className="h-6 text-xs"
191
+ onClick={() => {
192
+ onApplyDescription(result.improvedDescription);
193
+ setDescriptionApplied(true);
194
+ }}
195
+ disabled={descriptionApplied}
196
+ >
197
+ {descriptionApplied ? (
198
+ <><Check className="h-3 w-3 mr-1" /> Applied</>
199
+ ) : (
200
+ "Apply"
201
+ )}
202
+ </Button>
203
+ </div>
204
+ <p className="text-sm">{result.improvedDescription}</p>
205
+ </CardContent>
206
+ </Card>
207
+
208
+ {/* Reasoning + Workflow CTA combined callout */}
209
+ {(result.reasoning || showWorkflowCTA) && (
210
+ <div className="rounded-md border bg-muted/50 p-3 space-y-2.5">
211
+ {result.reasoning && (
212
+ <div className="flex gap-2">
213
+ <Info className="h-3.5 w-3.5 mt-0.5 shrink-0 text-muted-foreground" />
214
+ <p className="text-xs text-muted-foreground">
215
+ {result.reasoning}
216
+ {result.complexity === "complex" && result.recommendedPattern !== "single"
217
+ ? " Complex tasks benefit from workflow execution — each step builds on the previous result."
218
+ : ""}
219
+ </p>
220
+ </div>
221
+ )}
222
+ {showWorkflowCTA && (
223
+ <Button
224
+ type="button"
225
+ variant="outline"
226
+ size="sm"
227
+ className="w-full justify-start h-auto py-2 border-primary/50 bg-background"
228
+ onClick={() => onCreateWorkflow(result)}
229
+ >
230
+ <div className="text-left flex-1">
231
+ <div className="flex items-center gap-1.5">
232
+ <GitBranch className="h-3 w-3" />
233
+ <span className="text-xs font-medium">Create as Workflow</span>
234
+ <Badge variant="default" className="text-[10px] h-4 px-1">
235
+ Recommended
236
+ </Badge>
237
+ </div>
238
+ <div className="text-[11px] text-muted-foreground font-normal mt-0.5">
239
+ Runs as {patternLabels[result.recommendedPattern]?.toLowerCase() ?? result.recommendedPattern} — each step receives prior output
240
+ </div>
241
+ </div>
242
+ </Button>
243
+ )}
244
+ </div>
245
+ )}
246
+
247
+ {/* Breakdown as flat numbered list */}
248
+ {result.breakdown.length > 0 && (
249
+ <>
250
+ {/* "or" divider — only show when workflow CTA is present */}
251
+ {showWorkflowCTA && (
252
+ <div className="flex items-center gap-3">
253
+ <div className="flex-1 border-t" />
254
+ <span className="text-xs text-muted-foreground">or create as individual tasks</span>
255
+ <div className="flex-1 border-t" />
200
256
  </div>
201
- <p className="text-sm">{result.improvedDescription}</p>
202
- </CardContent>
203
- </Card>
257
+ )}
204
258
 
205
- {/* Task breakdown */}
206
- {result.breakdown.length > 0 && (
207
- <Card>
208
- <CardContent className="p-3">
209
- <div className="flex items-center justify-between mb-2">
210
- <span className="text-xs font-medium text-muted-foreground">
211
- Suggested Breakdown ({result.breakdown.length} sub-tasks)
259
+ <div className="space-y-2.5">
260
+ {result.breakdown.map((sub, i) => (
261
+ <div key={i} className="flex gap-2.5">
262
+ <span className="text-xs font-medium text-muted-foreground mt-0.5 shrink-0 w-4 text-right">
263
+ {i + 1}.
212
264
  </span>
213
- <div className="flex gap-1">
214
- {onCreateWorkflow &&
215
- result.breakdown.length >= 2 &&
216
- result.recommendedPattern !== "single" && (
217
- <Button
218
- type="button"
219
- variant="ghost"
220
- size="sm"
221
- className="h-6 text-xs"
222
- onClick={() => onCreateWorkflow(result)}
223
- >
224
- <GitBranch className="h-3 w-3 mr-1" />
225
- Workflow
226
- </Button>
227
- )}
228
- <Button
229
- type="button"
230
- variant="ghost"
231
- size="sm"
232
- className="h-6 text-xs"
233
- onClick={() => onCreateSubtasks(result.breakdown)}
234
- >
235
- Create All
236
- </Button>
265
+ <div className="min-w-0">
266
+ <span className="text-sm font-medium">{sub.title}</span>
267
+ <p className="text-xs text-muted-foreground">{sub.description}</p>
237
268
  </div>
238
269
  </div>
239
- <div className="max-h-60 overflow-y-auto space-y-1.5">
240
- {result.breakdown.map((sub, i) => (
241
- <div key={i} className="text-sm">
242
- <span className="font-medium">{sub.title}</span>
243
- <p className="text-xs text-muted-foreground">{sub.description}</p>
244
- </div>
245
- ))}
246
- </div>
247
- </CardContent>
248
- </Card>
249
- )}
250
- </div>
270
+ ))}
271
+ </div>
251
272
 
252
- {/* Reasoning + Dismiss — full width */}
253
- <p className="text-xs text-muted-foreground">{result.reasoning}</p>
273
+ <Button
274
+ type="button"
275
+ variant="outline"
276
+ size="sm"
277
+ className="w-full"
278
+ onClick={() => onCreateSubtasks(result.breakdown)}
279
+ >
280
+ Create {result.breakdown.length} Independent Tasks
281
+ </Button>
282
+ </>
283
+ )}
254
284
 
255
285
  <Button
256
286
  type="button"
@@ -260,7 +290,7 @@ export function AIAssistPanel({
260
290
  setResult(null);
261
291
  onResultChange?.(false);
262
292
  }}
263
- className="w-full"
293
+ className="w-full text-muted-foreground"
264
294
  >
265
295
  <X className="h-3 w-3 mr-1" /> Dismiss
266
296
  </Button>
@@ -7,6 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { Copy, Download, Maximize2, Minimize2 } from "lucide-react";
9
9
  import { toast } from "sonner";
10
+ import { PROSE_READER } from "@/lib/constants/prose-styles";
10
11
 
11
12
  interface ContentPreviewProps {
12
13
  content: string;
@@ -75,7 +76,7 @@ export function ContentPreview({ content, contentType }: ContentPreviewProps) {
75
76
  {content}
76
77
  </pre>
77
78
  ) : contentType === "markdown" ? (
78
- <div className={`prose prose-sm dark:prose-invert max-w-none overflow-auto ${heightClass}`}>
79
+ <div className={`${PROSE_READER} overflow-auto ${heightClass}`}>
79
80
  <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
80
81
  </div>
81
82
  ) : (