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

15 KiB
Raw Blame History

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:

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:
    {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:

<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.

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

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.