From 48db16ccbf8cd4386a28947137eef23c027f47f8 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Fri, 13 Mar 2026 12:13:18 -0400 Subject: [PATCH] docs: add script library pane takeover implementation plan Co-Authored-By: Claude Sonnet 4.6 --- ...2026-03-13-script-library-pane-takeover.md | 688 ++++++++++++++++++ 1 file changed, 688 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-13-script-library-pane-takeover.md diff --git a/docs/superpowers/plans/2026-03-13-script-library-pane-takeover.md b/docs/superpowers/plans/2026-03-13-script-library-pane-takeover.md new file mode 100644 index 00000000..ce8a41ab --- /dev/null +++ b/docs/superpowers/plans/2026-03-13-script-library-pane-takeover.md @@ -0,0 +1,688 @@ +# 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 +```