# Script Library Pane Takeover — Implementation Plan > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Redesign the Script Library left pane to have Browse mode (template list + filter bar) and Configure mode (full-height parameter form + action buttons), with the right pane becoming a read-only `ScriptPreview`. **Architecture:** `ScriptLibraryPage` owns `paneMode` local state (`'browse' | 'configure'`). Clicking "Configure →" on a `TemplateCard` calls `store.selectTemplate(id)` then flips the pane. `ScriptConfigurePane` (new component) owns the configure-mode layout — back button, template header, param form, action bar. `ScriptGeneratorPanel` is deleted; the right pane becomes `ScriptPreview` in isolation. **Tech Stack:** React 19, TypeScript, Zustand (`useScriptGeneratorStore`), Tailwind CSS v3, Lucide React. Verification: `npx tsc -b --noEmit` (NOT `npm run build` — pre-existing Node 18 incompatibility with Vite). --- ## File Structure | File | Action | Responsibility | |------|--------|----------------| | `frontend/src/components/scripts/TemplateCard.tsx` | Modify | Non-interactive card with "Configure →" button; no store subscription | | `frontend/src/components/scripts/ScriptTemplateList.tsx` | Modify | Thread `onConfigure` prop to each `TemplateCard` | | `frontend/src/components/scripts/ScriptConfigurePane.tsx` | Create | Configure mode layout: back button, template header, form, action bar | | `frontend/src/pages/ScriptLibraryPage.tsx` | Modify | `paneMode` state, filter-bar moved into left pane, right pane simplified | | `frontend/src/components/scripts/ScriptGeneratorPanel.tsx` | Delete | Superseded by `ScriptConfigurePane` + right-pane simplification | --- ## Chunk 1: All Tasks ### Task 1: Modify `TemplateCard` — remove store subscription, add "Configure →" button **Files:** - Modify: `frontend/src/components/scripts/TemplateCard.tsx` Current state: `TemplateCard` is a ` ) } ``` - [ ] **Step 2: Verify TypeScript** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc -b --noEmit ``` Expected: errors only from `ScriptTemplateList` (it still passes no `onConfigure` prop) — that's fine, it's fixed in Task 2. - [ ] **Step 3: Commit** ```bash git add frontend/src/components/scripts/TemplateCard.tsx git commit -m "refactor: TemplateCard — remove store subscription, add Configure button" ``` --- ### Task 2: Modify `ScriptTemplateList` — thread `onConfigure` prop **Files:** - Modify: `frontend/src/components/scripts/ScriptTemplateList.tsx` Current state: `ScriptTemplateList` accepts `{ inputValue, onClearSearch }` and renders `` with no extra props. - [ ] **Step 1: Update the file** ```tsx import { FileCode, Search } from 'lucide-react' import { useScriptGeneratorStore } from '@/store/scriptGeneratorStore' import { TemplateCard } from './TemplateCard' interface Props { inputValue: string onClearSearch: () => void onConfigure: (id: string) => void } function TemplateSkeleton() { return (
) } export function ScriptTemplateList({ inputValue, onClearSearch, onConfigure }: Props) { const templates = useScriptGeneratorStore(s => s.templates) const isLoadingTemplates = useScriptGeneratorStore(s => s.isLoadingTemplates) if (isLoadingTemplates) { return (
) } if (templates.length === 0) { if (inputValue !== '') { return (

No templates match your search

) } return (

No templates found

) } return (
{templates.map(template => ( ))}
) } ``` - [ ] **Step 2: Verify TypeScript** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc -b --noEmit ``` Expected: errors now only from `ScriptLibraryPage` (it passes no `onConfigure` to `ScriptTemplateList` yet) — that's fine. - [ ] **Step 3: Commit** ```bash git add frontend/src/components/scripts/ScriptTemplateList.tsx git commit -m "refactor: ScriptTemplateList — add onConfigure prop threading" ``` --- ### Task 3: Create `ScriptConfigurePane` — configure mode layout **Files:** - Create: `frontend/src/components/scripts/ScriptConfigurePane.tsx` This new component renders the full configure-mode left pane: back button, loading spinner (when `isLoadingDetail`), first-selection error state, template header with tags, `ScriptParameterForm`, warnings callout, and the Generate/Download/Copy action bar. Data comes from `useScriptGeneratorStore` directly. `canGenerate` and `onBack` come from props. The action-bar Copy button copies `store.generatedScript` and is disabled when `generatedScript === null`. The Download button uses `selectedTemplate.slug` for the filename. Both Generate and Download are disabled when `isGenerating || !canGenerate`. Copy is disabled when `!generatedScript || !canGenerate`. - [ ] **Step 1: Create the file** ```tsx import { useState } from 'react' import { ArrowLeft, Terminal, Download, Loader2, AlertTriangle, Copy, Check } from 'lucide-react' import { cn } from '@/lib/utils' import { useScriptGeneratorStore } from '@/store/scriptGeneratorStore' import { ScriptParameterForm } from './ScriptParameterForm' const COMPLEXITY_CLASSES = { beginner: 'text-emerald-400 bg-emerald-400/10 border-emerald-400/20', intermediate: 'text-amber-400 bg-amber-400/10 border-amber-400/20', advanced: 'text-rose-500 bg-rose-500/10 border-rose-500/20', } as const interface Props { canGenerate: boolean onBack: () => void } export function ScriptConfigurePane({ canGenerate, onBack }: Props) { const selectedTemplate = useScriptGeneratorStore(s => s.selectedTemplate) const isLoadingDetail = useScriptGeneratorStore(s => s.isLoadingDetail) const categories = useScriptGeneratorStore(s => s.categories) const generatedScript = useScriptGeneratorStore(s => s.generatedScript) const generationWarnings = useScriptGeneratorStore(s => s.generationWarnings) const isGenerating = useScriptGeneratorStore(s => s.isGenerating) const generateError = useScriptGeneratorStore(s => s.generateError) const generate = useScriptGeneratorStore(s => s.generate) const [copied, setCopied] = useState(false) const handleCopy = async () => { if (!generatedScript) return try { await navigator.clipboard.writeText(generatedScript) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch { // silently fail } } const handleDownload = () => { if (!generatedScript || !selectedTemplate) return const blob = new Blob([generatedScript], { type: 'text/plain' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `${selectedTemplate.slug}.ps1` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } // Loading state if (isLoadingDetail) { return (
) } // First-selection failure state if (!selectedTemplate) { return (

