Files
Ready2Blob/.planning/phases/06-new-backends/06-RESEARCH.md
T
2026-03-30 13:28:42 +02:00

31 KiB

Phase 6: New Backends - Research

Researched: 2026-03-30 Domain: rclone backend registry extension — OneDrive, SFTP, GCS, Backblaze B2 Confidence: HIGH

<phase_requirements>

Phase Requirements

ID Description Research Support
BACK-01 User can configure an OneDrive remote (with OAuth token paste input and guidance) OneDrive rclone type = onedrive; token field is a JSON blob; drive_id and drive_type required; SFTP-style toggle not needed (single auth path: paste token)
BACK-02 User can configure an SFTP remote (host, user, password or key-based auth) SFTP rclone type = sftp; auth method toggle needed (password vs key); CSS-hidden toggle follows AzureAuthToggle precedent; key field is key_pem for inline paste
BACK-03 User can configure a Google Cloud Storage remote GCS rclone type = google cloud storage (with spaces); service_account_credentials is the correct unattended-paste field (JSON blob); project_number needed for bucket listing
BACK-04 User can configure a Backblaze B2 remote B2 rclone type = b2; fields are account (applicationKeyId) and key (application key); simplest of the four — no toggle needed
</phase_requirements>

Summary

This phase adds four backend entries to BACKEND_REGISTRY and the corresponding BACKEND_SCHEMAS entries in src/schemas/index.ts. Because Phase 5 (TECH-03) wired BackendSelectionStep to Object.entries(BACKEND_REGISTRY), adding an entry to the registry automatically surfaces a card in the UI — zero changes needed in BackendSelectionStep.

Three of the four backends (OneDrive, GCS, Backblaze B2) follow the existing "pure registry + FieldRenderer loop" pattern used for S3 and S3-compatible. SFTP is the only one that requires a custom auth-method toggle component (analogous to AzureAuthToggle) because users must choose between password and private-key authentication. The SftpAuthToggle component must follow the same CSS-hidden pattern as AzureAuthToggle to preserve both sets of field values in react-hook-form state when toggling.

RemoteConfigStep needs to be extended: the backendLabel object must be updated for the four new types, the Zod schema lookup must include the four new types, and SFTP needs a branch in the form rendering (similar to the azureblob branch today) to insert SftpAuthToggle instead of the plain FieldRenderer loop.

Primary recommendation: Implement in order: (1) registry entries for all four backends, (2) BACKEND_SCHEMAS extensions, (3) RCLONE_TYPE_MAP updates in rclone-conf.ts, (4) SftpAuthToggle component, (5) RemoteConfigStep extension. Write Wave 0 test stubs first for each.

Standard Stack

Core (already in use — no new installs)

Library Version Purpose Why Standard
React 18.3.1 UI rendering, hooks, conditional render Project baseline
TypeScript 5.5.3 Type safety for new BackendType union Project baseline
react-hook-form 7.72.0 Form state, registry-driven field registration Project baseline
zod 4.3.6 Schema derivation from registry fields Project baseline
@hookform/resolvers 5.2.2 Connects Zod schemas to react-hook-form Project baseline
Vitest 4.1.1 Test runner Project baseline
@testing-library/react 16.3.2 Component testing Project baseline
@testing-library/user-event 14.6.1 Async interaction simulation (act-safe) Project baseline

Installation: No new packages required.

Architecture Patterns

src/
├── schemas/
│   ├── registry.ts          # Add 'onedrive' | 'sftp' | 'gcs' | 'b2' to BackendType union; add 4 registry entries
│   └── index.ts             # Add 4 entries to BACKEND_SCHEMAS; extend buildZodSchema calls
├── generators/
│   └── rclone-conf.ts       # Add 4 entries to RCLONE_TYPE_MAP; gcs type has spaces
├── components/
│   ├── wizard/
│   │   ├── RemoteConfigStep.tsx   # Extend backendLabel; add sftp branch with SftpAuthToggle; handle gcs/b2/onedrive via registry loop
│   │   └── SftpAuthToggle.tsx     # NEW: password vs key-based auth toggle (follows AzureAuthToggle pattern)
│   └── ui/
│       └── (no new UI primitives needed — FieldRenderer + PasswordField cover all new fields)
├── tests/
│   ├── schemas/registry.test.ts         # Update EXPECTED_BACKENDS; add 4 backend assertions
│   ├── generators/rclone-conf.test.ts   # Add fixtures + assertions for all 4 new backends
│   └── components/wizard/RemoteConfigStep.test.tsx   # Add describe blocks for 4 new backends

