stagent 0.9.5 → 0.10.0
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.
- package/README.md +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -9,6 +9,47 @@ import { Inbox, Plus, CheckSquare, Square, ArrowRight, Play, Trash2 } from "luci
|
|
|
9
9
|
import { TaskCard, type TaskItem } from "./task-card";
|
|
10
10
|
import { WorkflowKanbanCard, type WorkflowKanbanItem } from "@/components/workflows/workflow-kanban-card";
|
|
11
11
|
import type { TaskStatus } from "@/lib/constants/task-status";
|
|
12
|
+
import type { SortOrder } from "./kanban-board";
|
|
13
|
+
|
|
14
|
+
type KanbanItem =
|
|
15
|
+
| { kind: "task"; data: TaskItem }
|
|
16
|
+
| { kind: "workflow"; data: WorkflowKanbanItem };
|
|
17
|
+
|
|
18
|
+
function itemName(item: KanbanItem): string {
|
|
19
|
+
return item.kind === "task" ? item.data.title : item.data.name;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mergedItems(
|
|
23
|
+
tasks: TaskItem[],
|
|
24
|
+
workflows: WorkflowKanbanItem[],
|
|
25
|
+
sortOrder: SortOrder
|
|
26
|
+
): KanbanItem[] {
|
|
27
|
+
const items: KanbanItem[] = [
|
|
28
|
+
...workflows.map((w) => ({ kind: "workflow" as const, data: w })),
|
|
29
|
+
...tasks.map((t) => ({ kind: "task" as const, data: t })),
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
switch (sortOrder) {
|
|
33
|
+
case "created-desc":
|
|
34
|
+
return items.sort(
|
|
35
|
+
(a, b) => new Date(b.data.createdAt).getTime() - new Date(a.data.createdAt).getTime()
|
|
36
|
+
);
|
|
37
|
+
case "created-asc":
|
|
38
|
+
return items.sort(
|
|
39
|
+
(a, b) => new Date(a.data.createdAt).getTime() - new Date(b.data.createdAt).getTime()
|
|
40
|
+
);
|
|
41
|
+
case "title-asc":
|
|
42
|
+
return items.sort((a, b) => itemName(a).localeCompare(itemName(b)));
|
|
43
|
+
case "priority":
|
|
44
|
+
// Workflows lack priority — keep them at top, then sort tasks by priority
|
|
45
|
+
return items.sort((a, b) => {
|
|
46
|
+
if (a.kind === "workflow" && b.kind === "workflow") return 0;
|
|
47
|
+
if (a.kind === "workflow") return -1;
|
|
48
|
+
if (b.kind === "workflow") return 1;
|
|
49
|
+
return a.data.priority - b.data.priority;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
12
53
|
|
|
13
54
|
const columnLabels: Record<string, string> = {
|
|
14
55
|
planned: "Planned",
|
|
@@ -22,6 +63,7 @@ export function KanbanColumn({
|
|
|
22
63
|
status,
|
|
23
64
|
tasks,
|
|
24
65
|
workflows = [],
|
|
66
|
+
sortOrder = "priority",
|
|
25
67
|
exitingIds,
|
|
26
68
|
onTaskClick,
|
|
27
69
|
onAddTask,
|
|
@@ -34,6 +76,7 @@ export function KanbanColumn({
|
|
|
34
76
|
status: TaskStatus;
|
|
35
77
|
tasks: TaskItem[];
|
|
36
78
|
workflows?: WorkflowKanbanItem[];
|
|
79
|
+
sortOrder?: SortOrder;
|
|
37
80
|
exitingIds?: Set<string>;
|
|
38
81
|
onTaskClick: (task: TaskItem) => void;
|
|
39
82
|
onAddTask?: () => void;
|
|
@@ -195,30 +238,26 @@ export function KanbanColumn({
|
|
|
195
238
|
</div>
|
|
196
239
|
) : (
|
|
197
240
|
<>
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
{/* Task cards (draggable) */}
|
|
203
|
-
{tasks.map((task) => {
|
|
204
|
-
const isExiting = exitingIds?.has(task.id);
|
|
205
|
-
return (
|
|
241
|
+
{mergedItems(tasks, workflows, sortOrder).map((item) =>
|
|
242
|
+
item.kind === "workflow" ? (
|
|
243
|
+
<WorkflowKanbanCard key={item.data.id} workflow={item.data} />
|
|
244
|
+
) : (
|
|
206
245
|
<div
|
|
207
|
-
key={
|
|
208
|
-
className={
|
|
246
|
+
key={item.data.id}
|
|
247
|
+
className={exitingIds?.has(item.data.id) ? "animate-card-exit pointer-events-none" : ""}
|
|
209
248
|
>
|
|
210
249
|
<TaskCard
|
|
211
|
-
task={
|
|
250
|
+
task={item.data}
|
|
212
251
|
onClick={onTaskClick}
|
|
213
252
|
selectionMode={selectMode}
|
|
214
|
-
selected={selectedIds.has(
|
|
253
|
+
selected={selectedIds.has(item.data.id)}
|
|
215
254
|
onSelect={handleSelect}
|
|
216
255
|
onDelete={onDeleteTask}
|
|
217
256
|
onEdit={onEditTask}
|
|
218
257
|
/>
|
|
219
258
|
</div>
|
|
220
|
-
)
|
|
221
|
-
|
|
259
|
+
)
|
|
260
|
+
)}
|
|
222
261
|
</>
|
|
223
262
|
)}
|
|
224
263
|
</div>
|
|
@@ -10,8 +10,11 @@ import {
|
|
|
10
10
|
Timer,
|
|
11
11
|
Cpu,
|
|
12
12
|
Paperclip,
|
|
13
|
+
CalendarClock,
|
|
14
|
+
CalendarCheck,
|
|
13
15
|
} from "lucide-react";
|
|
14
16
|
import { taskStatusVariant } from "@/lib/constants/status-colors";
|
|
17
|
+
import { formatCompactDateTime } from "@/lib/utils/format-timestamp";
|
|
15
18
|
import { TaskBentoCell } from "./task-bento-cell";
|
|
16
19
|
import type { TaskItem } from "./task-card";
|
|
17
20
|
import type { DocumentRow } from "@/lib/db/schema";
|
|
@@ -127,6 +130,22 @@ export function TaskBentoGrid({ task, docs }: TaskBentoGridProps) {
|
|
|
127
130
|
/>
|
|
128
131
|
)}
|
|
129
132
|
|
|
133
|
+
{usage?.startedAt && (
|
|
134
|
+
<TaskBentoCell
|
|
135
|
+
icon={CalendarClock}
|
|
136
|
+
label="Started At"
|
|
137
|
+
value={formatCompactDateTime(usage.startedAt)}
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
{usage?.finishedAt && (
|
|
142
|
+
<TaskBentoCell
|
|
143
|
+
icon={CalendarCheck}
|
|
144
|
+
label="Finished At"
|
|
145
|
+
value={formatCompactDateTime(usage.finishedAt)}
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
148
|
+
|
|
130
149
|
{usage?.startedAt && usage?.finishedAt && (
|
|
131
150
|
<TaskBentoCell
|
|
132
151
|
icon={Timer}
|
|
@@ -5,7 +5,8 @@ import { useSortable } from "@dnd-kit/sortable";
|
|
|
5
5
|
import { CSS } from "@dnd-kit/utilities";
|
|
6
6
|
import { Card } from "@/components/ui/card";
|
|
7
7
|
import { Badge } from "@/components/ui/badge";
|
|
8
|
-
import { AlertCircle, Bot, ArrowUp, ArrowDown, Minus, Trash2, Check, X, Loader2, Square, CheckSquare, Pencil, FileText } from "lucide-react";
|
|
8
|
+
import { AlertCircle, Bot, ArrowUp, ArrowDown, Minus, Trash2, Check, X, Loader2, Square, CheckSquare, Pencil, FileText, Clock } from "lucide-react";
|
|
9
|
+
import { formatCompactDateTime } from "@/lib/utils/format-timestamp";
|
|
9
10
|
import type { TaskStatus } from "@/lib/constants/task-status";
|
|
10
11
|
|
|
11
12
|
export interface TaskItem {
|
|
@@ -122,6 +123,14 @@ export function TaskCard({
|
|
|
122
123
|
const showDeleteButton = onDelete && !isRunning;
|
|
123
124
|
const isEditable = (task.status === "planned" || task.status === "queued") && !!onEdit;
|
|
124
125
|
|
|
126
|
+
// Status-contextual date: planned→createdAt, running→startedAt, completed/failed→finishedAt
|
|
127
|
+
const relevantDate =
|
|
128
|
+
task.status === "planned" || task.status === "queued"
|
|
129
|
+
? task.createdAt
|
|
130
|
+
: task.status === "running"
|
|
131
|
+
? task.usage?.startedAt ?? task.updatedAt
|
|
132
|
+
: task.usage?.finishedAt ?? task.updatedAt;
|
|
133
|
+
|
|
125
134
|
return (
|
|
126
135
|
<Card
|
|
127
136
|
ref={setNodeRef}
|
|
@@ -244,7 +253,13 @@ export function TaskCard({
|
|
|
244
253
|
<Pencil className="h-3.5 w-3.5" />
|
|
245
254
|
</button>
|
|
246
255
|
)}
|
|
247
|
-
<
|
|
256
|
+
<span
|
|
257
|
+
className="flex items-center gap-1 text-xs text-muted-foreground tabular-nums truncate min-w-0 flex-1"
|
|
258
|
+
title={new Date(relevantDate).toLocaleString()}
|
|
259
|
+
>
|
|
260
|
+
<Clock className="h-3 w-3 shrink-0" />
|
|
261
|
+
{formatCompactDateTime(relevantDate)}
|
|
262
|
+
</span>
|
|
248
263
|
{showDeleteButton && (
|
|
249
264
|
<button
|
|
250
265
|
type="button"
|
|
@@ -257,7 +272,15 @@ export function TaskCard({
|
|
|
257
272
|
)}
|
|
258
273
|
</>
|
|
259
274
|
)
|
|
260
|
-
) :
|
|
275
|
+
) : (
|
|
276
|
+
<span
|
|
277
|
+
className="flex items-center gap-1 text-xs text-muted-foreground tabular-nums truncate min-w-0 flex-1"
|
|
278
|
+
title={new Date(relevantDate).toLocaleString()}
|
|
279
|
+
>
|
|
280
|
+
<Clock className="h-3 w-3 shrink-0" />
|
|
281
|
+
{formatCompactDateTime(relevantDate)}
|
|
282
|
+
</span>
|
|
283
|
+
)}
|
|
261
284
|
</div>
|
|
262
285
|
</Card>
|
|
263
286
|
);
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
ArrowUp,
|
|
20
20
|
ArrowDown,
|
|
21
21
|
Minus,
|
|
22
|
+
CalendarClock,
|
|
23
|
+
CalendarCheck,
|
|
22
24
|
} from "lucide-react";
|
|
23
25
|
import { taskStatusVariant } from "@/lib/constants/status-colors";
|
|
24
26
|
import { MAX_RESUME_COUNT } from "@/lib/constants/task-status";
|
|
@@ -172,6 +174,28 @@ export function TaskChipBar({
|
|
|
172
174
|
>
|
|
173
175
|
Updated {formatTimestamp(task.updatedAt)}
|
|
174
176
|
</Badge>
|
|
177
|
+
|
|
178
|
+
{task.usage?.startedAt && (
|
|
179
|
+
<Badge
|
|
180
|
+
variant="outline"
|
|
181
|
+
className="text-xs font-normal gap-1"
|
|
182
|
+
title={new Date(task.usage.startedAt).toLocaleString()}
|
|
183
|
+
>
|
|
184
|
+
<CalendarClock className="h-3 w-3" />
|
|
185
|
+
Started {formatTimestamp(task.usage.startedAt)}
|
|
186
|
+
</Badge>
|
|
187
|
+
)}
|
|
188
|
+
|
|
189
|
+
{task.usage?.finishedAt && (
|
|
190
|
+
<Badge
|
|
191
|
+
variant="outline"
|
|
192
|
+
className="text-xs font-normal gap-1"
|
|
193
|
+
title={new Date(task.usage.finishedAt).toLocaleString()}
|
|
194
|
+
>
|
|
195
|
+
<CalendarCheck className="h-3 w-3" />
|
|
196
|
+
Finished {formatTimestamp(task.usage.finishedAt)}
|
|
197
|
+
</Badge>
|
|
198
|
+
)}
|
|
175
199
|
</div>
|
|
176
200
|
|
|
177
201
|
{/* Row 3: Relationship Links (only if any FK exists) */}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Separator } from "@/components/ui/separator";
|
|
2
2
|
import { LightMarkdown } from "@/components/shared/light-markdown";
|
|
3
|
-
import { ExpandableResult } from "@/components/workflows/
|
|
3
|
+
import { ExpandableResult } from "@/components/workflows/shared/step-result";
|
|
4
4
|
|
|
5
5
|
interface TaskResultRendererProps {
|
|
6
6
|
description: string | null;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Play } from "lucide-react";
|
|
6
|
+
import { toast } from "sonner";
|
|
7
|
+
import { formatDuration } from "@/lib/workflows/delay";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Body content for a delay step row in the workflow status view. Renders
|
|
11
|
+
* three visual states keyed off the step's status:
|
|
12
|
+
*
|
|
13
|
+
* - Pending (workflow hasn't reached this step yet): "Will wait 3d"
|
|
14
|
+
* - Active delay (workflow paused, waiting): absolute resume time +
|
|
15
|
+
* remaining duration + Resume Now button
|
|
16
|
+
* - Completed (workflow has already passed this step): "Delayed 3d — completed"
|
|
17
|
+
*
|
|
18
|
+
* Countdown is static on mount/focus — no per-second ticking, because live
|
|
19
|
+
* aria-live updates would flood assistive tech users. Users needing a refresh
|
|
20
|
+
* can reload or refocus the page.
|
|
21
|
+
*
|
|
22
|
+
* Extracted from workflow-status-view.tsx during the TDR-031 router refactor
|
|
23
|
+
* so the non-loop subview can import it without a circular dependency on the
|
|
24
|
+
* thin router file.
|
|
25
|
+
*/
|
|
26
|
+
export function DelayStepBody({
|
|
27
|
+
workflowId,
|
|
28
|
+
delayDuration,
|
|
29
|
+
stepStatus,
|
|
30
|
+
resumeAt,
|
|
31
|
+
}: {
|
|
32
|
+
workflowId: string;
|
|
33
|
+
delayDuration: string;
|
|
34
|
+
stepStatus: string;
|
|
35
|
+
resumeAt: number | null;
|
|
36
|
+
}) {
|
|
37
|
+
const [resuming, setResuming] = useState(false);
|
|
38
|
+
|
|
39
|
+
const handleResumeNow = useCallback(async () => {
|
|
40
|
+
setResuming(true);
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(`/api/workflows/${workflowId}/resume`, { method: "POST" });
|
|
43
|
+
if (res.status === 202) {
|
|
44
|
+
toast.success("Resume dispatched");
|
|
45
|
+
} else if (res.status === 409) {
|
|
46
|
+
toast.info("Workflow already resumed by scheduler");
|
|
47
|
+
} else {
|
|
48
|
+
const body = await res.json().catch(() => ({}));
|
|
49
|
+
toast.error(body.error ?? "Failed to resume workflow");
|
|
50
|
+
}
|
|
51
|
+
} catch (err) {
|
|
52
|
+
toast.error(err instanceof Error ? err.message : "Failed to resume workflow");
|
|
53
|
+
} finally {
|
|
54
|
+
setResuming(false);
|
|
55
|
+
}
|
|
56
|
+
}, [workflowId]);
|
|
57
|
+
|
|
58
|
+
if (stepStatus === "completed") {
|
|
59
|
+
return (
|
|
60
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
61
|
+
Delayed {delayDuration} — completed
|
|
62
|
+
</p>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (stepStatus === "delayed" && resumeAt) {
|
|
67
|
+
const resumeDate = new Date(resumeAt);
|
|
68
|
+
const remainingMs = Math.max(0, resumeAt - Date.now());
|
|
69
|
+
const remainingLabel =
|
|
70
|
+
remainingMs < 60_000
|
|
71
|
+
? "less than a minute"
|
|
72
|
+
: formatDuration(Math.round(remainingMs / 60_000) * 60_000);
|
|
73
|
+
return (
|
|
74
|
+
<div className="mt-1 space-y-2">
|
|
75
|
+
<p className="text-xs text-status-warning">
|
|
76
|
+
Resumes{" "}
|
|
77
|
+
<time dateTime={resumeDate.toISOString()}>
|
|
78
|
+
{resumeDate.toLocaleString(undefined, {
|
|
79
|
+
weekday: "short",
|
|
80
|
+
month: "short",
|
|
81
|
+
day: "numeric",
|
|
82
|
+
hour: "numeric",
|
|
83
|
+
minute: "2-digit",
|
|
84
|
+
timeZoneName: "short",
|
|
85
|
+
})}
|
|
86
|
+
</time>{" "}
|
|
87
|
+
<span className="text-muted-foreground">({remainingLabel} remaining)</span>
|
|
88
|
+
</p>
|
|
89
|
+
<Button
|
|
90
|
+
size="sm"
|
|
91
|
+
variant="outline"
|
|
92
|
+
onClick={handleResumeNow}
|
|
93
|
+
disabled={resuming}
|
|
94
|
+
aria-label="Resume workflow now"
|
|
95
|
+
>
|
|
96
|
+
<Play className="h-3 w-3 mr-1" />
|
|
97
|
+
{resuming ? "Resuming..." : "Resume Now"}
|
|
98
|
+
</Button>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Pending / upcoming delay — workflow hasn't reached this step yet
|
|
104
|
+
return (
|
|
105
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
106
|
+
Will wait {delayDuration}
|
|
107
|
+
</p>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import type { WorkflowStatusResponse } from "@/lib/workflows/types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Polling hook for `GET /api/workflows/[id]/status`.
|
|
8
|
+
*
|
|
9
|
+
* Owns the fetch, the 3-second interval, cancellation on unmount, and
|
|
10
|
+
* re-subscription when `workflowId` changes. Exposes `setData` so callers can
|
|
11
|
+
* apply optimistic updates (e.g. flipping a step to "running" immediately when
|
|
12
|
+
* Execute is clicked) without racing the next poll tick.
|
|
13
|
+
*
|
|
14
|
+
* Returns `WorkflowStatusResponse | null` — narrowing on `data.pattern` is the
|
|
15
|
+
* caller's job per TDR-031. This hook intentionally does not narrow for
|
|
16
|
+
* consumers; each pattern-specific subview handles its own arm.
|
|
17
|
+
*/
|
|
18
|
+
export function useWorkflowStatus(workflowId: string): {
|
|
19
|
+
data: WorkflowStatusResponse | null;
|
|
20
|
+
setData: (updater: (current: WorkflowStatusResponse | null) => WorkflowStatusResponse | null) => void;
|
|
21
|
+
refetch: () => Promise<void>;
|
|
22
|
+
} {
|
|
23
|
+
const [data, setDataInternal] = useState<WorkflowStatusResponse | null>(null);
|
|
24
|
+
|
|
25
|
+
const refetch = useCallback(async () => {
|
|
26
|
+
const res = await fetch(`/api/workflows/${workflowId}/status`);
|
|
27
|
+
if (res.ok) {
|
|
28
|
+
const json = (await res.json()) as WorkflowStatusResponse;
|
|
29
|
+
setDataInternal(json);
|
|
30
|
+
}
|
|
31
|
+
}, [workflowId]);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
// Reset data when the workflowId changes so the old workflow's state
|
|
35
|
+
// never briefly shows while the new one is fetching.
|
|
36
|
+
setDataInternal(null);
|
|
37
|
+
refetch();
|
|
38
|
+
const interval = setInterval(refetch, 3000);
|
|
39
|
+
return () => clearInterval(interval);
|
|
40
|
+
}, [refetch]);
|
|
41
|
+
|
|
42
|
+
const setData = useCallback(
|
|
43
|
+
(updater: (current: WorkflowStatusResponse | null) => WorkflowStatusResponse | null) => {
|
|
44
|
+
setDataInternal((prev) => updater(prev));
|
|
45
|
+
},
|
|
46
|
+
[]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return { data, setData, refetch };
|
|
50
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Badge } from "@/components/ui/badge";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
|
-
import { ExpandableResult } from "./
|
|
6
|
+
import { ExpandableResult } from "./shared/step-result";
|
|
7
7
|
import {
|
|
8
8
|
CheckCircle,
|
|
9
9
|
Circle,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import ReactMarkdown from "react-markdown";
|
|
6
|
+
import remarkGfm from "remark-gfm";
|
|
7
|
+
import { FileText } from "lucide-react";
|
|
8
|
+
import { PROSE_NOTIFICATION } from "@/lib/constants/prose-styles";
|
|
9
|
+
import type { WorkflowStatusDocument } from "@/lib/workflows/types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Expandable step result with gradient fade progressive disclosure.
|
|
13
|
+
* Extracted from the legacy workflow-status-view god component so it can be
|
|
14
|
+
* reused by pattern-specific subviews and LoopStatusView without a circular
|
|
15
|
+
* import.
|
|
16
|
+
*/
|
|
17
|
+
export function ExpandableResult({ result }: { result: string }) {
|
|
18
|
+
const [expanded, setExpanded] = useState(false);
|
|
19
|
+
|
|
20
|
+
if (!result) return null;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="mt-2">
|
|
24
|
+
<div
|
|
25
|
+
className={`${PROSE_NOTIFICATION} ${
|
|
26
|
+
expanded
|
|
27
|
+
? "max-h-96 overflow-auto"
|
|
28
|
+
: result.length > 200
|
|
29
|
+
? "max-h-20 overflow-hidden mask-fade-bottom"
|
|
30
|
+
: ""
|
|
31
|
+
}`}
|
|
32
|
+
>
|
|
33
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{result}</ReactMarkdown>
|
|
34
|
+
</div>
|
|
35
|
+
{result.length > 200 && (
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
className="mt-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
39
|
+
onClick={() => setExpanded(!expanded)}
|
|
40
|
+
>
|
|
41
|
+
{expanded ? "Show less" : "Show more"}
|
|
42
|
+
</button>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Document list for a single step or parent task — renders a labeled cluster
|
|
50
|
+
* of links to document detail pages.
|
|
51
|
+
*/
|
|
52
|
+
export function DocumentList({
|
|
53
|
+
docs,
|
|
54
|
+
label,
|
|
55
|
+
}: {
|
|
56
|
+
docs: WorkflowStatusDocument[];
|
|
57
|
+
label: string;
|
|
58
|
+
}) {
|
|
59
|
+
if (docs.length === 0) return null;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="mt-3">
|
|
63
|
+
<p className="text-xs font-medium text-muted-foreground mb-1.5">{label}</p>
|
|
64
|
+
<div className="space-y-1">
|
|
65
|
+
{docs.map((doc) => (
|
|
66
|
+
<Link
|
|
67
|
+
key={doc.id}
|
|
68
|
+
href={`/documents/${doc.id}`}
|
|
69
|
+
className="flex items-center gap-2 text-xs text-brand-blue hover:underline"
|
|
70
|
+
>
|
|
71
|
+
<FileText className="h-3 w-3 shrink-0" />
|
|
72
|
+
{doc.originalName}
|
|
73
|
+
</Link>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { CardHeader, CardTitle } from "@/components/ui/card";
|
|
7
|
+
import {
|
|
8
|
+
Play,
|
|
9
|
+
Pencil,
|
|
10
|
+
Copy,
|
|
11
|
+
RotateCcw,
|
|
12
|
+
Trash2,
|
|
13
|
+
FolderKanban,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
|
|
16
|
+
import { IconCircle, getWorkflowIconFromName } from "@/lib/constants/card-icons";
|
|
17
|
+
import type { WorkflowStatusResponse } from "@/lib/workflows/types";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pattern-agnostic header card for the workflow detail page. Renders the
|
|
21
|
+
* workflow name, pattern label, project/run badges, status badge, and the
|
|
22
|
+
* action buttons (Execute, Edit, Clone, Re-run, Delete). Each subview passes
|
|
23
|
+
* callbacks and the narrowed-arm `data` object.
|
|
24
|
+
*
|
|
25
|
+
* This component is deliberately read-only with respect to polling state —
|
|
26
|
+
* subviews own their own `executing` state so the Execute button can show
|
|
27
|
+
* the "Starting..." label without a round-trip through the router.
|
|
28
|
+
*/
|
|
29
|
+
export function WorkflowHeader({
|
|
30
|
+
data,
|
|
31
|
+
executing,
|
|
32
|
+
canExecute,
|
|
33
|
+
onExecute,
|
|
34
|
+
onRerun,
|
|
35
|
+
onDelete,
|
|
36
|
+
}: {
|
|
37
|
+
data: WorkflowStatusResponse;
|
|
38
|
+
executing: boolean;
|
|
39
|
+
/** Subviews decide when Execute makes sense (e.g. loop workflows hide it in favour of the loop's own start/pause controls). */
|
|
40
|
+
canExecute: boolean;
|
|
41
|
+
onExecute: () => void;
|
|
42
|
+
onRerun: () => void;
|
|
43
|
+
onDelete: () => void;
|
|
44
|
+
}) {
|
|
45
|
+
const router = useRouter();
|
|
46
|
+
const hasDefinition = !!data.definition;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<CardHeader>
|
|
50
|
+
<div className="flex items-center justify-between">
|
|
51
|
+
<div className="flex items-center gap-3">
|
|
52
|
+
<IconCircle
|
|
53
|
+
icon={getWorkflowIconFromName(data.name, data.pattern).icon}
|
|
54
|
+
colors={getWorkflowIconFromName(data.name, data.pattern).colors}
|
|
55
|
+
/>
|
|
56
|
+
<div>
|
|
57
|
+
<CardTitle>{data.name}</CardTitle>
|
|
58
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
59
|
+
{patternLabels[data.pattern] ?? data.pattern}
|
|
60
|
+
</p>
|
|
61
|
+
<div className="flex items-center gap-2 mt-1">
|
|
62
|
+
{data.projectId && (
|
|
63
|
+
<Badge
|
|
64
|
+
variant="outline"
|
|
65
|
+
className="text-xs cursor-pointer hover:bg-accent gap-1"
|
|
66
|
+
onClick={() => router.push(`/projects/${data.projectId}`)}
|
|
67
|
+
>
|
|
68
|
+
<FolderKanban className="h-3 w-3" />
|
|
69
|
+
Project
|
|
70
|
+
</Badge>
|
|
71
|
+
)}
|
|
72
|
+
{data.runNumber != null && data.runNumber > 0 && (
|
|
73
|
+
<Badge variant="outline" className="text-xs font-normal">
|
|
74
|
+
Run #{data.runNumber}
|
|
75
|
+
</Badge>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div className="flex items-center gap-2">
|
|
81
|
+
<Badge variant={workflowStatusVariant[data.status] ?? "secondary"}>
|
|
82
|
+
{data.status}
|
|
83
|
+
</Badge>
|
|
84
|
+
|
|
85
|
+
{canExecute && (data.status === "draft" || data.status === "paused") && (
|
|
86
|
+
<Button size="sm" onClick={onExecute} disabled={executing}>
|
|
87
|
+
<Play className="h-3 w-3 mr-1" />
|
|
88
|
+
{executing ? "Starting..." : "Execute"}
|
|
89
|
+
</Button>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{["draft", "completed", "failed"].includes(data.status) && hasDefinition && (
|
|
93
|
+
<Button
|
|
94
|
+
variant="outline"
|
|
95
|
+
size="sm"
|
|
96
|
+
onClick={() => router.push(`/workflows/${data.id}/edit`)}
|
|
97
|
+
>
|
|
98
|
+
<Pencil className="h-3.5 w-3.5 mr-1.5" />
|
|
99
|
+
Edit
|
|
100
|
+
</Button>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{hasDefinition && (
|
|
104
|
+
<Button
|
|
105
|
+
variant="outline"
|
|
106
|
+
size="sm"
|
|
107
|
+
onClick={() => router.push(`/workflows/${data.id}/edit?clone=true`)}
|
|
108
|
+
>
|
|
109
|
+
<Copy className="h-3.5 w-3.5 mr-1.5" />
|
|
110
|
+
Clone
|
|
111
|
+
</Button>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{(data.status === "completed" || data.status === "failed") && (
|
|
115
|
+
<Button
|
|
116
|
+
variant="outline"
|
|
117
|
+
size="sm"
|
|
118
|
+
onClick={onRerun}
|
|
119
|
+
disabled={executing}
|
|
120
|
+
>
|
|
121
|
+
<RotateCcw className="h-3.5 w-3.5 mr-1.5" />
|
|
122
|
+
Re-run
|
|
123
|
+
</Button>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{data.status !== "active" && (
|
|
127
|
+
<Button
|
|
128
|
+
variant="outline"
|
|
129
|
+
size="sm"
|
|
130
|
+
className="text-destructive hover:text-destructive"
|
|
131
|
+
onClick={onDelete}
|
|
132
|
+
>
|
|
133
|
+
<Trash2 className="h-3.5 w-3.5 mr-1.5" />
|
|
134
|
+
Delete
|
|
135
|
+
</Button>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</CardHeader>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
|
2
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Loading state shown by the workflow detail router while the first poll
|
|
6
|
+
* tick is in flight. Extracted from the router so the router itself stays
|
|
7
|
+
* within its ≤80-line budget per TDR-031.
|
|
8
|
+
*/
|
|
9
|
+
export function WorkflowLoadingSkeleton() {
|
|
10
|
+
return (
|
|
11
|
+
<Card>
|
|
12
|
+
<CardHeader>
|
|
13
|
+
<div className="flex items-center justify-between">
|
|
14
|
+
<div>
|
|
15
|
+
<Skeleton className="h-6 w-48 mb-2" />
|
|
16
|
+
<Skeleton className="h-4 w-24" />
|
|
17
|
+
</div>
|
|
18
|
+
<Skeleton className="h-6 w-16 rounded-full" />
|
|
19
|
+
</div>
|
|
20
|
+
</CardHeader>
|
|
21
|
+
<CardContent>
|
|
22
|
+
<div className="space-y-3">
|
|
23
|
+
{[1, 2, 3].map((i) => (
|
|
24
|
+
<div key={i} className="flex items-start gap-3">
|
|
25
|
+
<Skeleton className="h-4 w-4 rounded-full mt-0.5" />
|
|
26
|
+
<div className="flex-1">
|
|
27
|
+
<Skeleton className="h-4 w-32 mb-1" />
|
|
28
|
+
<Skeleton className="h-3 w-full" />
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
33
|
+
</CardContent>
|
|
34
|
+
</Card>
|
|
35
|
+
);
|
|
36
|
+
}
|