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>
9.4 KiB
9.4 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 | 02 | execute | 2 |
|
|
true |
|
|
Purpose: These are pure, independently testable pieces. Isolating them here keeps ReviewStep.tsx focused on layout and state wiring, not download mechanics. Output: src/utils/downloadFile.ts, src/utils/downloadZip.ts, src/components/wizard/OutputBlock.tsx
<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/components/wizard/ReviewStep.test.tsxExpected by ReviewStep.test.tsx:
// src/utils/downloadFile.ts
export function downloadFile(content: string, filename: string): void
// src/utils/downloadZip.ts
export async function downloadZip(
files: { name: string; content: string }[],
zipName: string
): Promise<void>
// src/components/wizard/OutputBlock.tsx
export interface OutputBlockProps {
label: string;
content: string;
filename?: string; // if provided, show download button
disabled?: boolean; // gates both copy and download (security gate)
}
export function OutputBlock(props: OutputBlockProps): JSX.Element
Create src/utils/downloadFile.ts:
```typescript
// src/utils/downloadFile.ts
// Triggers a browser file download from a string.
// Pattern: MDN URL.createObjectURL — https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static
export function downloadFile(content: string, filename: string): void {
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
```
Create src/utils/downloadZip.ts:
```typescript
// src/utils/downloadZip.ts
// Builds a ZIP archive from file entries and triggers download.
// Pattern: JSZip official docs — https://stuk.github.io/jszip/documentation/examples/download-zip-file.html
import JSZip from 'jszip';
export async function downloadZip(
files: { name: string; content: string }[],
zipName: string
): Promise<void> {
const zip = new JSZip();
for (const { name, content } of files) {
zip.file(name, content);
}
const blob = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = zipName;
a.click();
URL.revokeObjectURL(url);
}
```
Do NOT create a src/utils/index.ts barrel — ReviewStep will import directly from the specific utility files to keep the mock paths in tests predictable (the test file already uses '../../utils/downloadFile' and '../../utils/downloadZip').
```typescript
// src/components/wizard/OutputBlock.tsx
// Reusable output block: pre-formatted content + copy button + optional download button.
// disabled prop gates both actions (SECU-01 security acknowledgement gate).
import { useState } from 'react';
import { downloadFile } from '../../utils/downloadFile';
export interface OutputBlockProps {
label: string;
content: string;
filename?: string;
disabled?: boolean;
}
export function OutputBlock({ label, content, filename, disabled = false }: OutputBlockProps) {
const [copied, setCopied] = useState(false);
async function handleCopy() {
await navigator.clipboard.writeText(content);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
return (
<div className="mb-6">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-gray-700">{label}</span>
<div className="flex gap-2">
<button
type="button"
disabled={disabled}
onClick={handleCopy}
className="text-xs px-2 py-1 rounded border border-gray-300 disabled:opacity-40"
>
{copied ? 'Copied!' : 'Copy'}
</button>
{filename && (
<button
type="button"
disabled={disabled}
onClick={() => downloadFile(content, filename)}
className="text-xs px-2 py-1 rounded border border-gray-300 disabled:opacity-40"
>
Download
</button>
)}
</div>
</div>
<pre className="bg-gray-900 text-gray-100 rounded p-4 text-xs overflow-x-auto whitespace-pre-wrap">
<code>{content}</code>
</pre>
</div>
);
}
```
Styling uses Tailwind classes consistent with the existing wizard components. The `disabled:opacity-40` class communicates the locked state visually without hiding buttons.
<success_criteria>
- src/utils/downloadFile.ts exports downloadFile function
- src/utils/downloadZip.ts exports downloadZip async function
- src/components/wizard/OutputBlock.tsx exports OutputBlock and OutputBlockProps
- jszip listed in package.json dependencies
- TypeScript compilation clean </success_criteria>