Files
kandev/docs/plans/workflow-duplication/plan.md
T
2026-08-23 17:34:52 +00:00

121 lines
9.9 KiB
Markdown

---
spec: docs/specs/tasks/requirements/workflow-duplication.md
created: 2026-08-11
status: complete
---
# Implementation Plan: Workflow Duplication
## Overview
Workflow duplication will reuse the current draft and save coordinator. It will not add a backend duplication endpoint.
The frontend will load the saved source steps and build an independent client-only graph. It will insert the graph after the source. The current workflow contributor will persist it on **Save changes**.
This order preserves the no-write-before-save behavior. It also reuses the current partial-save retry contract.
## Backend
No new duplication endpoint, schema, service, or route is required. The existing workflow-step controller and frontend adapter must carry the persisted `stage_type` field through create, update, and list operations so the current save endpoints preserve semantic step configuration.
The draft-save pipeline creates only a workflow and its steps. As a result, it excludes tasks and history.
## Frontend
### Draft construction
- Add `apps/web/app/settings/workspace/workflow-duplication.ts` with pure helpers that:
- derive the lowest available `<base> (copy N)` name from persisted workflows and route-local drafts.
- allocate a temporary workflow ID and one temporary ID per source step with `generateUUID()`.
- copy only editable workflow metadata: `description`, `prompt`, and `agent_profile_id`.
- omit `workflow_template_id`, `source`, `source_path`, timestamps, and server identity.
- clone all persisted editable step fields, including `stage_type`.
- remap nested `step_id` values in events and remap `pull_from_step_id`.
- clear timestamps and return an independent object graph.
- Extend `useWorkflowCreation` in `apps/web/app/settings/workspace/use-workflow-creation.ts` with `handleDuplicateWorkflow(source, sourceSteps)`. It stores the copied steps in `initialStepsByWorkflowId` and inserts the draft immediately after the source in `workflowItems`.
- Wire the handler through `useWorkflowActions`, `WorkflowList`, and `WorkspaceWorkflowsClient` in `apps/web/app/settings/workspace/workspace-workflows-client.tsx`. Existing ID remapping and workflow-order save behavior will persist the copy's final position.
### Duplicate action
- Extend `WorkflowCard` in `apps/web/components/settings/workflow-card.tsx` with an async duplication callback. On activation, fetch the authoritative saved source steps with `listWorkflowStepsAction`, then hand them to the parent draft constructor.
- Keep request state and disabled/error behavior in `apps/web/app/settings/workspace/use-workflow-duplication.ts`; keep dialog composition separate from the workflow card so the card stays within the frontend size limit.
- Disable the action for temporary workflows, dirty workflow/step drafts, pending workflow mutations, and while duplication is loading. Hide or disable it in the Improve Kandev workspace. Do not disable it solely because `workflow.source` is sync-managed.
- Surface step-load failures through the existing toast provider and create no draft on failure.
- Add a labeled **Duplicate** button with an `IconCopy`, stable `data-testid="duplicate-workflow-button"`, loading state, and save-first tooltip to `apps/web/components/settings/workflow-card-header-actions.tsx`. Place it between Export and Delete.
- Add all new copy to `apps/web/src/locales/en/workflows.json`. Do not hardcode user-facing strings.
### Mobile design contract
- **Desktop outcome:** the workflow-card footer offers Export, Duplicate, and Delete. Duplicate opens the full copied card after its source.
- **Mobile entry point:** the same visible labeled Duplicate action remains in the existing wrapping card-action row. It is not hidden behind hover or a desktop-only menu.
- **Nearest shipped exemplar:** use `WorkflowCardHeaderActions` and `apps/web/e2e/tests/workflow/mobile-workflow-settings.spec.ts`. Reuse their single-column composition and document scroll owner.
- **Hierarchy and primary action:** the copied card is the next vertical item, while the existing safe-area-aware floating **Save changes** control remains the persistence action.
- **Presentation rationale:** duplication is a single frequent command with no choices, so a direct button is clearer than a drawer or dialog. The resulting editor is substantive content and remains inline rather than being placed in a temporary overlay.
- **Geometry:** give the new touch action at least a 44px active height on phone/coarse-pointer layouts, keep the existing document as the only page scroll owner, and add no fixed controls or new safe-area requirements.
- **Shared logic:** all viewports share naming, graph cloning, request state, and persistence. Only button size and wrapping are responsive.
### Public documentation
- Update the workflow authoring how-to in `docs/public/workflow-tips.md` with the Duplicate flow, save requirement, generated names, and the explicit exclusion of tasks and history.
## Tests
- **What:** copied names choose `(copy)`, then the lowest free numeric suffix, including when duplicating a duplicate.
**File:** `apps/web/app/settings/workspace/workflow-duplication.test.ts`.
**How:** table-driven pure-function tests including persisted and unsaved name collisions.
- **What:** workflow and step configuration is deeply copied, source metadata is omitted, and all internal step references target copied IDs.
**File:** `apps/web/app/settings/workspace/workflow-duplication.test.ts`.
**How:** construct a multi-step graph with nested transition actions and pull-from relationships. Mutate the result and prove the source is unchanged.
- **What:** the creation hook inserts the duplicate after its source and registers copied initial steps without persistence.
**File:** `apps/web/app/settings/workspace/use-workflow-creation.test.ts`.
**How:** hook test with mocked state setter and deterministic UUIDs.
- **What:** the action invokes duplication, exposes loading/error-safe disabled states, prevents same-render duplicate requests, and explains why an unsaved source cannot be copied.
**File:** `apps/web/components/settings/workflow-card-header-actions.test.tsx` and, if needed for card request wiring, a focused `workflow-card` test.
**How:** Testing Library interaction and accessibility assertions.
- **What:** the existing workflow-step transport preserves a non-custom stage type through list, create, update, and draft save.
**Files:** `apps/web/app/actions/workspaces.test.ts`, `apps/backend/internal/workflow/handlers/step_events_test.go`, and `apps/web/e2e/tests/workflow/workflow-duplication.spec.ts`.
**How:** assert frontend DTO and request bodies, assert the backend update event payload, and assert a persisted `review` step remains `review` after duplication.
## E2E Tests
- **Scenario:** GIVEN a configured workflow with a task, WHEN the user duplicates it, THEN a configured draft appears without a write. After Save and reload, it has remapped steps and no copied task.
**File:** `apps/web/e2e/tests/workflow/workflow-duplication.spec.ts`.
**What to verify:** generated name, pre-save workflow count, copied metadata and step relationships, post-save persistence, source preservation, and absence of tasks in copied steps.
- **Scenario:** GIVEN a 390px touch viewport, WHEN the user duplicates and saves a workflow, THEN the action is touch-reachable and the copied editor stays within the viewport.
**File:** `apps/web/e2e/tests/workflow/mobile-workflow-duplication.spec.ts`.
**What to verify:** `.tap()` activation, at least 44px Duplicate hitbox, visible duplicate card and Save action, persistence after reload, and no document horizontal overflow.
- Extend `apps/web/e2e/pages/workflow-settings-page.ts` with a duplicate-action locator/helper shared by both specs.
## Verification Results
Passed:
- `cd apps && pnpm install --frozen-lockfile` completed successfully.
- `cd apps && pnpm --filter @kandev/web test -- --run components/settings/workflow-card-header-actions.test.tsx app/settings/workspace/workflow-duplication.test.ts app/settings/workspace/use-workflow-creation.test.ts app/settings/workspace/use-workflow-duplication.test.ts` passed 4 files and 20 tests.
- `cd apps/web && pnpm --filter @kandev/web test -- --run app/actions/workspaces.test.ts app/settings/workspace/use-workflow-duplication.test.ts app/settings/workspace/workflow-duplication.test.ts components/settings/workflow-card-actions.test.ts` passed 4 files and 41 tests after the review fixes.
- `cd apps/backend && go test ./internal/workflow/handlers -run 'TestHTTP(UpdateStepPublishesUpdatedEvent|CreateStepPublishesCreatedEvent)' -count=1` passed.
- `cd apps/web && pnpm run typecheck` passed.
- `cd apps && pnpm --filter @kandev/web lint` passed for all changed frontend, E2E, and test files.
- `cd apps/web && pnpm run i18n:check && pnpm run i18n:ratchet` passed. Existing real-locale parity warnings remain advisory; the pseudo catalog and new-code ratchet are clean.
- `node --test scripts/validate-public-docs.test.mjs && node scripts/validate-public-docs.mjs` passed 59 tests and validated 41 published docs pages.
- `git diff --check` passed.
- `cd apps/web && pnpm e2e:run tests/workflow/workflow-duplication.spec.ts` passed 1 desktop test.
- `cd apps/web && pnpm e2e:run --no-build --project mobile-chrome tests/workflow/mobile-workflow-duplication.spec.ts` passed 1 mobile test after the shared card finder was made timing-safe.
- The managed E2E runner cleaned `e2e/test-results`, `e2e/blob-report`, `.pr-assets`, and temporary shard logs before each run. No generated E2E artifacts remain in the worktree.
## Implementation Waves And Parallel Candidates
Wave 1:
- [x] [Task 01: Build duplication drafts](task-01-build-duplication-drafts.md)
Wave 2:
- [x] [Task 02: Expose duplicate action](task-02-expose-duplicate-action.md)
Wave 3:
- [x] [Task 03: Prove duplication flows](task-03-prove-duplication-flows.md)
The tasks are sequential. The action depends on the draft constructor, and E2E depends on the completed UI and persistence wiring.