Files
resolutionflow/docs/plans/archive/2026-02-25-tree-fork-ui.md
chihlasm 932927b9df chore: archive old plan docs + add survey foundation files
Move completed plan docs to docs/plans/archive/. Add survey migration 046
and reference HTML/plan files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 02:03:38 -05:00

494 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tree Fork UI Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add an explicit `ForkModal` with a "Reason for Forking" field to replace the silent fork flow, and show a "Fork" chip badge on forked tree cards in the library and My Trees views.
**Architecture:** The backend is fully complete (POST `/trees/:id/fork` accepts `{ name, fork_reason }`). The frontend `treesApi.fork()` already accepts these params. We need: (1) `ForkInfo` types added to `tree.ts`, (2) a new `ForkModal` component, (3) updated fork handlers in `TreeLibraryPage` and `MyTreesPage` to open the modal instead of forking silently, (4) a "Fork" chip in all three card views (grid, list, table).
**Tech Stack:** React 19, TypeScript, Tailwind CSS, Lucide React, `treesApi.fork(id, { name, fork_reason })` already wired.
---
## Context for the Implementer
- `treesApi.fork(id, data?)` is at `frontend/src/api/trees.ts:42` — already accepts `{ fork_reason?, name? }`
- `onForkTree` prop exists on all three card views and currently passes only `treeId: string`
- `TreeLibraryPage` has `handleForkTree(treeId: string)` at line ~247 that calls `treesApi.fork(treeId)` silently
- `MyTreesPage` does NOT currently have a fork handler — the "Fork" UI there is an informational message (line ~215), not a button wired to `onForkTree`
- `TreeListItem` (used by all three views) does NOT yet have `fork_depth` or `parent_tree_id` — must add these
- `MyTreesPage` already uses `tree.parent_tree_id` at line ~283 for a "Forked from" display block — this field must be on the type for that to compile cleanly after our changes
- All three card views are in `frontend/src/components/library/`
- Design system: `bg-violet-400/15 text-violet-400` for the Fork chip; `bg-gradient-brand` for the Fork submit button; modal structure uses `bg-card border-border rounded-xl`
---
### Task 1: Add `ForkInfo` type and fork fields to `TreeListItem` and `Tree`
**Files:**
- Modify: `frontend/src/types/tree.ts:142-190`
This is a pure type change — no runtime behavior changes.
**Step 1: Add `ForkInfo` interface and fork fields**
In `frontend/src/types/tree.ts`, after line 141 (the `ProceduralTreeStructure` closing brace), add `ForkInfo` then update `Tree` and `TreeListItem`:
```typescript
export interface ForkInfo {
parent_tree_id: string
parent_tree_name: string | null
fork_depth: number
fork_reason: string | null
has_parent_updates: boolean
}
```
Add to `Tree` interface (after `usage_count: number`):
```typescript
fork_info?: ForkInfo | null
parent_tree_id?: string | null
fork_depth?: number
```
Add to `TreeListItem` interface (after `visibility` field):
```typescript
fork_depth?: number
parent_tree_id?: string | null
```
**Step 2: Verify TypeScript compiles cleanly**
```bash
cd /home/michaelchihlas/dev/patherly/frontend && npm run build 2>&1 | tail -20
```
Expected: Clean build, no errors.
**Step 3: Commit**
```bash
cd /home/michaelchihlas/dev/patherly
git add frontend/src/types/tree.ts
git commit -m "feat: add ForkInfo type and fork fields to Tree/TreeListItem
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
```
---
### Task 2: Create `ForkModal` component
**Files:**
- Create: `frontend/src/components/library/ForkModal.tsx`
**Step 1: Create the component file**
Create `frontend/src/components/library/ForkModal.tsx` with this exact content:
```tsx
import { useState } from 'react'
import { GitBranch, X } from 'lucide-react'
import { treesApi } from '@/api/trees'
import { toast } from '@/lib/toast'
import { cn } from '@/lib/utils'
import { useNavigate } from 'react-router-dom'
interface ForkModalProps {
treeId: string
treeName: string
onClose: () => void
}
export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
const navigate = useNavigate()
const [name, setName] = useState(`Copy of ${treeName}`)
const [forkReason, setForkReason] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!name.trim()) return
setIsSubmitting(true)
setError(null)
try {
await treesApi.fork(treeId, {
name: name.trim(),
fork_reason: forkReason.trim() || undefined,
})
toast.success('Flow forked successfully')
onClose()
navigate('/my-trees')
} catch (err) {
console.error('Failed to fork flow:', err)
setError('Failed to fork flow. Please try again.')
} finally {
setIsSubmitting(false)
}
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
<div className="w-full max-w-md rounded-xl border border-border bg-card shadow-xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-5 py-4">
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4 text-muted-foreground" />
<h2 className="text-sm font-semibold text-foreground">Fork Flow</h2>
</div>
<button
onClick={onClose}
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<X className="h-4 w-4" />
</button>
</div>
{/* Body */}
<form onSubmit={handleSubmit} className="space-y-4 px-5 py-4">
<div>
<label className="mb-1.5 block text-xs font-medium text-muted-foreground">
Name <span className="text-red-400">*</span>
</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
autoFocus
className={cn(
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
<div>
<label className="mb-1.5 block text-xs font-medium text-muted-foreground">
Reason for Forking{' '}
<span className="text-muted-foreground/60">(optional)</span>
</label>
<textarea
value={forkReason}
onChange={(e) => setForkReason(e.target.value)}
rows={3}
placeholder="e.g. customizing for a specific client…"
className={cn(
'w-full resize-none rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
{error && (
<p className="text-xs text-red-400">{error}</p>
)}
{/* Footer */}
<div className="flex justify-end gap-2 pt-1">
<button
type="button"
onClick={onClose}
className="rounded-lg border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
Cancel
</button>
<button
type="submit"
disabled={isSubmitting || !name.trim()}
className="bg-gradient-brand flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-40"
>
{isSubmitting ? 'Forking…' : 'Fork Flow'}
</button>
</div>
</form>
</div>
</div>
)
}
```
**Step 2: Verify TypeScript compiles cleanly**
```bash
cd /home/michaelchihlas/dev/patherly/frontend && npm run build 2>&1 | tail -20
```
Expected: Clean build, no errors.
**Step 3: Commit**
```bash
cd /home/michaelchihlas/dev/patherly
git add frontend/src/components/library/ForkModal.tsx
git commit -m "feat: add ForkModal component with name and reason fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
```
---
### Task 3: Update `TreeLibraryPage` to open `ForkModal`
**Files:**
- Modify: `frontend/src/pages/TreeLibraryPage.tsx`
The current `handleForkTree` at ~line 247 calls `treesApi.fork(treeId)` silently. Replace it with state that opens `ForkModal`.
**Step 1: Add import for `ForkModal` and `TreeListItem`**
At the top of `TreeLibraryPage.tsx`, the file already imports `TreeListItem` from `@/types`. Add `ForkModal` to the library component imports. Find the line that imports from `@/components/library/...` and add:
```tsx
import { ForkModal } from '@/components/library/ForkModal'
```
**Step 2: Replace fork state**
Find (around line 76):
```tsx
// Fork state
const [isForkingTree, setIsForkingTree] = useState(false)
```
Replace with:
```tsx
// Fork modal state
const [forkTarget, setForkTarget] = useState<TreeListItem | null>(null)
```
**Step 3: Replace `handleForkTree`**
Find (around line 247):
```tsx
const handleForkTree = async (treeId: string) => {
if (isForkingTree) return
setIsForkingTree(true)
try {
await treesApi.fork(treeId)
toast.success('Flow forked successfully')
navigate('/my-trees')
} catch (err) {
console.error('Failed to fork flow:', err)
toast.error('Failed to fork flow')
} finally {
setIsForkingTree(false)
}
}
```
Replace with:
```tsx
const handleForkTree = (treeId: string) => {
const tree = trees.find((t) => t.id === treeId)
if (tree) setForkTarget(tree)
}
```
Note: `trees` is the existing state variable holding the fetched tree list. If the variable is named differently in context, use the correct name.
**Step 4: Add `ForkModal` to JSX**
Find the closing `</div>` of the page's root element (near the end of the return statement, after all the other modals like `FolderEditModal`, `ConfirmDialog`). Add before the root closing tag:
```tsx
{forkTarget && (
<ForkModal
treeId={forkTarget.id}
treeName={forkTarget.name}
onClose={() => setForkTarget(null)}
/>
)}
```
**Step 5: Verify TypeScript compiles cleanly**
```bash
cd /home/michaelchihlas/dev/patherly/frontend && npm run build 2>&1 | tail -20
```
Expected: Clean build, no errors. If there are unused import errors for `treesApi` (if it was only used by the old `handleForkTree`), check whether `treesApi` is still used elsewhere on the page; if not, remove it from imports.
**Step 6: Commit**
```bash
cd /home/michaelchihlas/dev/patherly
git add frontend/src/pages/TreeLibraryPage.tsx
git commit -m "feat: open ForkModal on fork action in TreeLibraryPage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
```
---
### Task 4: Update `MyTreesPage` to open `ForkModal`
**Files:**
- Modify: `frontend/src/pages/MyTreesPage.tsx`
`MyTreesPage` does NOT currently pass `onForkTree` to any view components — the page is a custom hand-rolled list, not using the three card views. The fork action is not wired here. However, `tree.parent_tree_id` is already rendered (line ~283), so we just need to add a `ForkModal` trigger for any fork buttons that may be present.
**Step 1: Read the MyTreesPage fork section carefully**
Read lines 200300 of `frontend/src/pages/MyTreesPage.tsx` to understand the exact current fork UI and whether there's a fork button.
```bash
sed -n '200,300p' /home/michaelchihlas/dev/patherly/frontend/src/pages/MyTreesPage.tsx
```
**Step 2: Add import for `ForkModal`**
Add to the imports:
```tsx
import { ForkModal } from '@/components/library/ForkModal'
```
**Step 3: Add fork modal state**
Find the state declarations section. Add:
```tsx
const [forkTarget, setForkTarget] = useState<TreeListItem | null>(null)
```
**Step 4: Add a "Fork" button to each tree row (if not already present)**
In the tree list rendering, find the action buttons area for each tree (look for the edit/delete buttons). Add a Fork button next to them:
```tsx
<button
type="button"
onClick={() => setForkTarget(tree)}
className="rounded-md border border-border p-2 text-muted-foreground hover:bg-accent hover:text-foreground"
title="Fork flow"
>
<GitBranch className="h-4 w-4" />
</button>
```
Note: `GitBranch` is already imported in `MyTreesPage` (line 3).
**Step 5: Add `ForkModal` to JSX**
Find the end of the return statement. Before the root closing tag, add:
```tsx
{forkTarget && (
<ForkModal
treeId={forkTarget.id}
treeName={forkTarget.name}
onClose={() => setForkTarget(null)}
/>
)}
```
**Step 6: Verify TypeScript compiles cleanly**
```bash
cd /home/michaelchihlas/dev/patherly/frontend && npm run build 2>&1 | tail -20
```
Expected: Clean build, no errors.
**Step 7: Commit**
```bash
cd /home/michaelchihlas/dev/patherly
git add frontend/src/pages/MyTreesPage.tsx
git commit -m "feat: add ForkModal to MyTreesPage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
```
---
### Task 5: Add "Fork" chip badge to all three card views
**Files:**
- Modify: `frontend/src/components/library/TreeGridView.tsx`
- Modify: `frontend/src/components/library/TreeListView.tsx`
- Modify: `frontend/src/components/library/TreeTableView.tsx`
Show a small violet chip when `tree.fork_depth > 0` (or `tree.parent_tree_id` is set). Place it near the tree name or alongside other metadata chips.
**Step 1: Add Fork chip to `TreeGridView`**
Read `frontend/src/components/library/TreeGridView.tsx` lines 60100 to find where tree name and category badge are rendered.
In the name/header area of each card (near where `tree.category_info` chip is rendered), add:
```tsx
{(tree.fork_depth ?? 0) > 0 && (
<span className="shrink-0 rounded-full bg-violet-400/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-violet-400">
Fork
</span>
)}
```
Place this chip alongside or just after the tree name `<span>`, or next to the category badge — wherever fits the card layout (read the file to confirm exact placement).
**Step 2: Add Fork chip to `TreeListView`**
Read `frontend/src/components/library/TreeListView.tsx` lines 60130 to find the name + metadata row.
Add the same chip in the same relative position:
```tsx
{(tree.fork_depth ?? 0) > 0 && (
<span className="shrink-0 rounded-full bg-violet-400/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-violet-400">
Fork
</span>
)}
```
**Step 3: Add Fork chip to `TreeTableView`**
Read `frontend/src/components/library/TreeTableView.tsx` lines 80150 to find the name column cell.
Add the same chip inline after the tree name in the name column:
```tsx
{(tree.fork_depth ?? 0) > 0 && (
<span className="shrink-0 rounded-full bg-violet-400/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-violet-400">
Fork
</span>
)}
```
**Step 4: Verify TypeScript compiles cleanly**
```bash
cd /home/michaelchihlas/dev/patherly/frontend && npm run build 2>&1 | tail -20
```
Expected: Clean build, no errors.
**Step 5: Commit**
```bash
cd /home/michaelchihlas/dev/patherly
git add frontend/src/components/library/TreeGridView.tsx \
frontend/src/components/library/TreeListView.tsx \
frontend/src/components/library/TreeTableView.tsx
git commit -m "feat: show Fork chip badge on forked tree cards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
```
---
## Manual Verification Checklist
After all tasks are complete:
1. **Fork flow (Library):** Go to Flow Library → click GitBranch icon on any published flow → `ForkModal` opens with name pre-filled as "Copy of <name>" → enter a reason → click "Fork Flow" → toast appears → redirected to My Trees.
2. **Fork flow (My Trees):** Go to My Trees → find a flow → click Fork button → same modal + behavior.
3. **Fork badge:** Fork a flow → go to My Trees → forked flow shows violet "Fork" chip in card header.
4. **Badge in Library views:** In Flow Library, switch to grid/list/table view — forked flows (your own) show "Fork" chip.
5. **Reason is optional:** Fork a flow without entering a reason → still works.
6. **Cancel:** Open ForkModal → click Cancel → modal closes, nothing forked.