docs(06-new-backends): create phase plan

4 plans across 3 waves: Wave 0 TDD stubs, Wave 1 data layer (parallel: OneDrive/GCS/B2 + SFTP/SftpAuthToggle), Wave 2 RemoteConfigStep wiring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-30 13:38:46 +02:00
co-authored by Claude Sonnet 4.6
parent e9bf449183
commit b314e65536
5 changed files with 1359 additions and 2 deletions
@@ -0,0 +1,291 @@
---
phase: 06-new-backends
plan: "03"
type: execute
wave: 2
depends_on:
- "06-01"
- "06-02"
files_modified:
- src/components/wizard/RemoteConfigStep.tsx
autonomous: false
requirements:
- BACK-01
- BACK-02
- BACK-03
- BACK-04
must_haves:
truths:
- "User can select OneDrive in BackendSelectionStep and see token, drive_id, drive_type fields in RemoteConfigStep"
- "User can select SFTP and see host, user, and the Password/Private Key toggle in RemoteConfigStep"
- "User can select GCS and see project_number and service_account_credentials fields"
- "User can select Backblaze B2 and see Application Key ID and Application Key fields"
- "All four new backends appear in BackendSelectionStep (automatic — driven by BACKEND_REGISTRY)"
- "Full Vitest suite passes with zero failures and zero TypeScript errors"
artifacts:
- path: "src/components/wizard/RemoteConfigStep.tsx"
provides: "Extended component handling all 7 backends; sftp branch with SftpAuthToggle"
contains: "SftpAuthToggle"
key_links:
- from: "src/components/wizard/RemoteConfigStep.tsx"
to: "src/components/wizard/SftpAuthToggle.tsx"
via: "sftp branch import and render"
pattern: "backendType === 'sftp'"
- from: "src/components/wizard/RemoteConfigStep.tsx"
to: "src/schemas/index.ts BACKEND_SCHEMAS"
via: "schema lookup — all 7 BackendType values must be keys in BACKEND_SCHEMAS"
pattern: "BACKEND_SCHEMAS\\[backendType\\]"
- from: "src/components/wizard/RemoteConfigStep.tsx"
to: "src/schemas/registry.ts BACKEND_REGISTRY"
via: "backendLabel record must be exhaustive over all 7 BackendType values"
pattern: "backendLabel.*onedrive|sftp|gcs|b2"
---
<objective>
Wire all four new backends into RemoteConfigStep — extend backendLabel, add the sftp branch with SftpAuthToggle, and ensure gcs/b2/onedrive render via the existing registry loop.
Purpose: The only UI wiring step. After Plans 01 and 02, the data layer is complete. This plan makes it visible and interactive in the wizard.
Output: RemoteConfigStep.tsx updated; all RemoteConfigStep tests green; full suite 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/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/06-new-backends/06-RESEARCH.md
@.planning/phases/06-new-backends/06-01-SUMMARY.md
@.planning/phases/06-new-backends/06-02-SUMMARY.md
<interfaces>
<!-- Current RemoteConfigStep.tsx — full file for reference -->
From src/components/wizard/RemoteConfigStep.tsx (current — pre-modification):
```typescript
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useWizard } from '../../store/context';
import { BACKEND_REGISTRY } from '../../schemas/registry';
import { BACKEND_SCHEMAS } from '../../schemas';
import { FieldRenderer } from '../ui/FieldRenderer';
import { AzureAuthToggle } from './AzureAuthToggle';
import type { FieldError } from 'react-hook-form';
export function RemoteConfigStep() {
const { state, dispatch } = useWizard();
const backendType = state.remote.backendType;
useEffect(() => {
if (!backendType) dispatch({ type: 'SET_STEP', payload: 0 });
}, [backendType, dispatch]);
const schema = backendType ? BACKEND_SCHEMAS[backendType] : BACKEND_SCHEMAS['azureblob'];
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
mode: 'onSubmit',
reValidateMode: 'onChange',
defaultValues: state.remote.params,
});
if (!backendType) return null;
const onNext = (values: Record<string, string>) => {
dispatch({ type: 'SET_REMOTE_PARAMS', payload: values });
dispatch({ type: 'SET_STEP', payload: 2 });
};
const backendLabel: Record<NonNullable<typeof backendType>, string> = {
azureblob: 'Azure Blob Storage',
s3: 'Amazon S3',
's3-compatible': 'S3-Compatible Storage',
};
return (
<div>
<h2>Step 2: Configure {backendLabel[backendType]}</h2>
<form onSubmit={handleSubmit(onNext)}>
{backendType === 'azureblob' ? (
<>
<FieldRenderer field={BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'account')!} register={register} error={errors.account as FieldError | undefined} />
<AzureAuthToggle register={register} errors={{ key: errors.key as FieldError | undefined, sas_url: errors.sas_url as FieldError | undefined }} />
</>
) : (
BACKEND_REGISTRY[backendType].fields.map(field => (
<FieldRenderer key={field.key} field={field} register={register} error={errors[field.key] as FieldError | undefined} />
))
)}
<div className="flex gap-3 mt-6">
<button type="button" onClick={() => dispatch({ type: 'SET_STEP', payload: 0 })} className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50">Back</button>
<button type="submit" className="px-4 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700">Next</button>
</div>
</form>
</div>
);
}
```
From src/components/wizard/SftpAuthToggle.tsx (created by Plan 02):
```typescript
export function SftpAuthToggle({ register, errors }: SftpAuthToggleProps)
// Props: { register: UseFormRegister<any>; errors: { pass?: FieldError; key_pem?: FieldError } }
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Extend RemoteConfigStep.tsx — backendLabel + sftp branch + all four new backends</name>
<files>src/components/wizard/RemoteConfigStep.tsx</files>
<behavior>
- Import SftpAuthToggle from './SftpAuthToggle'
- backendLabel record covers all 7 BackendType values: adds onedrive/'OneDrive', sftp/'SFTP', gcs/'Google Cloud Storage', b2/'Backblaze B2'
- The ternary in JSX becomes a three-branch chain:
1. backendType === 'azureblob' → existing AzureAuthToggle branch (unchanged)
2. backendType === 'sftp' → host field + user field via FieldRenderer, then SftpAuthToggle for pass/key_pem
3. all others (onedrive, gcs, b2, s3, s3-compatible) → registry loop (unchanged)
- SFTP branch renders host and user fields from BACKEND_REGISTRY.sftp.fields using FieldRenderer (same as azureblob renders account), then SftpAuthToggle
- onedrive, gcs, b2 render entirely via the registry loop — no custom branch needed
- `npx vitest run src/components/wizard/RemoteConfigStep.test.tsx` fully green for all 7 backends
- `npx vitest run` (full suite) fully green
- `npx tsc --noEmit` zero errors
</behavior>
<action>
Edit src/components/wizard/RemoteConfigStep.tsx:
1. Add import at top (after AzureAuthToggle import):
`import { SftpAuthToggle } from './SftpAuthToggle';`
2. Extend backendLabel record:
```typescript
const backendLabel: Record<NonNullable<typeof backendType>, string> = {
azureblob: 'Azure Blob Storage',
s3: 'Amazon S3',
's3-compatible': 'S3-Compatible Storage',
onedrive: 'OneDrive',
sftp: 'SFTP',
gcs: 'Google Cloud Storage',
b2: 'Backblaze B2',
};
```
3. Replace the ternary block in JSX (inside the form, before the button div):
```tsx
{backendType === 'azureblob' ? (
<>
{/* Account field via FieldRenderer */}
<FieldRenderer
field={BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'account')!}
register={register}
error={errors.account as FieldError | undefined}
/>
{/* Auth toggle handles key + sas_url — both always registered */}
<AzureAuthToggle
register={register}
errors={{
key: errors.key as FieldError | undefined,
sas_url: errors.sas_url as FieldError | undefined,
}}
/>
</>
) : backendType === 'sftp' ? (
<>
{/* host and user via FieldRenderer */}
<FieldRenderer
field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'host')!}
register={register}
error={errors.host as FieldError | undefined}
/>
<FieldRenderer
field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'user')!}
register={register}
error={errors.user as FieldError | undefined}
/>
{/* Auth toggle handles pass + key_pem — both always registered */}
<SftpAuthToggle
register={register}
errors={{
pass: errors.pass as FieldError | undefined,
key_pem: errors.key_pem as FieldError | undefined,
}}
/>
</>
) : (
/* All others (onedrive, gcs, b2, s3, s3-compatible): full registry loop */
BACKEND_REGISTRY[backendType].fields.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))
)}
```
After editing, run the full suite immediately. Confirm all 7 backend describe blocks in RemoteConfigStep.test.tsx are green.
Note on OneDrive drive_type field: BACKEND_REGISTRY.onedrive has drive_type with inputType: 'select'. FieldRenderer already handles 'select' type — no changes to FieldRenderer needed. The test asserts getByLabelText(/drive type/i) — ensure FieldRenderer renders a label matching that text.
</action>
<verify>
<automated>npx vitest run 2>&1 | tail -20</automated>
</verify>
<done>RemoteConfigStep handles all 7 backends; SFTP branch uses SftpAuthToggle; onedrive/gcs/b2 render via registry loop; full Vitest suite green; zero TypeScript errors from `npx tsc --noEmit`</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 2: Human verify all four new backends in the wizard UI</name>
<files>src/components/wizard/RemoteConfigStep.tsx</files>
<action>Run `npm run dev` and manually test each new backend through the wizard UI flow.</action>
<verify>
<automated>npx vitest run 2>&1 | tail -5</automated>
</verify>
<done>All four backends visually verified in the running app by the user</done>
<what-built>
All four new backends fully wired into the wizard:
- OneDrive: token (password field), Drive ID, Drive Type (select: Personal/Business/SharePoint)
- SFTP: Host, Username, Password/Private Key toggle (CSS-hidden, preserves values on switch)
- Google Cloud Storage: Project Number, Service Account JSON (password field)
- Backblaze B2: Application Key ID, Application Key (password field)
All four backends appear as cards in BackendSelectionStep. Automated tests are fully green.
</what-built>
<how-to-verify>
1. `npm run dev` — open http://localhost:5173
2. Verify BackendSelectionStep shows 7 backend cards including OneDrive, SFTP, Google Cloud Storage, Backblaze B2
3. Select OneDrive → confirm OAuth Token (JSON), Drive ID, Drive Type fields appear
4. Go back, select SFTP → confirm Host, Username fields + Password/Private Key tab buttons
- Type a value in Password field, switch to Private Key, switch back — value must still be there
5. Go back, select GCS → confirm Project Number and Service Account JSON fields appear
6. Go back, select B2 → confirm Application Key ID and Application Key fields appear
7. For any backend: fill all required fields, click Next — wizard must advance without errors
</how-to-verify>
<resume-signal>Type "approved" to complete Phase 6, or describe any visual issues found</resume-signal>
</task>
</tasks>
<verification>
`npx vitest run` — all tests green including all 4 new backend describe blocks in RemoteConfigStep.test.tsx
`npx tsc --noEmit` — zero TypeScript errors
Manual: all 4 new backends visible and functional in the wizard UI
</verification>
<success_criteria>
- RemoteConfigStep handles all 7 BackendType values without TypeScript errors
- SFTP branch uses SftpAuthToggle with CSS-hidden pattern
- onedrive, gcs, b2 render entirely via the registry loop
- All 4 new backends visible in BackendSelectionStep UI (automatic via registry)
- Full Vitest suite green
- Zero TypeScript errors
- Human verification: all backends visually correct and functional
</success_criteria>
<output>
After completion, create `.planning/phases/06-new-backends/06-03-SUMMARY.md`
</output>