Skip to content

Commit 2c26c9e

Browse files
feat: autosave draft, re-run step hook, migration 075
- Migration 075: add draft_config column to teams table - useSaveDraft/usePublishDraft hooks for canvas autosave workflow - useRerunStep hook for re-executing individual pipeline steps - Extended updateTeam to accept draft_config and output_route_ids
1 parent 1d6f5cd commit 2c26c9e

5 files changed

Lines changed: 101 additions & 2 deletions

File tree

src/db/teams.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export async function createTeam(input: CreateTeamInput): Promise<Team> {
7171
/** Update a team's details */
7272
export async function updateTeam(
7373
id: string,
74-
updates: Partial<Pick<Team, 'name' | 'description' | 'config' | 'mode'>>,
74+
updates: Partial<Pick<Team, 'name' | 'description' | 'config' | 'mode' | 'output_route_ids' | 'draft_config'>>,
7575
): Promise<Team> {
7676
const result = await supabase
7777
.from('teams')

src/hooks/useRerunStep.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// Copyright (C) 2026 CrewForm
3+
4+
/**
5+
* Hook for re-running a single pipeline step.
6+
*
7+
* Calls the task-runner endpoint to re-execute a specific step
8+
* using the output of the previous step as input.
9+
* Only applicable to pipeline mode.
10+
*/
11+
12+
import { useMutation, useQueryClient } from '@tanstack/react-query'
13+
14+
interface RerunStepInput {
15+
runId: string
16+
teamId: string
17+
stepIdx: number
18+
}
19+
20+
interface RerunStepResult {
21+
success: boolean
22+
message?: string
23+
}
24+
25+
/**
26+
* Re-run a single pipeline step.
27+
*
28+
* The task-runner will:
29+
* 1. Fetch the previous step's output from team_messages
30+
* 2. Re-execute the agent at stepIdx with that input
31+
* 3. Write new messages for the re-run
32+
* 4. Reset downstream steps to pending
33+
*/
34+
export function useRerunStep() {
35+
const queryClient = useQueryClient()
36+
37+
return useMutation<RerunStepResult, Error, RerunStepInput>({
38+
mutationFn: async ({ runId, stepIdx }) => {
39+
const response = await fetch(`/api/team-runs/${runId}/steps/${stepIdx}/rerun`, {
40+
method: 'POST',
41+
headers: { 'Content-Type': 'application/json' },
42+
})
43+
44+
if (!response.ok) {
45+
const body = await response.json().catch(() => ({})) as { error?: string }
46+
throw new Error(body.error ?? `Failed to re-run step ${stepIdx}`)
47+
}
48+
49+
return response.json() as Promise<RerunStepResult>
50+
},
51+
onSuccess: (_data, variables) => {
52+
// Invalidate the team run and messages queries
53+
void queryClient.invalidateQueries({ queryKey: ['team-run', variables.runId] })
54+
void queryClient.invalidateQueries({ queryKey: ['team-messages', variables.runId] })
55+
void queryClient.invalidateQueries({ queryKey: ['team', variables.teamId] })
56+
},
57+
})
58+
}

src/hooks/useUpdateTeam.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Team } from '@/types'
77

88
interface UpdateTeamInput {
99
id: string
10-
updates: Partial<Pick<Team, 'name' | 'description' | 'config' | 'mode' | 'output_route_ids'>>
10+
updates: Partial<Pick<Team, 'name' | 'description' | 'config' | 'mode' | 'output_route_ids' | 'draft_config'>>
1111
}
1212

1313
/**
@@ -24,3 +24,34 @@ export function useUpdateTeam() {
2424
},
2525
})
2626
}
27+
28+
/**
29+
* Save a draft config (canvas autosave — non-destructive).
30+
*/
31+
export function useSaveDraft() {
32+
const queryClient = useQueryClient()
33+
34+
return useMutation<Team, Error, { teamId: string; draftConfig: Team['config'] }>({
35+
mutationFn: ({ teamId, draftConfig }) =>
36+
updateTeam(teamId, { draft_config: draftConfig }),
37+
onSuccess: (updated) => {
38+
void queryClient.invalidateQueries({ queryKey: ['team', updated.id] })
39+
},
40+
})
41+
}
42+
43+
/**
44+
* Publish draft → copies draft_config to config and clears draft_config.
45+
*/
46+
export function usePublishDraft() {
47+
const queryClient = useQueryClient()
48+
49+
return useMutation<Team, Error, { teamId: string; draftConfig: Team['config'] }>({
50+
mutationFn: ({ teamId, draftConfig }) =>
51+
updateTeam(teamId, { config: draftConfig, draft_config: null }),
52+
onSuccess: (updated) => {
53+
void queryClient.invalidateQueries({ queryKey: ['team', updated.id] })
54+
void queryClient.invalidateQueries({ queryKey: ['teams', updated.workspace_id] })
55+
},
56+
})
57+
}

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export interface Team {
133133
description: string
134134
mode: TeamMode
135135
config: PipelineConfig | OrchestratorConfig | CollaborationConfig
136+
draft_config: PipelineConfig | OrchestratorConfig | CollaborationConfig | null
136137
output_route_ids: string[] | null // null = all routes, [] = none, [...ids] = specific
137138
// Marketplace fields
138139
is_published: boolean
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Migration: 075_team_draft_config.sql
2+
-- Adds draft_config column for autosave/publish workflow on the canvas.
3+
-- When draft_config IS NOT NULL the team has unpublished canvas changes.
4+
5+
ALTER TABLE public.teams
6+
ADD COLUMN IF NOT EXISTS draft_config JSONB;
7+
8+
COMMENT ON COLUMN public.teams.draft_config IS
9+
'Draft team config from canvas autosave. NULL = no pending changes. Copy to config to publish.';

0 commit comments

Comments
 (0)