Pattern 1: Registry Entry for a Simple Backend (B2 and SFTP non-toggle fields)

What: Add a Record<BackendType, { displayName, description, fields: FieldDef[] }> entry. When to use: When all fields are rendered uniformly through the FieldRenderer loop (no custom toggle).

// Source: Direct code audit of src/schemas/registry.ts (existing pattern)
b2: {
  displayName: 'Backblaze B2',
  description: 'Backblaze B2 Cloud Storage',
  fields: [
    {
      key: 'account',
      label: 'Application Key ID',
      inputType: 'text',
      required: true,
      placeholder: 'your-application-key-id',
      helpText: 'The applicationKeyId — not the master account ID',
    },
    {
      key: 'key',
      label: 'Application Key',
      inputType: 'password',
      required: true,
      helpText: 'The application key (secret value from the B2 dashboard)',
    },
  ],
},

Pattern 2: Registry Entry for OneDrive (token as password field)

What: OneDrive requires a token JSON blob paste. No drive_id or drive_type in the form — they are part of the token JSON obtained via rclone authorize. Decision: Per REQUIREMENTS.md Out of Scope: "OAuth flow in browser (OneDrive) — Requires backend proxy; paste pre-obtained token instead." The token field receives the full JSON output of rclone authorize "onedrive" and the drive_id / drive_type are embedded in that JSON or set interactively — however for unattended rclone config the minimal viable approach is token + drive_id + drive_type.

// Source: https://rclone.org/onedrive/ — verified 2026-03-30
onedrive: {
  displayName: 'OneDrive',
  description: 'Microsoft OneDrive (paste pre-obtained rclone token)',
  fields: [
    {
      key: 'token',
      label: 'OAuth Token (JSON)',
      inputType: 'password',
      required: true,
      placeholder: '{"access_token":"...","token_type":"Bearer","refresh_token":"...","expiry":"..."}',
      helpText: 'Paste the JSON token from: rclone authorize "onedrive"',
    },
    {
      key: 'drive_id',
      label: 'Drive ID',
      inputType: 'text',
      required: true,
      placeholder: 'b!...',
      helpText: 'The drive ID from your OneDrive. Found in the rclone authorize output.',
    },
    {
      key: 'drive_type',
      label: 'Drive Type',
      inputType: 'select',
      required: true,
      options: [
        { value: 'personal', label: 'Personal' },
        { value: 'business', label: 'Business' },
        { value: 'documentLibrary', label: 'SharePoint Document Library' },
      ],
      helpText: 'Personal for consumer OneDrive, Business for Microsoft 365',
    },
  ],
},

Pattern 3: Registry Entry for Google Cloud Storage

What: GCS uses service_account_credentials (JSON blob) as the recommended unattended auth method, since service_account_file requires a file path on disk (not compatible with wizard paste flow).

// Source: https://rclone.org/googlecloudstorage/ — verified 2026-03-30
// Note: rclone type string is exactly "google cloud storage" (with spaces)
gcs: {
  displayName: 'Google Cloud Storage',
  description: 'Google Cloud Storage',
  fields: [
    {
      key: 'project_number',
      label: 'Project Number',
      inputType: 'text',
      required: true,
      placeholder: '123456789',
      helpText: 'Your GCP project number (not project ID)',
    },
    {
      key: 'service_account_credentials',
      label: 'Service Account JSON',
      inputType: 'password',
      required: true,
      placeholder: '{"type":"service_account","project_id":"..."}',
      helpText: 'Paste the full content of your service account JSON key file',
    },
  ],
},

Pattern 4: SftpAuthToggle Component (new, follows AzureAuthToggle)

What: SFTP supports two auth methods: password (using the pass key) and private key (using key_pem for inline paste). The toggle follows the exact CSS-hidden pattern of AzureAuthToggle.

