# 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 `<button>` that calls `store.selectTemplate()` on click and applies active-border styling when `selectedTemplate?.id === template.id`. It imports `useScriptGeneratorStore`.

- [ ] **Step 1: Replace the entire file with the updated implementation**

```tsx
import { ShieldAlert } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { ScriptTemplateListItem } from '@/types'

const COMPLEXITY_CLASSES: Record<ScriptTemplateListItem['complexity'], string> = {
  beginner: 'text-emerald-400 bg-emerald-400/10',
  intermediate: 'text-amber-400 bg-amber-400/10',
  advanced: 'text-rose-500 bg-rose-500/10',
}

interface Props {
  template: ScriptTemplateListItem
  onConfigure: (id: string) => void
}

export function TemplateCard({ template, onConfigure }: Props) {
  return (
    <div
      className={cn(
        'w-full text-left px-4 py-3 rounded-xl border transition-all',
        'border-border bg-transparent'
      )}
    >
      <div className="flex items-start justify-between gap-2 mb-1">
        <span className="text-sm font-medium text-foreground line-clamp-1">
          {template.name}
        </span>
        <div className="flex items-center gap-1.5 shrink-0">
          {template.requires_elevation && (
            <span title="Requires administrator elevation">
              <ShieldAlert size={13} className="text-amber-400" />
            </span>
          )}
          <span className={cn('font-label text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded', COMPLEXITY_CLASSES[template.complexity])}>
            {template.complexity}
          </span>
        </div>
      </div>

      {template.description && (
        <p className="text-xs text-muted-foreground line-clamp-2 mb-2">
          {template.description}
        </p>
      )}

      <div className="flex items-center justify-between gap-3">
        <div className="flex items-center gap-3 text-[0.625rem] text-muted-foreground font-label">
          <span>{template.usage_count}× used</span>
          {template.tags.length > 0 && (
            <div className="flex gap-1 flex-wrap">
              {template.tags.slice(0, 3).map(tag => (
                <span key={tag} className="bg-white/5 border border-border rounded px-1.5 py-0.5">
                  {tag}
                </span>
              ))}
              {template.tags.length > 3 && (
                <span className="text-muted-foreground">+{template.tags.length - 3}</span>
              )}
            </div>
          )}
        </div>
        <button
          type="button"
          onClick={() => onConfigure(template.id)}
          className="shrink-0 bg-primary/10 border border-primary/20 text-primary text-xs px-2.5 py-1 rounded-md hover:bg-primary/20 transition-colors"
        >
          Configure →
        </button>
      </div>
    </div>
  )
}
```

- [ ] **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 `<TemplateCard template={template} />` 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 (
    <div className="px-4 py-3 rounded-xl border border-border animate-pulse">
      <div className="flex justify-between mb-2">
        <div className="h-4 w-2/3 bg-white/10 rounded" />
        <div className="h-4 w-14 bg-white/10 rounded" />
      </div>
      <div className="h-3 w-full bg-white/5 rounded mb-1" />
      <div className="h-3 w-3/4 bg-white/5 rounded" />
    </div>
  )
}

