31 KiB
Phase 13: Add Remaining RClone Remotes - Research
Researched: 2026-04-01 Domain: rclone backend registry expansion, React component architecture, UX search/categorization Confidence: HIGH
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
- Comprehensive coverage — all rclone backends that make sense for unattended Windows deployment
- OAuth-requiring backends included via token-paste approach (same as OneDrive: user runs
rclone authorizelocally, pastes resulting JSON token) - All new backends shipped in a single phase (no sub-phases by auth type)
- Claude picks the specific backend list based on rclone documentation and deployment viability
- Reuse per-backend AuthToggle component pattern for backends with mutually-exclusive auth fields (e.g., FTP password vs key file) — same proven pattern as AzureAuthToggle/SftpAuthToggle
- Build a new reusable OAuthInstructions component for OAuth-token backends — collapsible step-by-step guide showing how to run
rclone authorizefor that specific backend - OAuthInstructions takes backend-specific command and steps as props, keeps RemoteConfigStep clean
- Categorized sections with headings: Cloud Object Storage, Cloud Drives, Protocol-based — grouped by storage type
- Search/filter bar at the top that filters instantly as user types
- Search matches across all fields: displayName, description, category, and field labels
- Categories with no matching backends collapse when search is active
- Add
categoryfield to each BACKEND_REGISTRY entry — single source of truth stays single - Add inline SVG icons per backend card — hand-picked SVGs bundled in the app, no external dependency
Claude's Discretion
- BackendType implementation: keep explicit union type or derive dynamically from registry keys — pick whichever balances type safety and maintainability best at 20+ backends
- Specific backend list selection based on rclone docs and unattended deployment viability
- Field validation depth per backend (regex patterns vs simple required/optional)
- Icon design approach (monochrome vs brand colors, sizing)
Deferred Ideas (OUT OF SCOPE)
None — discussion stayed within phase scope </user_constraints>
Summary
Phase 13 expands the BACKEND_REGISTRY from 7 to approximately 18-20 entries, grouping them into three categories (Cloud Object Storage, Cloud Drives, Protocol-based). Each new backend requires: a registry entry, a RCLONE_TYPE_MAP entry, a buildZodSchema() call, and a backendLabel entry. Backends with OAuth tokens get an OAuthInstructions collapsible component. Backends with mutually-exclusive auth get a new AuthToggle component. The BackendSelectionStep gets a complete UX overhaul with category headings, instant search, and inline SVG icons.
The critical architectural decision is whether BackendType stays as an explicit union literal or is derived from registry keys. At 20+ backends, explicit union becomes a maintenance burden (every new backend requires editing 6 files). Deriving from registry keys reduces that to 4 files and eliminates the "forgot to update BackendType" class of bug.
Primary recommendation: Derive BackendType from keyof typeof BACKEND_REGISTRY to reduce per-backend boilerplate from 6 touch-points to 4. Use as const assertion on the registry object for full TypeScript narrowing.
Recommended Backend List
Based on rclone documentation and suitability for unattended Windows deployment via RMM/Intune, the following backends should be added. Each entry shows the rclone type string and auth approach.
Category: Cloud Object Storage (S3-like)
These all use static API credentials — ideal for unattended deployment.
| Backend ID | displayName | rclone type | Auth approach | Notes |
|---|---|---|---|---|
azureblob |
Azure Blob Storage | azureblob |
Key or SAS URL | Existing |
azure-files |
Azure Files Storage | azurefiles |
Account + Key | New — uses account + key fields |
s3 |
Amazon S3 | s3 |
Access Key + Secret | Existing |
s3-compatible |
S3-Compatible | s3 (provider=Other) |
Access Key + Secret + Endpoint | Existing |
gcs |
Google Cloud Storage | google cloud storage |
Service Account JSON | Existing |
b2 |
Backblaze B2 | b2 |
Application Key ID + Key | Existing |
swift |
OpenStack Swift | swift |
User + Key + Auth URL + Tenant | New — API credential |
Category: Cloud Drives (OAuth token-paste)
These require OAuth — user runs rclone authorize locally, pastes JSON token.
| Backend ID | displayName | rclone type | Auth approach | Notes |
|---|---|---|---|---|
onedrive |
OneDrive | onedrive |
OAuth token + drive_id + drive_type | Existing |
gdrive |
Google Drive | drive |
OAuth token + service account option | New — token or service_account_credentials |
dropbox |
Dropbox | dropbox |
OAuth token (JSON blob) | New — token field only |
box |
Box | box |
OAuth token (JSON blob) | New — token + box_sub_type |
pcloud |
pCloud | pcloud |
OAuth token (JSON blob) | New — token + hostname |
Category: Protocol-based
These use server credentials — IP/hostname, user, password.
| Backend ID | displayName | rclone type | Auth approach | Notes |
|---|---|---|---|---|
sftp |
SFTP | sftp |
Password or Key PEM (AuthToggle) | Existing |
ftp |
FTP | ftp |
Host + User + Password | New — also FTPS via tls select |
webdav |
WebDAV | webdav |
URL + User + Password + Vendor select | New — includes NextCloud, SharePoint, OwnCloud, etc. |
smb |
SMB / Windows Share | smb |
Host + User + Pass + Domain | New — ideal for Windows shops |
http |
HTTP (read-only) | http |
URL only | New — simple, read-only |
seafile |
Seafile | seafile |
URL + User + Password | New — self-hosted cloud |
Total: 7 existing + 10 new = 17 backends
Excluded from scope (not suitable for unattended Windows deployment):
- HDFS — Hadoop/enterprise Linux ecosystem, not Windows-native
- Google Photos — read-only, consumer
- iCloud Drive — macOS-only tooling
- Mega, Jottacloud, Yandex, Mail.ru — consumer-grade, poor enterprise adoption
- pCloud — included only if token-paste approach is feasible (MEDIUM confidence)
- Internet Archive, Pixeldrain, Uloz.to, Gofile — public/consumer file sharing
Standard Stack
Core (no new dependencies)
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| react | ^18.3.1 | UI rendering | Existing |
| react-hook-form | ^7.72.0 | Form state + validation | Existing — all forms use it |
| zod | ^4.3.6 | Schema validation | Existing — buildZodSchema() auto-derives |
| @hookform/resolvers | ^5.2.2 | Zod <-> RHF bridge | Existing |
No new npm dependencies. Inline SVG icons are bundled as TSX components. No icon library needed.
Installation
# Nothing new to install — all new code uses existing stack
Architecture Patterns
Recommended Project Structure (additions only)
src/
├── schemas/
│ └── registry.ts # Add ~10 new backend entries + category field
├── generators/
│ └── rclone-conf.ts # Add ~10 entries to RCLONE_TYPE_MAP
├── components/
│ ├── wizard/
│ │ ├── BackendSelectionStep.tsx # Full overhaul: categories, search, icons
│ │ ├── RemoteConfigStep.tsx # Add auth-toggle branches for FTP/WebDAV
│ │ ├── FtpAuthToggle.tsx # New: password vs no-auth (anonymous) toggle
│ │ ├── OAuthInstructions.tsx # New: reusable collapsible rclone authorize guide
│ │ └── [backend]AuthToggle.tsx # Only if backend needs mutually-exclusive auth
│ └── icons/
│ └── BackendIcons.tsx # New: inline SVG icon map, keyed by BackendType
Pattern 1: Deriving BackendType from Registry (RECOMMENDED)
The current explicit union 'azureblob' | 's3' | ... must be touched in registry.ts every time a backend is added. At 17+ backends, derive it instead:
// src/schemas/registry.ts
export const BACKEND_REGISTRY = {
azureblob: { ... },
gdrive: { ... },
// ... all entries
} as const; // <-- CRITICAL: 'as const' needed for keyof narrowing
export type BackendType = keyof typeof BACKEND_REGISTRY;
// Result: 'azureblob' | 's3' | 'gdrive' | ... (auto-updated when registry grows)
What changes: BackendType no longer lives in registry.ts as a separate literal union — it is derived. The src/store/types.ts re-export (export type { BackendType }) continues to work unchanged. All consumers importing BackendType from ./registry or ../store/types require zero changes.
What must be verified: The BACKEND_REGISTRY cast at the bottom currently uses as Record<BackendType, ...> which would become circular. Remove that cast — with as const the registry is already fully typed by inference.
Confidence: HIGH — standard TypeScript pattern, verified against codebase.
Pattern 2: BACKEND_REGISTRY Entry with category Field
Add category to the registry entry — single source of truth for categorization:
// src/schemas/registry.ts
export type BackendCategory = 'cloud-object-storage' | 'cloud-drives' | 'protocol-based';
export interface BackendMeta {
displayName: string;
description: string;
category: BackendCategory; // NEW field
fields: FieldDef[];
}
export const BACKEND_REGISTRY = {
azureblob: {
displayName: 'Azure Blob Storage',
description: 'Microsoft Azure cloud storage',
category: 'cloud-object-storage', // NEW
fields: [ ... ],
},
gdrive: {
displayName: 'Google Drive',
description: 'Google Drive (paste pre-obtained rclone token)',
category: 'cloud-drives',
fields: [ ... ],
},
} as const;
Pattern 3: OAuthInstructions Component
Collapsible component, collapsed by default. Takes backend-specific props:
// src/components/wizard/OAuthInstructions.tsx
interface OAuthInstructionsProps {
backendName: string; // e.g. "Google Drive"
authorizeCommand: string; // e.g. 'rclone authorize "drive"'
steps?: string[]; // optional custom step descriptions
}
export function OAuthInstructions({ backendName, authorizeCommand, steps }: OAuthInstructionsProps) {
const [expanded, setExpanded] = useState(false);
// Renders a collapsible details/summary or button-controlled div
// Shows numbered steps: install rclone → run command → browser opens → paste JSON
}
Used in RemoteConfigStep for gdrive, dropbox, box, pcloud alongside the token PasswordField:
// In RemoteConfigStep, new OAuth branch:
} : backendType === 'gdrive' ? (
<>
<OAuthInstructions
backendName="Google Drive"
authorizeCommand='rclone authorize "drive"'
/>
<FieldRenderer field={BACKEND_REGISTRY.gdrive.fields.find(f => f.key === 'token')!} ... />
<FieldRenderer field={BACKEND_REGISTRY.gdrive.fields.find(f => f.key === 'root_folder_id')!} ... />
</>
)
Pattern 4: BackendSelectionStep Category + Search
The current flat grid becomes a category-grouped, searchable layout:
// Category display order (stable, not alphabetical)
const CATEGORY_ORDER: BackendCategory[] = [
'cloud-object-storage',
'cloud-drives',
'protocol-based',
];
const CATEGORY_LABELS: Record<BackendCategory, string> = {
'cloud-object-storage': 'Cloud Object Storage',
'cloud-drives': 'Cloud Drives',
'protocol-based': 'Protocol-based',
};
// Search filter logic — matches displayName, description, category label, and field labels
function matchesSearch(entry: BackendMeta, query: string): boolean {
const q = query.toLowerCase();
if (entry.displayName.toLowerCase().includes(q)) return true;
if (entry.description.toLowerCase().includes(q)) return true;
if (CATEGORY_LABELS[entry.category].toLowerCase().includes(q)) return true;
if (entry.fields.some(f => f.label.toLowerCase().includes(q))) return true;
return false;
}
Search bar uses the existing TextFieldMD3 component. The search query is stored in local useState — no global state needed.
Pattern 5: Inline SVG Icon Component
Icons are TSX components returning <svg> elements directly — no external dependency, full styling control:
// src/components/icons/BackendIcons.tsx
interface IconProps { className?: string; }
export const AzureIcon = ({ className }: IconProps) => (
<svg className={className} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
{/* Azure blob SVG path */}
</svg>
);
// Icon map keyed by BackendType
export const BACKEND_ICONS: Partial<Record<BackendType, React.FC<IconProps>>> = {
azureblob: AzureIcon,
gdrive: GoogleDriveIcon,
// ...
};
BackendCard receives an optional icon prop rendered at the top-left of the card.
Anti-Patterns to Avoid
- Maintaining explicit BackendType union: At 17+ backends, a forgotten update causes silent TypeScript errors in
backendLabelexhaustiveness checks. Derive from registry keys instead. - Splitting
categoryinto a separate map: Keeping it inBACKEND_REGISTRYmeans one file to edit per new backend. A separateBACKEND_CATEGORIESrecord creates two sources of truth. - Using
display: noneCSS for search-hidden categories: The category heading itself must be hidden when its backends are all filtered out — use conditional rendering, not CSS hiding (CSS hiding leaves empty heading wrappers in DOM). - Eager OAuthInstructions expansion: Default expanded state wastes screen space and is intimidating. Default collapsed, user expands when they need help.
- Placeholder prop on
TextFieldMD3: TextFieldMD3 usesplaceholder=" "(space) internally for the CSS-only floating label. Do NOT pass a real placeholder string — it will break thepeer-[:not(:placeholder-shown)]selector. UsehelpTextfor descriptive hints instead.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| OAuth token paste textarea | Multi-line textarea component | PasswordField (inputType: 'password') |
Existing component with show/hide, tooltip, validation — textarea breaks form validation contract |
| Search debouncing | useDebounce hook |
Direct useState + filter on render |
17 backends filter in <1ms; debounce is premature optimization |
| Icon library | npm icon library install | Inline TSX SVG components in BackendIcons.tsx |
Zero dependency, full Tailwind/CSS control, no bundle bloat |
| BackendType exhaustiveness check | Manual assertNever() |
keyof typeof BACKEND_REGISTRY derivation |
TypeScript already errors if a key is missing from backendLabel record |
| Category data store | Separate CATEGORY_REGISTRY map |
category field on BACKEND_REGISTRY entry |
Single source of truth already established |
| rclone type mapping override | Per-backend conditional logic | RCLONE_TYPE_MAP entry (existing pattern) |
Already used for s3-compatible → s3 |
Key insight: The registry-driven pattern already handles 95% of new-backend complexity. Most new backends (FTP, Seafile, Koofr) need zero custom React components — just a registry entry, a RCLONE_TYPE_MAP entry, a schema call, and a backendLabel string.
Common Pitfalls
Pitfall 1: BackendType Cast Conflicts with as const
What goes wrong: The current registry.ts bottom has } as Record<BackendType, { displayName: string; description: string; fields: FieldDef[] }>. When BackendType is derived from keyof typeof BACKEND_REGISTRY, this cast creates a circular dependency (BackendType depends on the registry type, which is cast to use BackendType).
Why it happens: TypeScript cannot resolve the type when both sides of the as cast reference each other.
How to avoid: Remove the cast entirely. With as const on the registry object, TypeScript infers the full precise type. The BackendMeta interface should be used for individual entries, not as a cast on the whole object.
Warning signs: TypeScript error like "Type alias 'BackendType' circularly references itself."
Pitfall 2: backendLabel Record in RemoteConfigStep Breaks Exhaustiveness
What goes wrong: backendLabel is typed as Record<NonNullable<typeof backendType>, string> — adding a new BackendType without adding to backendLabel causes a TypeScript error at this record. This is desirable (forced exhaustiveness) but easy to miss during development.
How to avoid: Add backendLabel entry as the FIRST thing when adding a new backend. Use displayName from registry directly instead of duplicating: const backendLabel = Object.fromEntries(Object.entries(BACKEND_REGISTRY).map(([k, v]) => [k, v.displayName])) — eliminates the duplication entirely.
Warning signs: TS2741 "Property X is missing in type Record<...>"
Pitfall 3: BACKEND_SCHEMAS Explicit Object Misses New Backends
What goes wrong: BACKEND_SCHEMAS in src/schemas/index.ts is an explicit object with one buildZodSchema() call per backend. Adding a backend to the registry but forgetting BACKEND_SCHEMAS causes a runtime crash in RemoteConfigStep when the schema lookup returns undefined.
How to avoid: Auto-generate BACKEND_SCHEMAS from the registry:
export const BACKEND_SCHEMAS = Object.fromEntries(
(Object.keys(BACKEND_REGISTRY) as BackendType[]).map(t => [t, buildZodSchema(t)])
) as Record<BackendType, ReturnType<typeof buildZodSchema>>;
This makes BACKEND_SCHEMAS auto-expand whenever the registry grows.
Warning signs: Runtime error "Cannot read properties of undefined (reading 'parse')" in RemoteConfigStep.
Pitfall 4: FTP/WebDAV Toggle Auth — Zod Schema for Hidden Fields
What goes wrong: AzureAuthToggle and SftpAuthToggle register BOTH auth fields always (CSS-hide the inactive one) so values are preserved on tab switch. The Zod schema marks both as optional. This works for Azure (key/sas_url are both optional) but FTP's pass field may be expected to be required — making it optional in Zod allows submitting with no password, which silently creates a broken config.
Why it happens: Toggle pattern requires both fields to be optional in Zod regardless of visual state.
How to avoid: For FTP, both pass (required: false) and the anonymous-access toggle approach is fine since FTP often legitimately uses anonymous auth. Mark both toggle fields as required: false in the registry. Document in helpText that the inactive field is ignored.
Pitfall 5: Search Filter Leaves Empty Category Headings in DOM
What goes wrong: If search is implemented with CSS hidden on non-matching cards but the category heading is always rendered, an empty <h3>Cloud Drives</h3> appears when all Cloud Drive backends are filtered out.
How to avoid: Filter backends per category before render, skip the entire category block (heading + cards) when filteredBackends.length === 0 for that category.
Pitfall 6: rclone Type Strings Are Exact and Case-Sensitive
What goes wrong: RCLONE_TYPE_MAP maps backend IDs to rclone type strings. Errors here produce a broken rclone.conf that rclone silently rejects.
Verified mappings (HIGH confidence — from rclone official docs):
| BackendType | rclone type string |
|---|---|
gdrive |
drive |
dropbox |
dropbox |
box |
box |
pcloud |
pcloud |
ftp |
ftp |
webdav |
webdav |
smb |
smb |
http |
http |
swift |
swift |
seafile |
seafile |
azure-files |
azurefiles |
Warning signs: rclone error "didn't find section in config file" or "unknown backend type."
Pitfall 7: Google Drive Has Two Auth Paths — Service Account vs OAuth Token
What goes wrong: Google Drive supports both service account JSON (for unattended, like GCS) and OAuth token paste (like OneDrive). Exposing both options requires an AuthToggle, or a decision to support only one path.
Recommendation: Support both via a GdriveAuthToggle component:
- Tab 1: "OAuth Token" — token field + OAuthInstructions component
- Tab 2: "Service Account" — service_account_credentials field (reuses GCS pattern)
This matches the unattended deployment goal: domain admins can use service accounts; personal Drive setups use OAuth token paste.
Code Examples
Verified patterns from codebase:
Auto-derived BackendType (replaces explicit union)
// src/schemas/registry.ts
export const BACKEND_REGISTRY = {
azureblob: { displayName: '...', description: '...', category: 'cloud-object-storage', fields: [...] },
gdrive: { displayName: '...', description: '...', category: 'cloud-drives', fields: [...] },
// ...
} as const; // REQUIRED for keyof narrowing
export type BackendCategory = 'cloud-object-storage' | 'cloud-drives' | 'protocol-based';
export type BackendType = keyof typeof BACKEND_REGISTRY;
// Automatically includes every key in the registry
Auto-generated BACKEND_SCHEMAS (replaces explicit per-backend calls)
// src/schemas/index.ts
export const BACKEND_SCHEMAS = Object.fromEntries(
(Object.keys(BACKEND_REGISTRY) as BackendType[]).map(t => [t, buildZodSchema(t)])
) as Record<BackendType, ReturnType<typeof buildZodSchema>>;
backendLabel derived from registry (removes duplicate displayNames)
// In RemoteConfigStep.tsx
const backendLabel = Object.fromEntries(
Object.entries(BACKEND_REGISTRY).map(([k, v]) => [k, v.displayName])
) as Record<BackendType, string>;
Category-grouped backend rendering in BackendSelectionStep
const CATEGORY_ORDER: BackendCategory[] = ['cloud-object-storage', 'cloud-drives', 'protocol-based'];
// Inside component:
const filteredEntries = Object.entries(BACKEND_REGISTRY).filter(
([, entry]) => !query || matchesSearch(entry, query)
);
{CATEGORY_ORDER.map(cat => {
const backends = filteredEntries.filter(([, e]) => e.category === cat);
if (backends.length === 0) return null; // collapse empty categories
return (
<section key={cat}>
<h3>{CATEGORY_LABELS[cat]}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{backends.map(([type, entry]) => (
<BackendCard key={type} ... icon={BACKEND_ICONS[type as BackendType]} />
))}
</div>
</section>
);
})}
OAuthInstructions usage in RemoteConfigStep
// For gdrive OAuth path in RemoteConfigStep:
<OAuthInstructions
backendName="Google Drive"
authorizeCommand='rclone authorize "drive"'
/>
<FieldRenderer
field={BACKEND_REGISTRY.gdrive.fields.find(f => f.key === 'token')!}
register={register}
error={errors.token as FieldError | undefined}
/>
FTP registry entry (example of new backend with optional FTPS)
ftp: {
displayName: 'FTP',
description: 'File Transfer Protocol (supports FTPS)',
category: 'protocol-based',
fields: [
{ key: 'host', label: 'Host', inputType: 'text', required: true, placeholder: 'ftp.example.com' },
{ key: 'user', label: 'Username', inputType: 'text', required: false, placeholder: 'anonymous', helpText: 'Leave blank for anonymous FTP' },
{ key: 'pass', label: 'Password', inputType: 'password', required: false },
{ key: 'port', label: 'Port', inputType: 'text', required: false, placeholder: '21' },
{ key: 'explicit_tls', label: 'TLS Mode', inputType: 'select', required: false,
options: [
{ value: '', label: 'Plain FTP (no encryption)' },
{ value: 'true', label: 'Explicit FTPS (STARTTLS)' },
],
helpText: 'Use Explicit FTPS for encrypted FTP connections (port 21 + STARTTLS)',
},
],
},
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
Explicit BackendType union literal (7 entries) |
Derive from keyof typeof BACKEND_REGISTRY |
Phase 13 | Eliminates one of 6 touch-points per new backend |
Explicit per-backend buildZodSchema() calls |
Auto-generate from registry keys | Phase 13 | Eliminates one of 6 touch-points per new backend |
Manual backendLabel record duplication |
Derive from entry.displayName |
Phase 13 | Eliminates last duplication of displayName |
| Flat card grid (no categories) | Category-grouped with search | Phase 13 | Required UX for 17+ backends |
| No OAuth guidance | OAuthInstructions collapsible component | Phase 13 | New backends (gdrive, dropbox, box) need this |
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | Vitest ^4.1.1 |
| Config file | vite.config.ts (test.environment: jsdom) + vitest.config.ts (node env for generator tests) |
| Quick run command | npx vitest run |
| Full suite command | npx vitest run |
Phase Requirements → Test Map
| Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|
| All 17 backend types present in BACKEND_REGISTRY | unit | npx vitest run src/schemas/registry.test.ts |
Update existing ✅ |
| Each new backend has required fields | unit | npx vitest run src/schemas/registry.test.ts |
Update existing ✅ |
| BACKEND_SCHEMAS auto-covers new backends (no undefined) | unit | npx vitest run src/schemas/index.test.ts |
Wave 0 gap ❌ |
| RCLONE_TYPE_MAP has entry for each new backend | unit | npx vitest run src/generators/rclone-conf.test.ts |
Update existing ✅ |
| BackendSelectionStep renders all 17 cards | unit | npx vitest run src/components/wizard/BackendSelectionStep.test.tsx |
Update existing ✅ |
| Search filter hides non-matching cards | unit | npx vitest run src/components/wizard/BackendSelectionStep.test.tsx |
Wave 0 gap ❌ |
| Category headings collapse when all items filtered | unit | npx vitest run src/components/wizard/BackendSelectionStep.test.tsx |
Wave 0 gap ❌ |
| OAuthInstructions renders collapsed by default | unit | npx vitest run src/components/wizard/OAuthInstructions.test.tsx |
Wave 0 gap ❌ |
| OAuthInstructions expands on button click | unit | npx vitest run src/components/wizard/OAuthInstructions.test.tsx |
Wave 0 gap ❌ |
| RemoteConfigStep renders gdrive form fields | unit | npx vitest run src/components/wizard/RemoteConfigStep.test.tsx |
Update existing ✅ |
| RemoteConfigStep renders ftp form fields | unit | npx vitest run src/components/wizard/RemoteConfigStep.test.tsx |
Update existing ✅ |
| buildRcloneConf outputs correct type for gdrive | unit | npx vitest run src/generators/rclone-conf.test.ts |
Update existing ✅ |
Sampling Rate
- Per task commit:
npx vitest run - Per wave merge:
npx vitest run - Phase gate: Full suite green before
/gsd:verify-work
Wave 0 Gaps
src/schemas/index.test.ts— covers BACKEND_SCHEMAS auto-generation (verify no undefined for new types)src/components/wizard/OAuthInstructions.test.tsx— covers collapsed default + expand behavior- New test cases in
BackendSelectionStep.test.tsx— search filter and category collapse behavior
Open Questions
-
Google Drive: OAuth token vs service account — single form or toggle?
- What we know: GCS already uses service_account_credentials. Google Drive supports both approaches.
- What's unclear: Whether IT pros deploying via RMM will have service accounts for Drive (less common than for GCS).
- Recommendation: Implement
GdriveAuthTogglewith two tabs (OAuth token + Service Account) to cover both. If that adds too much complexity, default to OAuth token path only (simpler, more universally applicable).
-
Azure Files vs Azure Blob — same
azurefilestype string?- What we know: rclone docs list
azurefilesas the type for Azure Files Storage (distinct fromazureblob). - What's unclear: Required fields for
azurefiles— need to verify account/key vs connection_string approach. - Recommendation: Verify against https://rclone.org/azurefiles/ during plan execution before writing registry fields. HIGH confidence on type string, MEDIUM confidence on exact field names.
- What we know: rclone docs list
-
pCloud hostname requirement for EU users
- What we know: pCloud has region servers; EU users must set
hostnametoeapi.pcloud.com. - What's unclear: Can we default
hostnametoapi.pcloud.comand let users override, or is a select dropdown better? - Recommendation: Add
hostnameas a select with two options (US/EU) — clearer than asking IT pros to know the API hostname.
- What we know: pCloud has region servers; EU users must set
-
BackendCard icon sizing and color
- What we know: User decided "inline SVG, no npm library."
- What's unclear: Monochrome (currentColor, works in dark mode) vs brand colors.
- Recommendation: Monochrome (
fill="currentColor") for consistency with MD3 token system and dark mode compatibility. Brand-colored icons require hardcoded hex values that ignore the MD3 color token system.
Sources
Primary (HIGH confidence)
- rclone.org/drive/ — Google Drive type string (
drive), service account fields, OAuth token structure - rclone.org/dropbox/ — Dropbox type string (
dropbox), token field - rclone.org/box/ — Box type string (
box), token + box_sub_type fields - rclone.org/pcloud/ — pCloud type string (
pcloud), token + hostname fields - rclone.org/ftp/ — FTP type string (
ftp), host/user/pass/tls fields - rclone.org/webdav/ — WebDAV type string (
webdav), url/user/pass/vendor fields + vendor options - rclone.org/smb/ — SMB type string (
smb), host/user/pass/domain fields - rclone.org/swift/ — OpenStack Swift type string (
swift), required fields - rclone.org/seafile/ — Seafile type string (
seafile), url/user/pass fields - rclone.org/koofr/ — Koofr type string (
koofr), user/password fields - rclone.org/s3/ — S3-compatible provider enum (Wasabi, Cloudflare, MinIO, etc.)
- Existing codebase (registry.ts, BackendSelectionStep.tsx, RemoteConfigStep.tsx) — HIGH confidence on patterns
Secondary (MEDIUM confidence)
- rclone.org/overview/ — full backend list confirmed
- rclone.org/docs/ — comprehensive backend list verified
Tertiary (LOW confidence)
- Azure Files (
azurefiles) field names — type string confirmed, exact credential fields need verification against https://rclone.org/azurefiles/
Metadata
Confidence breakdown:
- Standard stack: HIGH — no new dependencies, all existing patterns
- Backend list selection: HIGH — rclone official docs confirmed for all 10 new backends
- rclone type strings: HIGH — verified against official docs for all backends
- Architecture patterns (BackendType derivation): HIGH — standard TypeScript
keyof typeofpattern - Architecture patterns (OAuthInstructions component): HIGH — directly mirrors existing AzureAuthToggle/SftpAuthToggle patterns
- BackendSelectionStep overhaul: HIGH — filter/group/search is standard React pattern, no novel abstractions
- Azure Files field names: MEDIUM — type string confirmed, credential fields need docs verification
- pCloud EU hostname handling: MEDIUM — documented in rclone docs, implementation approach is recommendation
Research date: 2026-04-01 Valid until: 2026-05-01 (rclone docs are stable; backend type strings rarely change)