Key design decisions:

  • Use pass for password field (rclone SFTP key name — not password)
  • Use key_pem for inline key paste (avoids file path: key_file requires a path on the server, not pasteable in the wizard)
  • CSS hidden (not conditional render) — preserves both field values when toggling, consistent with the AzureAuthToggle decision recorded in PROJECT.md
// Source: https://rclone.org/sftp/ — verified 2026-03-30; pattern from AzureAuthToggle.tsx
// SftpAuthToggle.tsx
import { useState } from 'react';
import type { UseFormRegister, FieldError } from 'react-hook-form';
import { PasswordField } from '../ui/PasswordField';

type SftpAuthMethod = 'password' | 'key';

interface SftpAuthToggleProps {
  register: UseFormRegister<any>;
  errors: {
    pass?: FieldError;
    key_pem?: FieldError;
  };
}

export function SftpAuthToggle({ register, errors }: SftpAuthToggleProps) {
  const [authMethod, setAuthMethod] = useState<SftpAuthMethod>('password');
  return (
    <div className="flex flex-col gap-3">
      <div className="flex rounded-md border border-gray-300 overflow-hidden">
        <button type="button" onClick={() => setAuthMethod('password')} ...>Password</button>
        <button type="button" onClick={() => setAuthMethod('key')} ...>Private Key</button>
      </div>
      <div className={authMethod === 'password' ? 'block' : 'hidden'}>
        <PasswordField id="pass" label="Password" error={errors.pass} registration={register('pass')} />
      </div>
      <div 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-----)" />
      </div>
    </div>
  );
}

Pattern 5: SFTP Registry Entry (structural fields only — auth toggle handles pass/key_pem)

// Source: https://rclone.org/sftp/ — verified 2026-03-30
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',
    },
    // NOTE: pass and key_pem are NOT in the fields array.
    // They are handled by SftpAuthToggle (both always registered via CSS-hidden toggle).
    // This mirrors AzureAuthToggle's approach: key + sas_url not in registry fields array.
  ],
},

Important: Like azureblob, the sftp backend has fields not listed in BACKEND_REGISTRY.sftp.fields (pass and key_pem). These are registered by SftpAuthToggle directly. The Zod schema built from the registry (buildZodSchema('sftp')) will NOT include pass/key_pem — a custom Zod schema override for sftp is needed (like azureblob has its manual schema).

Wait — re-examine the current schema approach:

buildZodSchema makes required fields .min(1) and optional fields .optional(). For azureblob: key and sas_url are both in the registry (both optional). The schema covers them. So the toggle fields ARE in the registry for azureblob, just with required: false.

Revised SFTP approach: Include pass and key_pem in the registry fields as required: false. SftpAuthToggle still handles their visibility, but the registry/schema does cover them. This avoids needing a custom schema.

sftp: {
  displayName: 'SFTP',
  description: 'SSH File Transfer Protocol',
  fields: [
    { key: 'host',    label: 'Host',     inputType: 'text',     required: true,  placeholder: 'sftp.example.com' },
    { key: 'user',    label: 'Username', inputType: 'text',     required: true,  placeholder: 'admin' },
    { key: 'pass',    label: 'Password', inputType: 'password', required: false, helpText: 'SFTP password (leave blank if using private key)' },
    { key: 'key_pem', label: 'Private Key (PEM)', inputType: 'password', required: false, helpText: 'Paste PEM-encoded private key' },
  ],
},

Pattern 6: RCLONE_TYPE_MAP Extension

What: rclone-conf.ts maps our BackendType keys to rclone's type = values. GCS is the special case — its rclone type string contains spaces.

// Source: https://rclone.org/googlecloudstorage/ — verified 2026-03-30
const RCLONE_TYPE_MAP: Record<string, string> = {
  azureblob:    'azureblob',
  s3:           's3',
  's3-compatible': 's3',
  onedrive:     'onedrive',
  sftp:         'sftp',
  gcs:          'google cloud storage',  // NOTE: spaces in rclone type value
  b2:           'b2',
};

Pattern 7: RemoteConfigStep Extension

What: The component needs to handle 4 new backends. Only sftp needs special treatment (SftpAuthToggle). The other three render via the registry loop.

// Extend backendLabel record
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',
};

