Files
kawaandClaude Sonnet 4.6 b314e65536 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>
2026-03-30 13:38:46 +02:00

12 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
06-new-backends 02 execute 1
06-00
src/schemas/registry.ts
src/schemas/index.ts
src/generators/rclone-conf.ts
src/components/wizard/SftpAuthToggle.tsx
true
BACK-02
truths artifacts key_links
BACKEND_REGISTRY has sftp entry with host (required), user (required), pass (optional), key_pem (optional)
SftpAuthToggle renders a Password tab and Private Key tab; inactive tab's field is CSS-hidden (not unmounted)
Switching SFTP auth tabs does not clear the hidden field value (CSS-hidden, not conditional render)
buildRcloneConf(sftpPasswordState) outputs 'type = sftp' with pass, omits key_pem
buildRcloneConf(sftpKeyState) outputs 'type = sftp' with key_pem, omits pass
path provides contains
src/schemas/registry.ts sftp entry in BACKEND_REGISTRY key_pem
path provides exports
src/components/wizard/SftpAuthToggle.tsx SFTP password vs private-key toggle following AzureAuthToggle CSS-hidden pattern
SftpAuthToggle
path provides contains
src/generators/rclone-conf.ts RCLONE_TYPE_MAP sftp entry sftp.*sftp
from to via pattern
src/components/wizard/SftpAuthToggle.tsx src/components/ui/PasswordField.tsx PasswordField component for both pass and key_pem fields PasswordField.*pass|PasswordField.*key_pem
from to via pattern
src/schemas/registry.ts BACKEND_REGISTRY sftp entry pass and key_pem fields required: false — SftpAuthToggle registers them directly key_pem.*required.*false
Add SFTP to the data layer and create the SftpAuthToggle component.

Purpose: SFTP is the only new backend requiring a custom auth-method toggle (password vs private key). Isolating SFTP work in its own plan keeps Plan 01 clean and allows parallel execution. Output: sftp entry in registry.ts/index.ts/rclone-conf.ts; SftpAuthToggle.tsx component following the AzureAuthToggle CSS-hidden pattern.

<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/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-new-backends/06-RESEARCH.md @.planning/phases/06-new-backends/06-00-SUMMARY.md

From src/components/wizard/AzureAuthToggle.tsx:

import { useState } from 'react';
import type { UseFormRegister, FieldError } from 'react-hook-form';
import { PasswordField } from '../ui/PasswordField';

type AuthMethod = 'sas' | 'key';

interface AzureAuthToggleProps {
  register: UseFormRegister<any>;
  errors: { key?: FieldError; sas_url?: FieldError; };
}

export function AzureAuthToggle({ register, errors }: AzureAuthToggleProps) {
  const [authMethod, setAuthMethod] = useState<AuthMethod>('sas');
  return (
    <div className="flex flex-col gap-3">
      <div className="flex rounded-md border border-gray-300 overflow-hidden">
        <button type="button" onClick={() => setAuthMethod('sas')} className={[
          'flex-1 py-1.5 text-sm font-medium transition-colors',
          authMethod === 'sas' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 hover:bg-gray-50',
        ].join(' ')}>SAS URL</button>
        <button type="button" onClick={() => setAuthMethod('key')} className={...}>Access Key</button>
      </div>
      {/* Both fields always registered — CSS toggling only */}
      <div className={authMethod === 'sas' ? 'block' : 'hidden'}>
        <PasswordField id="sas_url" label="SAS URL" error={errors.sas_url} registration={register('sas_url')} ... />
      </div>
      <div className={authMethod === 'key' ? 'block' : 'hidden'}>
        <PasswordField id="key" label="Access Key" error={errors.key} registration={register('key')} ... />
      </div>
    </div>
  );
}

From src/schemas/registry.ts (current sftp fields — to add):

// sftp registry entry (add to BACKEND_REGISTRY):
sftp: {
  displayName: 'SFTP',
  description: 'SSH File Transfer Protocol',
  fields: [
    { key: 'host',    label: 'Host',     inputType: 'text',     required: true,  placeholder: 'sftp.example.com', helpText: 'The SSH server hostname or IP address' },
    { key: 'user',    label: 'Username', inputType: 'text',     required: true,  placeholder: 'admin' },
    { key: 'pass',    label: 'Password', inputType: 'password', required: false, helpText: 'SFTP password. Note: rclone may require the password to be obscured using `rclone obscure <password>`. If authentication fails, use the obscured value.' },
    { key: 'key_pem', label: 'Private Key (PEM)', inputType: 'password', required: false, helpText: 'Paste your private key in PEM format (-----BEGIN ... PRIVATE KEY-----)' },
  ],
},
Task 1: Add sftp entry to registry.ts, index.ts, and rclone-conf.ts src/schemas/registry.ts, src/schemas/index.ts, src/generators/rclone-conf.ts - BACKEND_REGISTRY gains sftp entry with host (text, required), user (text, required), pass (password, optional), key_pem (password, optional) - pass helpText mentions rclone obscure requirement (security note, does not block generation) - BACKEND_SCHEMAS gains sftp entry via buildZodSchema('sftp') - RCLONE_TYPE_MAP gains sftp → 'sftp' - `npx vitest run src/schemas/registry.test.ts` passes all sftp assertions (host, user, pass, key_pem) - `npx vitest run src/generators/rclone-conf.test.ts` passes sftp password and sftp key describe blocks - `npx tsc --noEmit` is clean (no more "sftp missing from BACKEND_REGISTRY" error) **src/schemas/registry.ts:** Add sftp entry to BACKEND_REGISTRY (after 's3-compatible', before or after the entries added by Plan 01 — order does not matter):
```typescript
sftp: {
  displayName: 'SFTP',
  description: 'SSH File Transfer Protocol',
  fields: [
    {
      key: 'host',
      label: 'Host',
      inputType: 'text',
      required: true,
      placeholder: 'sftp.example.com',
      helpText: 'The SSH server hostname or IP address',
    },
    {
      key: 'user',
      label: 'Username',
      inputType: 'text',
      required: true,
      placeholder: 'admin',
    },
    {
      key: 'pass',
      label: 'Password',
      inputType: 'password',
      required: false,
      helpText: 'SFTP password. Note: rclone may require the password to be obscured using `rclone obscure <password>`. If authentication fails, use the obscured value instead of the plain password.',
    },
    {
      key: 'key_pem',
      label: 'Private Key (PEM)',
      inputType: 'password',
      required: false,
      helpText: 'Paste your private key in PEM format (-----BEGIN ... PRIVATE KEY-----)',
    },
  ],
},
```

