Files
kawaandClaude Sonnet 4.6 405bf477b4 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>
2026-03-27 10:23:14 +01:00

273 lines
11 KiB
Markdown
Raw Permalink 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.
---
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>