Files
resolutionflow/docs/superpowers/specs/2026-03-13-script-library-pane-takeover-design.md
chihlasm d4dbf44781 feat: Script Generator Phase 1+2 — backend, engine, API, frontend, template editor, parameter detector
Complete Script Generator feature including:

Backend:
- ScriptCategory, ScriptTemplate, ScriptGeneration models
- ScriptTemplateEngine with substitution, filters, sanitization
- CRUD + share API endpoints with permission checks
- Integration tests for permissions and sharing
- Migration 057 with AD User Management seed templates

Frontend — Script Library:
- Browse templates with category tabs and search
- Configure pane with parameter form and script generation
- Script preview with live substitution and copy/download
- scriptGeneratorStore Zustand store

Frontend — Template Editor:
- Full CRUD form with metadata, script body (Monaco Editor), parameters
- ParameterSchemaBuilder with visual builder + JSON toggle
- ScriptManagePage with routing and nav link

Frontend — Parameter Detector:
- Client-side PowerShell parameter detection engine
- Detects script-level param() blocks and variable assignments
- Type inference from PS type annotations and value patterns
- ParameterDetectorStepper one-by-one review UI with accept/skip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 20:18:59 -04:00

240 lines
15 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.
# Script Library — Left Pane Takeover Design Spec
> **Created:** 2026-03-13
> **Feature:** Redesign Script Library left pane to have two distinct modes: Browse and Configure
---
## Overview
The Script Library left pane gains two distinct modes. In **Browse mode** the user sees the full template list with filter bar and a "Configure →" button on each card. Clicking "Configure →" transitions the entire left pane (including the filter bar) to **Configure mode**, which replaces the list with a full-height view of the selected template's header, parameter form, and action buttons (Generate, Download, Copy). The right pane becomes a **read-only preview** (`ScriptPreview` only — no form or buttons). "Back to library" returns to Browse mode with filter/search state preserved.
---
## Structural Change Summary
This is a cross-column relocation of the Generate/Download/Copy controls:
| Before | After |
|--------|-------|
| Left pane: template list + filter bar | Left pane: Browse mode (list + filter) OR Configure mode (form + actions) |
| Right pane: param form + action buttons + preview | Right pane: `ScriptPreview` only (read-only display) |
`ScriptGeneratorPanel` (which currently owns the right column's form, actions, and preview) is **deleted**. The right pane becomes `ScriptPreview` in isolation. The new `ScriptConfigurePane` component owns the left pane in Configure mode and contains the form + all action buttons.
---
## Goals
- Make template selection intentional (no accidental click-to-configure)
- Give the parameter form more vertical space by using the full left pane height
- Keep the output preview always visible on the right
- Preserve filter/search state across Browse ↔ Configure transitions
---
## Non-Goals
- No changes to `ScriptPreview` internals — it moves to the right pane as-is, including its existing copy overlay button
- No changes to the Zustand store shape or actions
- No changes to the filter/search debounce logic
- No changes to routing
---
## New Work (not pre-existing)
The **Copy button in the action bar** is new — it does not exist in the current `ScriptGeneratorPanel`. It copies `generatedScript` from the store. It is **disabled** when `generatedScript === null` (i.e., before the user has clicked Generate). The draft preview's copy needs are handled by `ScriptPreview`'s existing overlay copy button. No draft-substitution logic needs to be duplicated in `ScriptConfigurePane`.
---
## Left Pane — Two Modes
### Browse Mode
Rendered when `paneMode === 'browse'`.
The page header (`h1` "Script Library" + subtitle) stays at the top of `ScriptLibraryPage` above the two-column grid, visible in both pane modes — it is not affected by this change.
The current `ScriptLibraryPage` renders `ScriptFilterBar` at the page level above the two-column grid. In this redesign, the filter bar moves **inside the left pane column** so it can be hidden in Configure mode. `inputValue`/`setInputValue` remain owned by `ScriptLibraryPage` (not inside the left pane sub-tree) so the search text survives the unmount when the pane switches to Configure mode.
Layout (top to bottom, fills left pane height):
1. **Filter bar** (`ScriptFilterBar`) — category pills + search input
2. **Template list** (`ScriptTemplateList`) — scrollable, fills remaining height
**TemplateCard changes:**
- Root element changes from `<button>` to `<div>` — the card itself is no longer clickable
- Remove active/selected visual state (`bg-primary/10 border-l-[3px]` etc.) — no card is "selected" in browse mode
- Add **"Configure →"** button, right-aligned in the bottom row
- Style: `bg-primary/10 border border-primary/20 text-primary text-xs px-2.5 py-1 rounded-md hover:bg-primary/20 transition-colors`
- Uses unicode `→` (not a Lucide icon)
- On click: calls `onConfigure(template.id)` prop
- The rest of the card (name, description, tags, complexity badge) is non-interactive
**Updated `TemplateCard` props:**
```tsx
interface Props {
template: ScriptTemplateListItem
onConfigure: (id: string) => void
}
```
`TemplateCard` also removes its `useScriptGeneratorStore` import entirely — it no longer reads `selectedTemplate` or `selectTemplate` from the store.
### Configure Mode
Rendered when `paneMode === 'configure'`.
The entire left pane (including filter bar) is replaced by the Configure view.
Layout (top to bottom, full pane height, `overflow-y-auto`):
1. **Back button**
- Label: `← Back to library`
- Style: `flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-4`
- On click: calls `onBack` prop → `ScriptLibraryPage` calls `store.clearOutput()` then `setPaneMode('browse')`
- Does NOT clear `selectedTemplate` in the store (no such action exists — `selectTemplate` only accepts a string ID). `selectedTemplate` remains set in the store after returning to Browse mode. The right pane (`ScriptPreview`) continues showing the last preview while browsing — this is intentional.
2. **Template header** (visible when `isLoadingDetail === false`)
- Name: `text-base font-semibold font-heading text-foreground`
- Description (if present): `text-sm text-muted-foreground mt-0.5`
- Tag row (left-to-right): `ShieldAlert` icon if `requires_elevation`, complexity badge, category name (resolved from `store.categories.find(c => c.id === selectedTemplate.category_id)?.name`), template tags (first 3, overflow as `+N`) — same badge/tag styles as current `TemplateCard`. If no matching category is found, omit the category name chip.
- Separator: `border-t border-border mt-3 pt-3`
3. **`ScriptParameterForm`** — existing component, `canGenerate` prop unchanged
4. **Warnings callout** — shown above Generate when `generationWarnings.length > 0` (same amber box as current `ScriptGeneratorPanel`)
5. **Action bar**
- **Generate** button: full-width, `bg-gradient-brand`, disabled/loading behavior same as current
- **Download .ps1** + **Copy** buttons: side by side below Generate, each `flex-1`
- The Copy button copies `store.generatedScript`. It is disabled when `generatedScript === null`. Draft copy is handled by `ScriptPreview`'s existing overlay — two copy entry-points is acceptable.
- Error text below if `generateError` is set
**Loading state:** When `isLoadingDetail === true`, shows a centered `<Loader2 size={28} className="text-primary animate-spin" />` filling the pane instead of the template content.
---
## Right Pane
After the redesign, the right pane contains **only `ScriptPreview`**, wrapped in a `glass-card-static h-full` container.
Two sub-states:
- **No template ever selected** (`selectedTemplate === null`): show empty state — Terminal icon + "Select a template to get started" text. **Important:** `ScriptPreview` returns `null` when `selectedTemplate` is null, so the empty state must be rendered by the right-pane wrapper in `ScriptLibraryPage`, not by `ScriptPreview` itself. Pattern:
```tsx
{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>
)}
```
- **Template selected** (in either pane mode): show `ScriptPreview`
The right pane is always read-only — no form, no Generate/Download buttons.
**Layout note:** `ScriptPreview` renders a `<div className="relative">` at its root; the copy overlay button is `position: absolute, top-3, right-3` inside it. The right-pane wrapper must **not** use `overflow-y-auto` — if it did, the absolute copy button would scroll out of view on long scripts. Instead, the wrapper is `overflow-hidden` and `ScriptPreview`'s inner `<pre>` (inside `PowerShellHighlighter`) provides its own `overflow-x-auto` for wide scripts. The right pane itself does not need to scroll vertically — `PowerShellHighlighter` already handles horizontal overflow. No changes to `ScriptPreview` are needed.
The correct right-pane wrapper pattern:
```tsx
<div className="glass-card-static h-full overflow-hidden p-4">
<ScriptPreview />
</div>
```
**Right pane during initial detail load:** When the user clicks "Configure →" for the first time (`selectedTemplate === null`, `isLoadingDetail === true`), the right pane still shows the empty state (Terminal icon). The left pane's configure view shows the loading spinner. This is acceptable — the right pane updating when `isLoadingDetail` resolves is sufficient. No right-pane loading state is needed.
---
## State — Pane Mode
Pane mode is **local React state** in `ScriptLibraryPage`, not the Zustand store.
```tsx
const [paneMode, setPaneMode] = useState<'browse' | 'configure'>('browse')
```
Transitions:
- `'browse' → 'configure'`: `onConfigure(id)` — calls `store.selectTemplate(id)` then `setPaneMode('configure')`
- `'configure' → 'browse'`: `onBack()` — calls `store.clearOutput()` then `setPaneMode('browse')`
**`isLoadingDetail` drives configure pane content, not pane mode.** When the user clicks "Configure →":
1. `selectTemplate(id)` is called (sets `isLoadingDetail: true` in store)
2. `setPaneMode('configure')` is called immediately — user sees the loading spinner in configure mode
**`selectTemplate` failure case:** If `selectTemplate` throws (network error), the store resets `isLoadingDetail: false` but leaves `selectedTemplate` unchanged. The pane mode remains `'configure'`.
- **First-selection failure** (`selectedTemplate === null` before the call): `ScriptConfigurePane` must handle `!isLoadingDetail && !selectedTemplate` — render an error state: "Failed to load template." with a "← Back to library" link.
- **Subsequent-selection failure** (`selectedTemplate` is still set from the previous template): the configure pane silently shows the previous template's form with stale data. This is an accepted edge case — network errors mid-session are rare and the user can press Back to recover. No special handling required.
**`paramValues` and `formErrors` on Back:** `clearOutput()` does not reset `paramValues` or `formErrors`. This is intentional — if the user returns to browse mode and then clicks "Configure →" on the same template again, `selectTemplate` will repopulate `paramValues` from defaults, discarding any edits. If they configure a different template, `selectTemplate` again repopulates from that template's defaults. There is no scenario where stale param values from a prior template persist into a new template's form. No additional cleanup is needed on Back.
**Filter/search preservation:** `inputValue` remains owned by `ScriptLibraryPage` at the page level. The filter bar unmounts in configure mode and remounts in browse mode with the same `inputValue`. Store's `activeCategoryId` and `searchQuery` are never cleared by pane transitions.
---
## Component Changes
| File | Change |
|------|--------|
| `frontend/src/pages/ScriptLibraryPage.tsx` | Add `paneMode` state; add `usePermissions` import for `canGenerate`; move `ScriptFilterBar` into left pane column; add `onConfigure`/`onBack` callbacks; render `ScriptConfigurePane` or browse content in left pane conditionally; render right pane as `ScriptPreview`-only with empty state |
| `frontend/src/components/scripts/TemplateCard.tsx` | Root `<button>` → `<div>`; remove `onClick`/active-state; add `onConfigure: (id: string) => void` prop; add "Configure →" button |
| `frontend/src/components/scripts/ScriptTemplateList.tsx` | Accept `onConfigure: (id: string) => void` prop; pass to each `TemplateCard`. `inputValue: string` and `onClearSearch: () => void` props are unchanged. |
| `frontend/src/components/scripts/ScriptConfigurePane.tsx` | **New** — configure mode layout (back button, template header, `ScriptParameterForm`, warnings, action bar with Generate + Download + Copy) |
| `frontend/src/components/scripts/ScriptGeneratorPanel.tsx` | **Delete** — superseded by `ScriptConfigurePane` and right-pane simplification |
No store changes. No API changes. No routing changes.
---
## Visual Spec
### Page layout (Configure mode active)
```
┌─ left pane (320px) ────────────┬─ right pane (1fr) ──────────────────┐
│ ← Back to library │ │
│ │ [ScriptPreview — always visible] │
│ Restart Windows Service │ │
│ Stops and restarts a service │ # Restart Windows Service │
│ 🛡 [Beginner] [Services] [win] │ param( │
│ ───────────────────────────── │ $ServiceName = "{{service_name}}" │
│ Service Name * │ ) [copy overlay] │
│ [________________] │ │
│ Target Computer │ │
│ [localhost______] │ │
│ Verify after restart [✓] │ │
│ │ │
│ [ Generate Script ] │ │
│ [ Download .ps1 ] [ Copy ] │ │
└────────────────────────────────┴──────────────────────────────────────┘
```
### TemplateCard — Browse mode
```
┌──────────────────────────────────────────────────────┐
│ Restart Windows Service 🛡 [Beginner] │
│ Stops and restarts a named service │
│ 4× used [services] [windows] +1 [Configure →] │
└──────────────────────────────────────────────────────┘
```
### `ScriptConfigurePane` props
```tsx
interface Props {
canGenerate: boolean
onBack: () => void
}
```
All other data read from Zustand store directly. `ScriptConfigurePane` derives `canGenerate` from props only — it does NOT call `usePermissions` internally. `ScriptLibraryPage` is the sole caller of `usePermissions` for this feature.
**Download filename:** `${selectedTemplate.slug}.ps1` — consistent with the current `ScriptGeneratorPanel` behavior.