**src/schemas/index.ts:** Add sftp to BACKEND_SCHEMAS:
```typescript
sftp: buildZodSchema('sftp'),
```

**src/generators/rclone-conf.ts:** Add sftp to RCLONE_TYPE_MAP:
```typescript
sftp: 'sftp',
```

Note: BackendType union already includes 'sftp' (added by Plan 01 Task 1). This plan only adds the registry/schema/map entries. If Plan 01 and Plan 02 run in parallel on separate branches, merge Plan 01 first then add sftp entries on top.
npx vitest run src/schemas/registry.test.ts src/generators/rclone-conf.test.ts 2>&1 | tail -20 sftp entry in registry with all four field keys; sftp in BACKEND_SCHEMAS and RCLONE_TYPE_MAP; registry and rclone-conf tests fully green for all 7 backends; zero TypeScript errors Task 2: Create SftpAuthToggle.tsx src/components/wizard/SftpAuthToggle.tsx - Component renders a segmented control with two buttons: "Password" (left) and "Private Key" (right) - Default auth method is 'password' — Password tab is active on first render - Password tab active: pass field wrapper has class 'block', key_pem field wrapper has class 'hidden' - Private Key tab active: pass field wrapper has class 'hidden', key_pem field wrapper has class 'block' - Both fields are ALWAYS registered with react-hook-form (CSS-hidden, not conditional render) - Pass field: id="pass", label="Password", uses PasswordField - Key field: id="key_pem", label="Private Key (PEM)", uses PasswordField with multiline hint in helpText - Active button has class 'bg-blue-600 text-white', inactive has 'bg-white text-gray-700 hover:bg-gray-50' - Component accepts register and errors props matching SftpAuthToggleProps interface Create src/components/wizard/SftpAuthToggle.tsx following AzureAuthToggle.tsx exactly, with these substitutions: - AuthMethod type: 'password' | 'key' (was 'sas' | 'key') - Initial state: 'password' (was 'sas') - Tab 1 button text: "Password" (was "SAS URL"), onClick: setAuthMethod('password') - Tab 2 button text: "Private Key" (was "Access Key"), onClick: setAuthMethod('key') - Active condition for tab 1: authMethod === 'password' - Active condition for tab 2: authMethod === 'key' - Field 1 wrapper: className={authMethod === 'password' ? 'block' : 'hidden'} PasswordField: id="pass", label="Password", error={errors.pass}, registration={register('pass')}, helpText="SFTP password. Note: rclone may require the password to be obscured using `rclone obscure `. If authentication fails, use the obscured value." - Field 2 wrapper: className={authMethod === 'key' ? 'block' : 'hidden'} PasswordField: id="key_pem", label="Private Key (PEM)", error={errors.key_pem}, registration={register('key_pem')}, helpText="Paste your private key in PEM format (-----BEGIN ... PRIVATE KEY-----)"
Props interface:
```typescript
interface SftpAuthToggleProps {
  register: UseFormRegister<any>;
  errors: {
    pass?: FieldError;
    key_pem?: FieldError;
  };
}
```

The SFTP toggle test in RemoteConfigStep.test.tsx asserts:
- getByText(/^password$/i, { selector: 'button' }) — button text must be exactly "Password"
- getByText(/private key/i, { selector: 'button' }) — button text must contain "Private Key"
- getByLabelText(/^password$/i) — PasswordField label must be exactly "Password"
- getByLabelText(/private key.*pem/i) — PasswordField label must match "Private Key (PEM)"
Ensure button text and label text match these patterns exactly.
npx vitest run src/components/wizard/RemoteConfigStep.test.tsx 2>&1 | grep -E "(BACK-02|SFTP|PASS|FAIL)" | head -20 SftpAuthToggle.tsx created; component matches AzureAuthToggle pattern; SFTP toggle tests in RemoteConfigStep.test.tsx will pass after Plan 03 wires it into RemoteConfigStep (component exists and is importable) `npx vitest run src/schemas/registry.test.ts src/generators/rclone-conf.test.ts` Expected: fully green — all 7 backends pass in both files.

npx tsc --noEmit Expected: zero errors (sftp now present in BACKEND_REGISTRY satisfying the exhaustive Record type).

<success_criteria>

  • sftp registry entry with host, user, pass, key_pem (correct rclone key names)
  • SftpAuthToggle.tsx exists, exports SftpAuthToggle, follows AzureAuthToggle CSS-hidden pattern
  • sftp in BACKEND_SCHEMAS and RCLONE_TYPE_MAP
  • registry and rclone-conf tests fully green for all 7 backends
  • Zero TypeScript errors </success_criteria>
After completion, create `.planning/phases/06-new-backends/06-02-SUMMARY.md`