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>
11 KiB
11 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-review-download-security | 03 | tdd | 3 |
|
|
true |
|
|
Purpose: This is the primary deliverable of Phase 4. All eleven requirement behaviors (CONF-02, CONF-03, DOWN-01–DOWN-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.
<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>
@.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.tsxFrom src/store/context.tsx:
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):
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:
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:
export async function downloadZip(
files: { name: string; content: string }[],
zipName: string
): Promise<void>
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).
<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>