Failed to load template.

) } const categoryName = categories.find(c => c.id === selectedTemplate.category_id)?.name const displayTags = selectedTemplate.tags.slice(0, 3) const extraTagCount = selectedTemplate.tags.length - 3 return (
{/* Back button */} {/* Template header */}

{selectedTemplate.name}

{selectedTemplate.description && (

{selectedTemplate.description}

)}
{selectedTemplate.requires_elevation && ( ⚠ Elevated )} {selectedTemplate.complexity} {categoryName && ( {categoryName} )} {displayTags.map(tag => ( {tag} ))} {extraTagCount > 0 && ( +{extraTagCount} )}
{/* Parameter form */} {/* Warnings */} {generationWarnings.length > 0 && (
Warnings
{generationWarnings.map((w, i) => (

{w}

))}
)} {/* Action bar */}
{/* Generate error */} {generateError && (

{generateError}

)}
) } ``` - [ ] **Step 2: Verify TypeScript** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc -b --noEmit ``` Expected: No new errors from this file. Errors may still exist from `ScriptLibraryPage` not yet updated — that's fine. - [ ] **Step 3: Commit** ```bash git add frontend/src/components/scripts/ScriptConfigurePane.tsx git commit -m "feat: add ScriptConfigurePane — configure mode layout" ``` --- ### Task 4: Rewrite `ScriptLibraryPage` — pane mode, filter bar in column, right pane simplified **Files:** - Modify: `frontend/src/pages/ScriptLibraryPage.tsx` Changes: 1. Add `paneMode` state (`'browse' | 'configure'`) 2. Add `usePermissions` for `canGenerate` 3. Add `selectedTemplate` subscription for right-pane conditional 4. Move `ScriptFilterBar` into the left pane column (only rendered in browse mode) 5. Add `onConfigure` / `onBack` callbacks 6. Left pane conditionally renders browse content or `ScriptConfigurePane` 7. Right pane: empty state when `selectedTemplate === null`, otherwise `ScriptPreview` in `overflow-hidden` wrapper - [ ] **Step 1: Replace the entire file** ```tsx import { useState, useEffect } from 'react' import { Terminal } from 'lucide-react' import { useScriptGeneratorStore } from '@/store/scriptGeneratorStore' import { usePermissions } from '@/hooks/usePermissions' import { ScriptFilterBar } from '@/components/scripts/ScriptFilterBar' import { ScriptTemplateList } from '@/components/scripts/ScriptTemplateList' import { ScriptConfigurePane } from '@/components/scripts/ScriptConfigurePane' import { ScriptPreview } from '@/components/scripts/ScriptPreview' export default function ScriptLibraryPage() { const [inputValue, setInputValue] = useState('') const [paneMode, setPaneMode] = useState<'browse' | 'configure'>('browse') const loadCategories = useScriptGeneratorStore(s => s.loadCategories) const loadTemplates = useScriptGeneratorStore(s => s.loadTemplates) const setSearch = useScriptGeneratorStore(s => s.setSearch) const selectTemplate = useScriptGeneratorStore(s => s.selectTemplate) const clearOutput = useScriptGeneratorStore(s => s.clearOutput) const selectedTemplate = useScriptGeneratorStore(s => s.selectedTemplate) const { isEngineer } = usePermissions() const canGenerate = isEngineer useEffect(() => { loadCategories().then(() => loadTemplates()) }, [loadCategories, loadTemplates]) const onClearSearch = () => { setInputValue('') setSearch('') } const onConfigure = (id: string) => { selectTemplate(id) setPaneMode('configure') } const onBack = () => { clearOutput() setPaneMode('browse') } return (
{/* Page header */}