// In the JSX — add sftp branch alongside the existing azureblob branch
{backendType === 'azureblob' ? (
  <> ... existing AzureAuthToggle ... </>
) : backendType === 'sftp' ? (
  <>
    {/* host and user via FieldRenderer */}
    <FieldRenderer field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'host')!} ... />
    <FieldRenderer field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'user')!} ... />
    {/* Auth toggle handles pass + key_pem */}
    <SftpAuthToggle register={register} errors={{ pass: errors.pass as FieldError | undefined, key_pem: errors.key_pem as FieldError | undefined }} />
  </>
) : (
  /* All others: full registry loop */
  BACKEND_REGISTRY[backendType].fields.map(field => (
    <FieldRenderer key={field.key} field={field} register={register} error={errors[field.key] as FieldError | undefined} />
  ))
)}

Anti-Patterns to Avoid

  • Adding sftp to the generic FieldRenderer loop: SFTP's pass and key_pem must be handled by a toggle (CSS-hidden), not two separate visible fields. If both render simultaneously, UX is broken.
  • Using key_file instead of key_pem for SFTP: key_file expects a file system path (not pasteable in a browser wizard). key_pem accepts raw PEM content — correct for a paste workflow.
  • Using service_account_file for GCS: Same problem — it expects a file path. Use service_account_credentials for the JSON paste approach.
  • Hardcoding type = google cloud storage in the output: Do NOT hardcode this. The RCLONE_TYPE_MAP lookup handles it; buildRcloneConf already uses the map for all backends.
  • Forgetting to extend the schema fallback in RemoteConfigStep: Line 26 has const schema = backendType ? BACKEND_SCHEMAS[backendType] : BACKEND_SCHEMAS['azureblob']. When new types are added to BackendType, TypeScript will flag the BACKEND_SCHEMAS[backendType] access if the new type is not in BACKEND_SCHEMAS.

Don't Hand-Roll

Problem Don't Build Use Instead Why
SFTP auth toggle New toggle pattern from scratch SftpAuthToggle following AzureAuthToggle.tsx exactly AzureAuthToggle is proven, CSS-hidden preserves form state, already tested pattern
Zod schema for new backends Manual z.object({ ... }) for each new backend buildZodSchema() from registry fields The project's entire schema layer is auto-derived from registry — maintain that invariant
rclone type string mapping Conditional logic in generator RCLONE_TYPE_MAP entry GCS's spaced type string makes if/else brittle; the map is already the pattern
Field rendering for GCS/B2/OneDrive Custom form components FieldRenderer loop over registry fields These three have no toggle — FieldRenderer handles text/password/select/hidden correctly

Common Pitfalls

Pitfall 1: GCS type string has spaces

What goes wrong: Writing type = googlecloudstorage or type = gcs in the generated config — rclone rejects it. The correct rclone type string is google cloud storage (lowercase, with spaces). Why it happens: The internal key in BACKEND_REGISTRY uses gcs (short, no spaces) but the rclone config value is the full string. How to avoid: Add gcs: 'google cloud storage' to RCLONE_TYPE_MAP in rclone-conf.ts. Test: buildRcloneConf(gcsState) must contain type = google cloud storage. Warning signs: rclone returns "didn't find section in config file" or "unknown backend type" for a GCS config.

Pitfall 2: B2 account field naming collision

What goes wrong: Backblaze B2 uses account for the applicationKeyId — same key name as Azure Blob's account field. In WizardState, params is a flat Record<string, string>, so this is fine — no collision. But a test that reuses an Azure fixture and changes only backendType to b2 would pass wrong params. Why it happens: Field name overlap across two unrelated backends. How to avoid: B2 test fixtures must explicitly set params: { account: 'APP_KEY_ID', key: 'APP_KEY' }. Do not reuse azureblob fixtures. Warning signs: B2 rclone.conf test passes but generated config has type = azureblob (fixture mistake).

Pitfall 3: BackendType union not extended in types.ts

What goes wrong: src/store/types.ts imports BackendType from registry.ts and re-exports it. Adding new types to registry.ts BackendType union automatically propagates. However, if any code has Record<BackendType, ...> exhaustive objects (like backendLabel in RemoteConfigStep), TypeScript will error until those are updated. Why it happens: Exhaustive Record<BackendType, string> objects do not accept partial keys. How to avoid: Update backendLabel and BACKEND_SCHEMAS when adding new BackendType values. Run npx tsc --noEmit after each registry addition. Warning signs: TypeScript error "Type 'X' is not assignable to type 'never'" or "Property 'X' is missing in type".