export function ScriptTemplateList({ inputValue, onClearSearch, onConfigure }: Props) {
  const templates = useScriptGeneratorStore(s => s.templates)
  const isLoadingTemplates = useScriptGeneratorStore(s => s.isLoadingTemplates)

  if (isLoadingTemplates) {
    return (
      <div className="flex flex-col gap-2 p-2">
        <TemplateSkeleton />
        <TemplateSkeleton />
        <TemplateSkeleton />
      </div>
    )
  }

  if (templates.length === 0) {
    if (inputValue !== '') {
      return (
        <div className="flex flex-col items-center justify-center gap-3 py-12 text-center px-4">
          <Search size={32} className="text-muted-foreground/40" />
          <p className="text-sm text-muted-foreground">No templates match your search</p>
          <button
            type="button"
            onClick={onClearSearch}
            className="text-xs text-primary hover:underline"
          >
            Clear search
          </button>
        </div>
      )
    }
    return (
      <div className="flex flex-col items-center justify-center gap-3 py-12 text-center px-4">
        <FileCode size={32} className="text-muted-foreground/40" />
        <p className="text-sm text-muted-foreground">No templates found</p>
      </div>
    )
  }

  return (
    <div className="flex flex-col gap-2 p-2">
      {templates.map(template => (
        <TemplateCard key={template.id} template={template} onConfigure={onConfigure} />
      ))}
    </div>
  )
}
```

- [ ] **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 (
      <div className="glass-card-static h-full flex flex-col p-4 overflow-y-auto">
        <button
          type="button"
          onClick={onBack}
          className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-4 w-fit"
        >
          <ArrowLeft size={12} />
          Back to library
        </button>
        <div className="flex-1 flex items-center justify-center">
          <Loader2 size={28} className="text-primary animate-spin" />
        </div>
      </div>
    )
  }

  // First-selection failure state
  if (!selectedTemplate) {
    return (
      <div className="glass-card-static h-full flex flex-col p-4 overflow-y-auto">
        <button
          type="button"
          onClick={onBack}
          className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-4 w-fit"
        >
          <ArrowLeft size={12} />
          Back to library
        </button>
        <div className="flex-1 flex flex-col items-center justify-center gap-3 text-center">
          <Terminal size={32} className="text-muted-foreground/40" />
          <p className="text-sm text-muted-foreground">Failed to load template.</p>
        </div>
      </div>
    )
  }

  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 (
    <div className="glass-card-static h-full flex flex-col p-4 overflow-y-auto">
      {/* Back button */}
      <button
        type="button"
        onClick={onBack}
        className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-4 w-fit"
      >
        <ArrowLeft size={12} />
        Back to library
      </button>

      {/* Template header */}
      <div className="mb-3">
        <h2 className="text-base font-semibold font-heading text-foreground">
          {selectedTemplate.name}
        </h2>
        {selectedTemplate.description && (
          <p className="text-sm text-muted-foreground mt-0.5">{selectedTemplate.description}</p>
        )}
        <div className="flex items-center gap-1.5 flex-wrap mt-2">
          {selectedTemplate.requires_elevation && (
            <span
              title="Requires administrator elevation"
              className="font-label text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded border text-amber-400 bg-amber-400/10 border-amber-400/20"
            >
              ⚠ Elevated
            </span>
          )}
          <span className={cn(
            'font-label text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded border',
            COMPLEXITY_CLASSES[selectedTemplate.complexity]
          )}>
            {selectedTemplate.complexity}
          </span>
          {categoryName && (
            <span className="font-label text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded border border-border text-muted-foreground bg-white/5">
              {categoryName}
            </span>
          )}
          {displayTags.map(tag => (
            <span key={tag} className="font-label text-[0.625rem] px-1.5 py-0.5 rounded border border-border text-muted-foreground bg-white/5">
              {tag}
            </span>
          ))}
          {extraTagCount > 0 && (
            <span className="font-label text-[0.625rem] text-muted-foreground">+{extraTagCount}</span>
          )}
        </div>
      </div>

      <div className="border-t border-border mb-4" />

      {/* Parameter form */}
      <ScriptParameterForm canGenerate={canGenerate} />

      {/* Warnings */}
      {generationWarnings.length > 0 && (
        <div className="flex flex-col gap-1 rounded-lg border border-amber-400/20 bg-amber-400/5 p-3 mt-4">
          <div className="flex items-center gap-1.5 text-amber-400 text-xs font-medium mb-1">
            <AlertTriangle size={13} />
            Warnings
          </div>
          {generationWarnings.map((w, i) => (
            <p key={i} className="text-xs text-amber-400/80">{w}</p>
          ))}
        </div>
      )}

      {/* Action bar */}
      <div className="flex flex-col gap-2 mt-4 pt-1">
        <span title={!canGenerate ? 'Engineer access required' : undefined}>
          <button
            type="button"
            onClick={() => generate()}
            disabled={isGenerating || !canGenerate}
            className="w-full flex items-center justify-center gap-1.5 bg-gradient-brand text-[#101114] font-semibold text-sm px-4 py-2 rounded-[10px] hover:opacity-90 active:scale-[0.97] transition-all shadow-lg shadow-primary/20 disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100"
          >
            {isGenerating && <Loader2 size={14} className="animate-spin" />}
            Generate Script
          </button>
        </span>

        <div className="flex gap-2">
          <span className="flex-1" title={!canGenerate ? 'Engineer access required' : undefined}>
            <button
              type="button"
              onClick={handleDownload}
              disabled={!generatedScript || !canGenerate}
              className="w-full flex items-center justify-center gap-1.5 bg-white/5 border border-border text-foreground text-sm px-4 py-2 rounded-[10px] hover:border-[rgba(255,255,255,0.12)] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
            >
              <Download size={14} />
              Download .ps1
            </button>
          </span>

          <span className="flex-1" title={!canGenerate ? 'Engineer access required' : undefined}>
            <button
              type="button"
              onClick={handleCopy}
              disabled={!generatedScript || !canGenerate}
              className="w-full flex items-center justify-center gap-1.5 bg-white/5 border border-border text-foreground text-sm px-4 py-2 rounded-[10px] hover:border-[rgba(255,255,255,0.12)] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
            >
              {copied ? <Check size={14} className="text-emerald-400" /> : <Copy size={14} />}
              {copied ? 'Copied!' : 'Copy'}
            </button>
          </span>
        </div>
      </div>

      {/* Generate error */}
      {generateError && (
        <p className="text-xs text-rose-500 mt-2">{generateError}</p>
      )}
    </div>
  )
}
```

- [ ] **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 (
    <div className="flex flex-col gap-4 p-6 h-full">
      {/* Page header */}
      <div>
        <h1 className="text-2xl font-heading font-bold text-foreground">Script Library</h1>
        <p className="text-sm text-muted-foreground mt-1">
          Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.
        </p>
      </div>

      {/* Two-column layout */}
      <div className="grid grid-cols-[320px_1fr] gap-4 flex-1 min-h-0">
        {/* Left pane — Browse or Configure */}
        {paneMode === 'browse' ? (
          <div className="glass-card-static flex flex-col overflow-hidden">
            <div className="p-3 border-b border-border">
              <ScriptFilterBar inputValue={inputValue} setInputValue={setInputValue} />
            </div>
            <div className="flex-1 overflow-y-auto">
              <ScriptTemplateList
                inputValue={inputValue}
                onClearSearch={onClearSearch}
                onConfigure={onConfigure}
              />
            </div>
          </div>
        ) : (
          <ScriptConfigurePane canGenerate={canGenerate} onBack={onBack} />
        )}

        {/* Right pane — always ScriptPreview */}
        {selectedTemplate === null ? (
          <div className="glass-card-static h-full flex flex-col items-center justify-center gap-3 text-center p-8">
            <Terminal size={40} className="text-muted-foreground/40" />
            <p className="text-sm text-muted-foreground">Select a template to get started</p>
          </div>
        ) : (
          <div className="glass-card-static h-full overflow-hidden p-4">
            <ScriptPreview />
          </div>
        )}
      </div>
    </div>
  )
}
```

- [ ] **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
```