Script Library

Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.

{/* Two-column layout */}
{/* Left pane — Browse or Configure */} {paneMode === 'browse' ? (
) : ( )} {/* Right pane — always ScriptPreview */} {selectedTemplate === null ? (

Select a template to get started

) : (
)}
) } ``` - [ ] **Step 2: Verify TypeScript — expect clean** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc -b --noEmit ``` Expected: Zero errors. - [ ] **Step 3: Commit** ```bash git add frontend/src/pages/ScriptLibraryPage.tsx git commit -m "feat: ScriptLibraryPage — pane takeover with Browse/Configure modes" ``` --- ### Task 5: Delete `ScriptGeneratorPanel` **Files:** - Delete: `frontend/src/components/scripts/ScriptGeneratorPanel.tsx` `ScriptGeneratorPanel` is no longer imported or used anywhere — `ScriptLibraryPage` now uses `ScriptConfigurePane` for the left pane and `ScriptPreview` directly for the right pane. - [ ] **Step 1: Verify nothing imports `ScriptGeneratorPanel`** ```bash grep -r "ScriptGeneratorPanel" /home/michaelchihlas/dev/patherly/frontend/src ``` Expected: No output (zero matches). - [ ] **Step 2: Delete the file** ```bash rm /home/michaelchihlas/dev/patherly/frontend/src/components/scripts/ScriptGeneratorPanel.tsx ``` - [ ] **Step 3: Verify TypeScript still clean** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npx tsc -b --noEmit ``` Expected: Zero errors. - [ ] **Step 4: Commit** ```bash git add -u frontend/src/components/scripts/ScriptGeneratorPanel.tsx git commit -m "chore: delete ScriptGeneratorPanel — superseded by ScriptConfigurePane" ``` --- ### Task 6: Smoke test - [ ] **Step 1: Start dev server** ```bash cd /home/michaelchihlas/dev/patherly/frontend && npm run dev ``` - [ ] **Step 2: Verify browse mode** Open `http://localhost:5173/scripts`. Expected: - Template list shows with filter bar above it (inside the left pane, no filter bar outside the pane) - Each template card has a "Configure →" button at bottom-right - Clicking anywhere on the card body (not the button) does nothing - Right pane shows Terminal icon + "Select a template to get started" - [ ] **Step 3: Verify configure mode** Click "Configure →" on any template. Expected: - Left pane transitions to configure view (filter bar and list hidden) - Loading spinner visible briefly, then full configure view appears - Template name, description, complexity badge, category name, tags visible at top - Parameter form below (all fields interactive if engineer role) - "Generate Script" button full-width, cyan gradient - "Download .ps1" and "Copy" buttons disabled (no generated script yet) - Right pane still shows empty state (first click) or previous preview - [ ] **Step 4: Verify generate flow** Fill required parameters, click "Generate Script". Expected: - Spinner appears on Generate button during generation - Right pane updates to show generated PowerShell (with syntax highlighting) - "Download .ps1" and "Copy" buttons become enabled - Copy button copies text; shows "Copied!" for 2 seconds - Download button triggers `.ps1` file download - [ ] **Step 5: Verify Back** Click "← Back to library". Expected: - Left pane returns to browse mode (filter bar + template list visible) - Search input and category pills restore to previous state - Right pane continues showing the previously generated output - [ ] **Step 6: Stop dev server and push** ```bash git push origin feat/script-generator ```