Pitfall 4: SFTP pass field vs rclone's obscure requirement

What goes wrong: rclone SFTP documentation notes that pass should be obscured using rclone obscure. The generated config will contain the plain-text password. Why it happens: rclone's pass key for SFTP stores an obscured (not encrypted) password by default. When writing configs manually, plain text may or may not work depending on rclone version. How to avoid: Add a helpText note to the pass field: "rclone may require the password to be obscured using rclone obscure <password>. If authentication fails, use the obscured value instead of the plain password." Important: Per project constraints, plain text in generated files is acceptable — the security warning gate covers this. Do not block on this. Warning signs: rclone connects but authentication fails silently (password not accepted as plain text).

Pitfall 5: OneDrive token is a JSON blob — FieldRenderer handles it as a password

What goes wrong: The token field contains {"access_token":"...","refresh_token":"...","expiry":"..."} — a JSON string. Treating it as a text field shows it in plain text. Treating it as a password field hides it (preferred for credentials). How to avoid: Set inputType: 'password' for the token field. FieldRenderer already handles inputType: 'password' via PasswordField. Warning signs: Token value visible in browser without any masking.

Pitfall 6: RemoteConfigStep's schema fallback must handle new types

What goes wrong: Line 26: const schema = backendType ? BACKEND_SCHEMAS[backendType] : BACKEND_SCHEMAS['azureblob']. If a new BackendType is added to registry.ts but not to BACKEND_SCHEMAS in index.ts, this lookup throws at runtime. Why it happens: BACKEND_SCHEMAS is a manually-maintained object (not auto-derived from all registry keys in the current implementation). How to avoid: After adding registry entries, immediately add the corresponding buildZodSchema('newtype') call to BACKEND_SCHEMAS in index.ts. TypeScript will flag the missing key in BACKEND_SCHEMAS[backendType] if the type is exhaustive.

Code Examples

rclone.conf output for each new backend

; OneDrive — Source: https://rclone.org/onedrive/ verified 2026-03-30
[my-onedrive]
type = onedrive
token = {"access_token":"...","token_type":"Bearer","refresh_token":"...","expiry":"..."}
drive_id = b!XXXXXXXXX
drive_type = business

; SFTP with password — Source: https://rclone.org/sftp/ verified 2026-03-30
[my-sftp]
type = sftp
host = sftp.example.com
user = admin
pass = mypassword

; SFTP with private key
[my-sftp-key]
type = sftp
host = sftp.example.com
user = admin
key_pem = -----BEGIN RSA PRIVATE KEY-----\n...

; GCS — Source: https://rclone.org/googlecloudstorage/ verified 2026-03-30
; Note: type value has spaces
[my-gcs]
type = google cloud storage
project_number = 123456789
service_account_credentials = {"type":"service_account","project_id":"..."}

; Backblaze B2 — Source: https://rclone.org/b2/ verified 2026-03-30
[my-b2]
type = b2
account = applicationKeyId123
key = secretApplicationKey456

BackendType union extension

// src/schemas/registry.ts — updated union
export type BackendType = 'azureblob' | 's3' | 's3-compatible' | 'onedrive' | 'sftp' | 'gcs' | 'b2';

BACKEND_SCHEMAS extension

// src/schemas/index.ts — extend the BACKEND_SCHEMAS object
export const BACKEND_SCHEMAS = {
  azureblob:       buildZodSchema('azureblob'),
  s3:              buildZodSchema('s3'),
  's3-compatible': buildZodSchema('s3-compatible'),
  onedrive:        buildZodSchema('onedrive'),
  sftp:            buildZodSchema('sftp'),
  gcs:             buildZodSchema('gcs'),
  b2:              buildZodSchema('b2'),
} as const;

Test fixture pattern for new backends (rclone-conf.test.ts)

