# Step Library Page Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Wire up the existing Step Library components into a fully functional standalone page — replacing the "Coming Soon" stub — with create, edit, delete, preview, and save-to-library actions. **Architecture:** `StepLibraryPage` owns all modal state and orchestrates four components: `StepLibraryBrowser` (list + filters), `StepFormModal` (new wrapper for create/edit), `StepDetailModal` (already exists), and a delete confirmation dialog. `StepCard` and `StepLibraryBrowser` get new optional props for library-page-specific actions. No new API calls beyond what already exists in `stepsApi`. **Tech Stack:** React 19, TypeScript, Tailwind CSS, Zustand (`useAuthStore` for current user ID), `stepsApi` + `stepCategoriesApi` (all endpoints already wired to backend). --- ## Key Files Reference - `frontend/src/pages/StepLibraryPage.tsx` — currently a "Coming Soon" stub; will be rewritten - `frontend/src/components/step-library/StepLibraryBrowser.tsx` — list + filters component - `frontend/src/components/step-library/StepCard.tsx` — individual step card - `frontend/src/components/step-library/StepDetailModal.tsx` — preview modal (already complete) - `frontend/src/components/step-library/StepForm.tsx` — create/edit form (already complete) - `frontend/src/api/steps.ts` — `stepsApi.create`, `.get`, `.update`, `.delete` already implemented - `frontend/src/types/step.ts` — `Step`, `StepListItem`, `StepCreate`, `StepUpdate` types - `frontend/src/store/authStore.ts` — use `useAuthStore((s) => s.user)` to get current user - `frontend/src/hooks/usePermissions.ts` — `canCreateSteps` already defined --- ## How to Get Current User ID ```tsx import { useAuthStore } from '@/store/authStore' const user = useAuthStore((s) => s.user) // user.id is the current user's UUID string ``` --- ## Task 1: Extend StepCard with library-page actions **Files:** - Modify: `frontend/src/components/step-library/StepCard.tsx` This task adds `onEdit`, `onDelete`, `onSave`, and `currentUserId` props. When on the library page (these props are present), the action buttons change based on ownership. **Step 1: Read the current file** Already read above. Current interface: ```ts interface StepCardProps { step: StepListItem onPreview: (step: StepListItem) => void onInsert: (step: StepListItem) => void } ``` **Step 2: Update the interface and button logic** Replace the `StepCardProps` interface and the Actions section at the bottom of `StepCard.tsx`. New interface (all new props are optional so existing `CustomStepModal` usage is unchanged): ```tsx interface StepCardProps { step: StepListItem onPreview: (step: StepListItem) => void onInsert?: (step: StepListItem) => void // session context (existing) onEdit?: (step: StepListItem) => void // library page onDelete?: (stepId: string) => void // library page onSave?: (step: StepListItem) => void // library page (save copy) currentUserId?: string // to determine ownership } ``` Replace the Actions section (the `
` at the bottom) with: ```tsx {/* Actions */}
{/* Library page context */} {(onEdit || onDelete || onSave) ? ( isOwn ? ( // Own step: Preview + Edit + Delete icon <> ) : ( // Others' step: Preview + Save <> ) ) : ( // Session context (original): Preview + Insert <> )}
``` Add `isOwn` derived value near top of the component function (before the return): ```tsx const isOwn = currentUserId ? step.created_by === currentUserId : false ``` Add new imports at top of file: ```tsx import { Star, User, Calendar, TrendingUp, Eye, Plus, HelpCircle, Zap, CheckCircle, Pencil, Trash2, Bookmark } from 'lucide-react' ``` **Step 3: Verify TypeScript compiles** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc --noEmit 2>&1 | head -30 ``` Expected: no errors related to StepCard. **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly git add frontend/src/components/step-library/StepCard.tsx git commit -m "feat: add library-page action props to StepCard (edit/delete/save) Co-Authored-By: Claude Sonnet 4.6 " ``` --- ## Task 2: Extend StepLibraryBrowser with library-page props **Files:** - Modify: `frontend/src/components/step-library/StepLibraryBrowser.tsx` Pass the new `onEdit`, `onDelete`, `onSave`, `currentUserId` props through from browser to each `StepCard`. Also expose a `refreshKey` prop so the page can trigger a reload after create/edit/delete/save. **Step 1: Update the interface** Current interface: ```ts interface StepLibraryBrowserProps { onInsert: (step: Step) => void onCreateNew?: () => void showCreateButton?: boolean } ``` New interface: ```ts interface StepLibraryBrowserProps { onInsert?: (step: Step) => void // now optional (not needed on library page) onCreateNew?: () => void showCreateButton?: boolean onEdit?: (step: StepListItem) => void onDelete?: (stepId: string) => void onSave?: (step: StepListItem) => void currentUserId?: string refreshKey?: number // increment to trigger reload } ``` **Step 2: Wire refreshKey into the steps useEffect** In the existing `useEffect` that calls `loadSteps`, add `refreshKey` to the dependency array: ```tsx useEffect(() => { const loadSteps = async () => { ... } loadSteps() }, [searchQuery, selectedCategoryId, selectedStepType, minRating, sortBy, selectedTag, refreshKey]) ``` **Step 3: Pass new props to StepCard** In all three `groupedSteps.private/team/public` map blocks, update `StepCard` usage: ```tsx ``` **Step 4: Verify TypeScript compiles** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc --noEmit 2>&1 | head -30 ``` **Step 5: Commit** ```bash cd /home/michaelchihlas/dev/patherly git add frontend/src/components/step-library/StepLibraryBrowser.tsx git commit -m "feat: pass library-page action props through StepLibraryBrowser + refreshKey Co-Authored-By: Claude Sonnet 4.6 " ``` --- ## Task 3: Create StepFormModal **Files:** - Create: `frontend/src/components/step-library/StepFormModal.tsx` A thin modal wrapper around the existing `StepForm`. Handles both create and edit modes. **Step 1: Create the file** ```tsx import { useState } from 'react' import { X } from 'lucide-react' import { stepsApi } from '@/api/steps' import { StepForm } from './StepForm' import type { Step, StepCreate, StepListItem } from '@/types/step' interface StepFormModalProps { isOpen: boolean onClose: () => void onSuccess: (step: Step) => void editingStep?: StepListItem | null // if set, edit mode; if null/undefined, create mode } export function StepFormModal({ isOpen, onClose, onSuccess, editingStep }: StepFormModalProps) { const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState(null) if (!isOpen) return null const isEditMode = !!editingStep const handleSubmit = async (data: StepCreate) => { setIsSubmitting(true) setError(null) try { let result: Step if (isEditMode && editingStep) { result = await stepsApi.update(editingStep.id, data) } else { result = await stepsApi.create(data) } onSuccess(result) } catch (err) { console.error('Failed to save step:', err) setError('Failed to save step. Please try again.') } finally { setIsSubmitting(false) } } // Build initialData from editingStep for edit mode // StepListItem doesn't have `content`, so for edit we need to fetch full step // This is handled by the parent (StepLibraryPage fetches full step before opening modal) const initialData = editingStep ? { title: editingStep.title, step_type: editingStep.step_type as 'decision' | 'action' | 'solution', visibility: editingStep.visibility as 'private' | 'team' | 'public', category_id: editingStep.category_id, tags: editingStep.tags, } : undefined return (
{/* Header */}

{isEditMode ? 'Edit Step' : 'Create Step'}

{/* Error */} {error && (
{error}
)} {/* Body */}
) } ``` **Step 2: Update StepForm to accept `submitLabel` and `isSubmitting` props** `StepForm` currently has a hardcoded submit button label ("Insert Step") and no loading state. Add these two optional props: ```ts interface StepFormProps { onSubmit: (data: StepCreate) => void onCancel: () => void initialData?: Partial submitLabel?: string // default: 'Insert Step' isSubmitting?: boolean // default: false } ``` In `StepForm`, use them on the submit button: ```tsx ``` **Step 3: Verify TypeScript compiles** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc --noEmit 2>&1 | head -30 ``` **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly git add frontend/src/components/step-library/StepFormModal.tsx \ frontend/src/components/step-library/StepForm.tsx git commit -m "feat: StepFormModal wrapper + submitLabel/isSubmitting props on StepForm Co-Authored-By: Claude Sonnet 4.6 " ``` --- ## Task 4: Rewrite StepLibraryPage **Files:** - Modify: `frontend/src/pages/StepLibraryPage.tsx` This is the main wiring task. Replace the stub with the full page. **Step 1: Write the new page** ```tsx import { useState } from 'react' import { Bookmark, Trash2 } from 'lucide-react' import { useAuthStore } from '@/store/authStore' import { usePermissions } from '@/hooks/usePermissions' import { stepsApi } from '@/api/steps' import { StepLibraryBrowser } from '@/components/step-library/StepLibraryBrowser' import { StepFormModal } from '@/components/step-library/StepFormModal' import type { Step, StepListItem } from '@/types/step' export default function StepLibraryPage() { const user = useAuthStore((s) => s.user) const { canCreateSteps } = usePermissions() // Modal state const [createOpen, setCreateOpen] = useState(false) const [editingStep, setEditingStep] = useState(null) const [deletingStep, setDeletingStep] = useState(null) const [isDeleting, setIsDeleting] = useState(false) const [deleteError, setDeleteError] = useState(null) const [saveToast, setSaveToast] = useState(null) // Increment to trigger StepLibraryBrowser reload const [refreshKey, setRefreshKey] = useState(0) const refresh = () => setRefreshKey(k => k + 1) const handleEdit = (step: StepListItem) => { setEditingStep(step) } const handleDeleteRequest = (stepId: string) => { // Find the step in order to show its title in the confirmation // We store the StepListItem via the browser's onDelete callback // The step object is passed from StepCard which has the full StepListItem setDeletingStep({ id: stepId } as StepListItem) } const handleDeleteConfirm = async () => { if (!deletingStep) return setIsDeleting(true) setDeleteError(null) try { await stepsApi.delete(deletingStep.id) setDeletingStep(null) refresh() } catch (err) { console.error('Failed to delete step:', err) setDeleteError('Failed to delete step. Please try again.') } finally { setIsDeleting(false) } } const handleSave = async (step: StepListItem) => { try { // Fetch full step to get content fields const full = await stepsApi.get(step.id) await stepsApi.create({ title: full.title, step_type: full.step_type, content: full.content, visibility: 'private', category_id: full.category_id, tags: full.tags, }) setSaveToast(`"${full.title}" saved to My Steps`) setTimeout(() => setSaveToast(null), 3000) refresh() } catch (err) { console.error('Failed to save step:', err) } } const handleFormSuccess = (_step: Step) => { setCreateOpen(false) setEditingStep(null) refresh() } return (
{/* Page Header */}

Step Library

Reusable steps you can insert into any flow

{canCreateSteps && ( )}
{/* Browser fills remaining height */}
{ // We need the full StepListItem for the confirmation title. // Pass a minimal object; the title will show as "this step" if not available. setDeletingStep({ id: stepId, title: '' } as StepListItem) }} onSave={handleSave} currentUserId={user?.id} refreshKey={refreshKey} showCreateButton={false} />
{/* Create / Edit Modal */} { setCreateOpen(false); setEditingStep(null) }} onSuccess={handleFormSuccess} editingStep={editingStep} /> {/* Delete Confirmation Dialog */} {deletingStep && (

Delete Step

{deletingStep.title ? <>Are you sure you want to delete "{deletingStep.title}"? : 'Are you sure you want to delete this step?' }

This cannot be undone.

{deleteError && (

{deleteError}

)}
)} {/* Save Toast */} {saveToast && (
{saveToast}
)}
) } ``` **NOTE on delete title:** The `onDelete` callback from `StepCard` only passes `stepId: string`, not the full `StepListItem`. To show the step title in the confirmation dialog, change the `StepLibraryBrowser`'s `onDelete` prop type to pass the full `StepListItem` instead: In `StepLibraryBrowser.tsx`, change: ```ts onDelete?: (stepId: string) => void ``` to: ```ts onDelete?: (step: StepListItem) => void ``` And update where it calls `onDelete` from cards — pass the full `step` object. Update `StepCard` similarly: change `onDelete?: (stepId: string) => void` to `onDelete?: (step: StepListItem) => void` and call `onDelete?.(step)` instead of `onDelete?.(step.id)`. Then in `StepLibraryPage`, use `handleDeleteRequest(step: StepListItem)` and set `setDeletingStep(step)` directly — no need to pass a minimal object. **Step 2: Run TypeScript check** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc --noEmit 2>&1 | head -40 ``` Fix any type errors before proceeding. **Step 3: Run build** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npm run build 2>&1 | tail -20 ``` Expected: build succeeds with no errors. **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly git add frontend/src/pages/StepLibraryPage.tsx \ frontend/src/components/step-library/StepLibraryBrowser.tsx \ frontend/src/components/step-library/StepCard.tsx git commit -m "feat: Step Library page — create, edit, delete, save-to-library Co-Authored-By: Claude Sonnet 4.6 " ``` --- ## Task 5: Manual verification checklist Start the dev server and verify these flows work end-to-end: ```bash docker start patherly_postgres cd /home/michaelchihlas/dev/patherly/backend && source venv/bin/activate && uvicorn app.main:app --reload & cd /home/michaelchihlas/dev/patherly/frontend && npm run dev ``` Navigate to `http://localhost:5173/step-library` and verify: - [ ] Page loads without errors (not "Coming Soon") - [ ] "+ Create Step" button appears (login as engineer or admin) - [ ] Creating a step via the modal saves it and it appears under "My Steps" on reload - [ ] "Edit" button appears on your own step cards - [ ] Editing a step opens the form pre-filled (note: `content` fields won't pre-fill since `StepListItem` doesn't have content — this is acceptable for now; see note below) - [ ] "Delete" button appears on your own step cards - [ ] Delete confirmation shows step title; confirming removes it from the list - [ ] "Save" button appears on team/community step cards - [ ] Saving a step copies it to "My Steps" and shows toast - [ ] "Preview" opens `StepDetailModal` correctly on all card types - [ ] Filters (category, type, rating, sort) work - [ ] Popular tags clickable and filter results **Note on edit pre-fill:** `StepListItem` does not include `content`. The `StepFormModal` passes `initialData` from `editingStep`, but `content` will be missing. For a full pre-fill, `StepLibraryPage.handleEdit` should fetch the full step via `stepsApi.get(step.id)` before opening the modal, and store the result as a `Step` (not `StepListItem`) in `editingStep` state. Update `editingStep` state type to `Step | null` and fetch in `handleEdit`: ```tsx const [editingStep, setEditingStep] = useState(null) const handleEdit = async (step: StepListItem) => { try { const full = await stepsApi.get(step.id) setEditingStep(full) } catch (err) { console.error('Failed to load step for edit:', err) } } ``` Update `StepFormModal`'s `editingStep` prop type to accept `Step | null` and build `initialData` from the full `Step` including `content`: ```tsx editingStep?: Step | null const initialData = editingStep ? { title: editingStep.title, step_type: editingStep.step_type, content: editingStep.content, visibility: editingStep.visibility, category_id: editingStep.category_id, tags: editingStep.tags, } : undefined ``` This should be done as part of Task 4 before verifying. --- ## Task 6: Final build validation and commit ```bash cd /home/michaelchihlas/dev/patherly/frontend && npm run build 2>&1 | tail -20 ``` Expected: clean build, no TypeScript errors, no warnings about missing exports. If clean: ```bash cd /home/michaelchihlas/dev/patherly git add -A git status # confirm only expected files changed git commit -m "chore: step library page final build validation Co-Authored-By: Claude Sonnet 4.6 " ```