docs(04-review-download-security): create phase 4 plan

5 plans across 5 waves: Wave 0 TDD stubs, utilities + OutputBlock,
ReviewStep implementation, App.tsx wiring, human verify checkpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-27 10:23:14 +01:00
co-authored by Claude Sonnet 4.6
parent 8d5f7090e2
commit 405bf477b4
6 changed files with 976 additions and 2 deletions
@@ -0,0 +1,272 @@
---
phase: 04-review-download-security
plan: 03
type: tdd
wave: 3
depends_on: [04-02]
files_modified:
- src/components/wizard/ReviewStep.tsx
autonomous: true
requirements: [CONF-02, CONF-03, DOWN-01, DOWN-02, DOWN-03, DOWN-04, DOWN-05, DOWN-06, SECU-01, SECU-02]
must_haves:
truths:
- "ReviewStep renders a live preview of the generated rclone.conf content"
- "A security warning checkbox must be checked before any download or copy button is enabled"
- "A 'no data sent to server' notice is visible in the ReviewStep render output"
- "OutputBlocks for all four generated files are shown (rclone.conf, intune-install.ps1, intune-detection.ps1, rmm-script.ps1)"
- "A 'Download ZIP' button calls downloadZip with all 4 file entries"
- "All test stubs from Plan 01 pass GREEN after this plan"
artifacts:
- path: "src/components/wizard/ReviewStep.tsx"
provides: "Step 3 component — live preview + security gate + all download/copy actions"
exports: ["ReviewStep"]
min_lines: 60
key_links:
- from: "src/components/wizard/ReviewStep.tsx"
to: "src/generators/index.ts"
via: "import { buildRcloneConf, buildIntuneInstall, buildIntuneDetection, buildRmmScript }"
pattern: "buildRcloneConf"
- from: "src/components/wizard/ReviewStep.tsx"
to: "src/store/context.tsx"
via: "import { useWizard }"
pattern: "useWizard"
- from: "src/components/wizard/ReviewStep.tsx"
to: "src/components/wizard/OutputBlock.tsx"
via: "import { OutputBlock }"
pattern: "OutputBlock"
- from: "src/components/wizard/ReviewStep.tsx"
to: "src/utils/downloadZip.ts"
via: "import { downloadZip }"
pattern: "downloadZip"
---
<objective>
Implement ReviewStep.tsx: the full Phase 4 wizard step that composes all download, copy, preview, and security behaviors. This plan turns every RED stub from Plan 01 GREEN.
Purpose: This is the primary deliverable of Phase 4. All eleven requirement behaviors (CONF-02, CONF-03, DOWN-01DOWN-06, SECU-01, SECU-02) are implemented in this single component, composed from the utilities and OutputBlock built in Plan 02.
Output: src/components/wizard/ReviewStep.tsx — fully implemented, all tests GREEN.
</objective>
<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/phases/04-review-download-security/04-RESEARCH.md
@src/store/types.ts
@src/store/context.tsx
@src/generators/index.ts
@src/components/wizard/OutputBlock.tsx
@src/utils/downloadFile.ts
@src/utils/downloadZip.ts
@src/components/wizard/ReviewStep.test.tsx
</context>
<interfaces>
<!-- Contracts the executor must satisfy — read the test file for exact assertion details -->
From src/store/context.tsx:
```typescript
const { state } = useWizard();
// state.remote.backendType: BackendType | null
// state.remote.name: string
// state.deployment: { includeInstall, configPath, scriptTargets }
```
From src/generators/index.ts (all accept WizardState, return string, buildRcloneConf throws on incomplete state):
```typescript
export function buildRcloneConf(state: WizardState): string
export function buildIntuneInstall(state: WizardState): string
export function buildIntuneDetection(state: WizardState): string
export function buildRmmScript(state: WizardState): string
```
From src/components/wizard/OutputBlock.tsx:
```typescript
export interface OutputBlockProps {
label: string;
content: string;
filename?: string; // when provided, shows Download button
disabled?: boolean; // SECU-01 gate
}
export function OutputBlock(props: OutputBlockProps): JSX.Element
```
From src/utils/downloadZip.ts:
```typescript
export async function downloadZip(
files: { name: string; content: string }[],
zipName: string
): Promise<void>
```
</interfaces>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Implement ReviewStep.tsx — full component turning all stubs GREEN</name>
<files>src/components/wizard/ReviewStep.tsx</files>
<behavior>
- CONF-02: rcloneConfContent computed via useMemo calling buildRcloneConf(state); wrapped in try/catch returning placeholder on error
- CONF-03: rclone.conf OutputBlock has no filename (copy-only for live preview area); separate rclone.conf OutputBlock with filename='rclone.conf' for download
- DOWN-01: OutputBlock with filename='rclone.conf' calls downloadFile(content, 'rclone.conf') on Download click
- DOWN-02: OutputBlock with filename='intune-install.ps1' calls downloadFile on click
- DOWN-03: OutputBlock with filename='intune-detection.ps1' calls downloadFile on click
- DOWN-04: OutputBlock with filename='rmm-script.ps1' calls downloadFile on click
- DOWN-05: "Download All (ZIP)" button calls downloadZip([...4 files...], 'rclone-deployment.zip')
- DOWN-06: every OutputBlock's Copy button calls clipboard.writeText with that block's content
- SECU-01: acknowledged useState boolean controls disabled prop on all OutputBlocks and Download ZIP button
- SECU-02: a visible notice "No data is sent to any server" (or similar) is present in the render
</behavior>
<action>
Create src/components/wizard/ReviewStep.tsx. Follow the TDD cycle:
RED: Run `npm test -- src/components/wizard/ReviewStep.test.tsx` first to confirm all stubs fail.
GREEN: Implement the component, run tests after each significant block.
Implementation structure:
```typescript
// src/components/wizard/ReviewStep.tsx
import { useMemo, useState } from 'react';
import { useWizard } from '../../store/context';
import {
buildRcloneConf,
buildIntuneInstall,
buildIntuneDetection,
buildRmmScript,
} from '../../generators/index';
import { OutputBlock } from './OutputBlock';
import { downloadZip } from '../../utils/downloadZip';
const PLACEHOLDER = '# Fill in the wizard steps to generate your rclone.conf';
export function ReviewStep() {
const { state } = useWizard();
const [acknowledged, setAcknowledged] = useState(false);
// CONF-02: live preview — updates on every state change
const rcloneConf = useMemo(() => {
try { return buildRcloneConf(state); } catch { return PLACEHOLDER; }
}, [state]);
const intuneInstall = useMemo(() => {
try { return buildIntuneInstall(state); } catch { return ''; }
}, [state]);
const intuneDetection = useMemo(() => {
try { return buildIntuneDetection(state); } catch { return ''; }
}, [state]);
const rmmScript = useMemo(() => {
try { return buildRmmScript(state); } catch { return ''; }
}, [state]);
// DOWN-05: ZIP bundle — always include all 4 files
async function handleDownloadZip() {
await downloadZip(
[
{ name: 'rclone.conf', content: rcloneConf },
{ name: 'intune-install.ps1', content: intuneInstall },
{ name: 'intune-detection.ps1', content: intuneDetection },
{ name: 'rmm-script.ps1', content: rmmScript },
],
'rclone-deployment.zip'
);
}
return (
<div>
{/* SECU-02: client-side only notice */}
<p className="text-sm text-green-700 bg-green-50 rounded p-3 mb-6">
No data is sent to any server. All file generation happens in your browser.
</p>
{/* SECU-01: security acknowledgement gate */}
<div className="bg-yellow-50 border border-yellow-300 rounded p-4 mb-6">
<p className="text-sm text-yellow-800 font-medium mb-2">
Security warning: generated files contain credentials in plain text.
</p>
<label className="flex items-center gap-2 text-sm text-yellow-900 cursor-pointer">
<input
type="checkbox"
checked={acknowledged}
onChange={(e) => setAcknowledged(e.target.checked)}
/>
I understand that the generated files contain credentials in plain text.
</label>
</div>
{/* Output blocks — DOWN-01 through DOWN-04, CONF-03, DOWN-06 */}
<OutputBlock
label="rclone.conf"
content={rcloneConf}
filename="rclone.conf"
disabled={!acknowledged}
/>
<OutputBlock
label="Intune Install Script"
content={intuneInstall}
filename="intune-install.ps1"
disabled={!acknowledged}
/>
<OutputBlock
label="Intune Detection Script"
content={intuneDetection}
filename="intune-detection.ps1"
disabled={!acknowledged}
/>
<OutputBlock
label="RMM Script"
content={rmmScript}
filename="rmm-script.ps1"
disabled={!acknowledged}
/>
{/* DOWN-05: ZIP bundle */}
<button
type="button"
disabled={!acknowledged}
onClick={handleDownloadZip}
className="w-full py-2 px-4 bg-blue-600 text-white rounded font-medium disabled:opacity-40 disabled:cursor-not-allowed hover:bg-blue-700"
>
Download All (ZIP)
</button>
</div>
);
}
```
After writing the file, run tests and fix any assertion mismatches:
- If a test expects a specific button text, match it exactly
- If a test queries by role or label, ensure the rendered output has matching accessible text
- Update test stubs to real assertions as you implement (replace `expect.fail('not yet implemented')` with actual assertions matching the rendered output)
PITFALL — buildRcloneConf throws on incomplete state: Always use try/catch in useMemo. Show PLACEHOLDER string when thrown. This prevents the component from crashing during navigation with partial state.
PITFALL — useMemo dependency: Pass `[state]` (the full state object) as the dependency array. WizardContext re-renders on every dispatch, so this will update on every form change (satisfies CONF-02).
</action>
<verify>
<automated>npm test -- src/components/wizard/ReviewStep.test.tsx 2>&1 | tail -20</automated>
</verify>
<done>All test stubs in ReviewStep.test.tsx are replaced with real assertions and pass GREEN. npm test shows 0 failing tests for this file. All pre-existing tests in other files remain GREEN.</done>
</task>
</tasks>
<verification>
`npm test` full suite passes GREEN. ReviewStep.test.tsx: all CONF-02, CONF-03, DOWN-01 through DOWN-06, SECU-01, SECU-02 assertions pass. reducer.test.ts SECU-03 passes. All Phase 13 tests still GREEN.
</verification>
<success_criteria>
- src/components/wizard/ReviewStep.tsx exists and exports ReviewStep
- All 10 requirement behaviors tested in ReviewStep.test.tsx pass GREEN
- No TypeScript errors in the component
- Full test suite green
</success_criteria>
<output>
After completion, create `.planning/phases/04-review-download-security/04-03-SUMMARY.md`
</output>