// Source: existing test pattern in src/generators/rclone-conf.test.ts
const b2State: WizardState = {
  ...INITIAL_STATE,
  remote: {
    name: 'my-b2',
    backendType: 'b2',
    params: { account: 'APP_KEY_ID', key: 'APP_KEY' },
  },
};

describe('buildRcloneConf — b2', () => {
  it('contains type = b2', () => {
    expect(buildRcloneConf(b2State)).toContain('type = b2');
  });
  it('contains account value', () => {
    expect(buildRcloneConf(b2State)).toContain('account = APP_KEY_ID');
  });
});

RemoteConfigStep test pattern for new backends

// Source: existing pattern in RemoteConfigStep.test.tsx (renderWithBackend helper)
describe('BACK-04: Backblaze B2 form', () => {
  it('renders Application Key ID field', async () => {
    renderWithBackend('b2');
    await waitFor(() => {
      expect(screen.getByLabelText(/application key id/i)).toBeDefined();
    });
  });
  it('renders Application Key field', async () => {
    renderWithBackend('b2');
    await waitFor(() => {
      expect(screen.getByLabelText(/application key/i)).toBeDefined();
    });
  });
});

State of the Art

Old Approach Current Approach When Changed Impact
Hardcoded backend list in BackendSelectionStep Object.entries(BACKEND_REGISTRY) loop Phase 5 (TECH-03) Adding registry entry auto-surfaces card in UI — zero additional BackendSelectionStep changes
BACKEND_REGISTRY: Record<BackendType, FieldDef[]> Record<BackendType, { displayName, description, fields }> Phase 5 (TECH-03) New entries follow the enriched shape

Deprecated/outdated:

  • BackendFormValues<T> type: removed in Phase 5 (TECH-04), do not re-introduce

Open Questions

  1. OneDrive drive_id and drive_type: should they be in the form, or embedded in the token?

    • What we know: The rclone authorize flow produces a token JSON, but drive_id and drive_type are separate config keys in rclone.conf.
    • What's unclear: Whether IT pros who pre-obtain a token via rclone authorize "onedrive" will have drive_id readily available, or whether this creates friction.
    • Recommendation: Include drive_id and drive_type as explicit form fields with guidance helpText. This is the explicit, transparent approach — avoids silent misconfiguration.
  2. SFTP pass field: warn about rclone obscure or silently output plain text?

    • What we know: rclone may require passwords in pass to be obscured. Plain text may work with some rclone versions but not others.
    • What's unclear: Current rclone version behavior for plain-text pass in sftp configs.
    • Recommendation: Add helpText noting the obscure requirement. Do not block generation. The security warning gate already covers plain-text credentials.
  3. GCS: should project_number be optional or required?

    • What we know: rclone docs say project_number is needed for bucket listing operations. GCS remotes may work for object access without it.
    • What's unclear: Whether the wizard's target audience (IT pros deploying rclone) will always have the project number.
    • Recommendation: Mark as required: true with a clear helpText. Better to require it upfront than produce a config that partially fails.

Validation Architecture

Test Framework

Property Value
Framework Vitest 4.1.1
Config file vitest.config.ts (root)
Quick run command npx vitest run src/schemas/registry.test.ts src/generators/rclone-conf.test.ts src/components/wizard/RemoteConfigStep.test.tsx
Full suite command npx vitest run

Phase Requirements → Test Map

Req ID Behavior Test Type Automated Command File Exists?
BACK-01 OneDrive card appears in BackendSelectionStep unit npx vitest run src/components/wizard/BackendSelectionStep.test.tsx (auto — BACKEND_REGISTRY drives the list; existing test now checks 7 backends)
BACK-01 OneDrive form renders token + drive_id + drive_type fields unit npx vitest run src/components/wizard/RemoteConfigStep.test.tsx Wave 0
BACK-01 buildRcloneConf for onedrive produces type = onedrive unit npx vitest run src/generators/rclone-conf.test.ts Wave 0
BACK-02 SFTP form renders host + user + password/key toggle unit npx vitest run src/components/wizard/RemoteConfigStep.test.tsx Wave 0
BACK-02 SFTP toggle CSS-hides inactive auth field (preserves value) unit npx vitest run src/components/wizard/RemoteConfigStep.test.tsx Wave 0
BACK-02 buildRcloneConf for sftp produces type = sftp with host/user unit npx vitest run src/generators/rclone-conf.test.ts Wave 0
BACK-03 GCS form renders project_number + service_account_credentials unit npx vitest run src/components/wizard/RemoteConfigStep.test.tsx Wave 0
BACK-03 buildRcloneConf for gcs produces type = google cloud storage unit npx vitest run src/generators/rclone-conf.test.ts Wave 0
BACK-04 B2 form renders account (Application Key ID) + key fields unit npx vitest run src/components/wizard/RemoteConfigStep.test.tsx Wave 0
BACK-04 buildRcloneConf for b2 produces type = b2 unit npx vitest run src/generators/rclone-conf.test.ts Wave 0
All All 7 backends in registry have displayName, description, fields unit npx vitest run src/schemas/registry.test.ts Wave 0 (update EXPECTED_BACKENDS)

Sampling Rate

  • Per task commit: npx vitest run (full suite — fast, no reason not to run all)
  • Per wave merge: npx vitest run + npx tsc --noEmit
  • Phase gate: Full suite green + zero TypeScript errors before /gsd:verify-work

Wave 0 Gaps

  • src/schemas/registry.test.ts — update EXPECTED_BACKENDS array to include all 7 backends; add SFTP-specific assertions (host, user, pass, key_pem fields); add B2 assertions; add GCS assertions; add OneDrive assertions
  • src/generators/rclone-conf.test.ts — add fixtures for onedrive, sftp (password), sftp (key), gcs, b2; assert type string and key fields for each; assert gcs produces type = google cloud storage (with spaces)
  • src/components/wizard/RemoteConfigStep.test.tsx — add describe blocks for BACK-01 (OneDrive), BACK-02 (SFTP with toggle assertions), BACK-03 (GCS), BACK-04 (B2) using existing renderWithBackend helper

Sources

Primary (HIGH confidence)

  • Direct code audit of src/schemas/registry.ts — confirmed BackendType union, BACKEND_REGISTRY shape, all existing field definitions
  • Direct code audit of src/schemas/index.ts — confirmed buildZodSchema function, BACKEND_SCHEMAS structure
  • Direct code audit of src/generators/rclone-conf.ts — confirmed RCLONE_TYPE_MAP, buildRcloneConf function
  • Direct code audit of src/components/wizard/RemoteConfigStep.tsx — confirmed backendLabel record, azureblob branch pattern, registry loop pattern
  • Direct code audit of src/components/wizard/AzureAuthToggle.tsx — confirmed CSS-hidden pattern, both fields always registered
  • Direct code audit of src/components/wizard/FieldRenderer.tsx — confirmed all input types handled (text, password, select, hidden)
  • package.json — confirmed all required libraries already installed, no new dependencies needed
  • https://rclone.org/onedrive/ — token JSON structure, drive_id, drive_type fields (fetched 2026-03-30)
  • https://rclone.org/sftp/ — host, user, pass, key_pem, key_file field names (fetched 2026-03-30)
  • https://rclone.org/googlecloudstorage/ — type string "google cloud storage", service_account_credentials field (fetched 2026-03-30)
  • https://rclone.org/b2/ — type "b2", account (applicationKeyId), key fields (fetched 2026-03-30)

Secondary (MEDIUM confidence)

  • STATE.md project decisions — AzureAuthToggle CSS-hidden pattern noted as established for SFTP auth toggle
  • PROJECT.md Out of Scope — "OAuth flow in browser (OneDrive)" confirms paste-token approach

Tertiary (LOW confidence)

  • SFTP pass plain-text behavior: rclone may require rclone obscure for the pass value. Documentation states it should be obscured but behavior with plain text varies. Recommend helpText warning rather than blocking.

Metadata

Confidence breakdown:

  • Standard stack: HIGH — no new libraries; all existing stack confirmed from package.json and code audit
  • Architecture: HIGH — patterns derived from direct code audit of AzureAuthToggle, registry, and generator
  • rclone field names: HIGH — verified against official rclone.org docs (fetched 2026-03-30)
  • SFTP pass plain-text behavior: LOW — rclone version-dependent; add helpText but do not block

Research date: 2026-03-30 Valid until: 2026-06-30 (rclone backend config format is stable; React/Vitest versions unchanged)