docs(13): create phase plan for backend expansion

4 plans across 3 waves to expand from 7 to 17 rclone backends:
- Plan 01 (W1): Registry refactoring, BackendType derivation, 10 new entries
- Plan 02 (W2): OAuthInstructions, GdriveAuthToggle, BackendIcons components
- Plan 03 (W2): BackendSelectionStep UX overhaul with categories, search, icons
- Plan 04 (W3): RemoteConfigStep wiring for all new backends + visual verify

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 15:58:51 +02:00
co-authored by Claude Opus 4.6
parent fac90462f7
commit 4bf3f1e45f
6 changed files with 1019 additions and 30 deletions
@@ -0,0 +1,246 @@
---
phase: 13-add-remaining-rclone-remotes
plan: 03
type: execute
wave: 2
depends_on: [13-01]
files_modified:
- src/components/wizard/BackendSelectionStep.tsx
- src/components/ui/BackendCard.tsx
- src/components/wizard/BackendSelectionStep.test.tsx
autonomous: true
requirements: [REMOTE-03]
must_haves:
truths:
- "Backends are grouped under category headings: Cloud Object Storage, Cloud Drives, Protocol-based"
- "Search bar filters backends instantly as user types"
- "Search matches displayName, description, category label, and field labels"
- "Categories with no matching backends are hidden (not rendered)"
- "Each backend card shows an inline SVG icon"
- "Existing remote name input and validation still work"
artifacts:
- path: "src/components/wizard/BackendSelectionStep.tsx"
provides: "Category-grouped, searchable backend selection with icons"
contains: "CATEGORY_ORDER"
- path: "src/components/ui/BackendCard.tsx"
provides: "Backend card with optional icon prop"
- path: "src/components/wizard/BackendSelectionStep.test.tsx"
provides: "Tests for search filter and category collapse"
key_links:
- from: "src/components/wizard/BackendSelectionStep.tsx"
to: "src/schemas/registry.ts"
via: "BACKEND_REGISTRY iteration with category grouping"
pattern: "BACKEND_REGISTRY.*category"
- from: "src/components/wizard/BackendSelectionStep.tsx"
to: "src/components/icons/BackendIcons.tsx"
via: "BACKEND_ICONS import for card rendering"
pattern: "BACKEND_ICONS"
- from: "src/components/wizard/BackendSelectionStep.tsx"
to: "src/components/ui/TextFieldMD3.tsx"
via: "Search bar input"
pattern: "TextFieldMD3"
---
<objective>
Overhaul BackendSelectionStep from a flat card grid to a categorized, searchable layout with icons. The step must handle 17 backends without overwhelming the user.
Purpose: With 17 backends (up from 7), a flat grid is unusable. Categories group by IT-pro mental model, search enables quick access, icons aid visual scanning.
Output: Refactored BackendSelectionStep.tsx, updated BackendCard.tsx, new test cases.
</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/phases/13-add-remaining-rclone-remotes/13-CONTEXT.md
@.planning/phases/13-add-remaining-rclone-remotes/13-RESEARCH.md
@.planning/phases/13-add-remaining-rclone-remotes/13-01-SUMMARY.md
<interfaces>
<!-- From Plan 01 output (registry with categories) -->
From src/schemas/registry.ts (after Plan 01):
```typescript
export type BackendCategory = 'cloud-object-storage' | 'cloud-drives' | 'protocol-based';
export type BackendType = keyof typeof BACKEND_REGISTRY;
// Each entry has: displayName, description, category, fields
```
From src/components/icons/BackendIcons.tsx (from Plan 02):
```typescript
interface IconProps { className?: string; }
export const BACKEND_ICONS: Partial<Record<BackendType, React.FC<IconProps>>>;
```
From src/components/ui/TextFieldMD3.tsx:
```typescript
interface TextFieldMD3Props {
id: string;
label: string;
registration: UseFormRegisterReturn;
error?: FieldError;
required?: boolean;
helpText?: string;
// ... more props
}
```
From src/components/ui/BackendCard.tsx (current):
```typescript
interface BackendCardProps {
name: string;
description: string;
selected?: boolean;
onClick: () => void;
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add icon prop to BackendCard and build category/search into BackendSelectionStep</name>
<files>src/components/ui/BackendCard.tsx, src/components/wizard/BackendSelectionStep.tsx</files>
<action>
1. Update BackendCard to accept an optional `icon` prop:
```typescript
interface BackendCardProps {
name: string;
description: string;
selected?: boolean;
onClick: () => void;
icon?: React.ReactNode; // NEW — rendered at top-left of card
}
```
- Render icon before the name span, in a flex row: `<div className="flex items-center gap-2"><span className="w-6 h-6 shrink-0">{icon}</span><span>{name}</span></div>`
- If icon is undefined/null, render name without the icon wrapper (no empty space)
2. Refactor BackendSelectionStep to add category grouping and search:
a. Add search state: `const [searchQuery, setSearchQuery] = useState('')`
b. Add search bar ABOVE the backend cards (below the remote name input). Use a plain `<input>` styled with MD3 tokens (NOT TextFieldMD3 — it uses placeholder=" " which conflicts with a search placeholder). Style it as:
```
className="w-full px-4 py-2.5 rounded-xl border border-outline bg-surface-container text-on-surface placeholder:text-on-surface-variant/50 focus:outline-none focus:ring-2 focus:ring-primary"
placeholder="Search backends..."
```
Use `onChange={(e) => setSearchQuery(e.target.value)}` — no debounce needed for 17 items.
c. Add category constants:
```typescript
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',
};
```
d. Add matchesSearch function that checks displayName, description, category label, and field labels:
```typescript
function matchesSearch(entry: { displayName: string; description: string; category: BackendCategory; fields: { label: string }[] }, query: string): boolean {
const q = query.toLowerCase();
return entry.displayName.toLowerCase().includes(q)
|| entry.description.toLowerCase().includes(q)
|| CATEGORY_LABELS[entry.category].toLowerCase().includes(q)
|| entry.fields.some(f => f.label.toLowerCase().includes(q));
}
```
e. Replace the flat `data-testid="backend-cards"` grid with category sections:
```tsx
{CATEGORY_ORDER.map(cat => {
const backends = Object.entries(BACKEND_REGISTRY)
.filter(([, e]) => e.category === cat)
.filter(([, e]) => !searchQuery || matchesSearch(e, searchQuery));
if (backends.length === 0) return null;
return (
<section key={cat} className="mb-6">
<h3 className="text-lg font-semibold text-on-surface mb-3">{CATEGORY_LABELS[cat]}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{backends.map(([type, entry]) => (
<BackendCard
key={type}
name={entry.displayName}
description={entry.description}
selected={state.remote.backendType === type}
onClick={() => handleCardClick(type as BackendType)}
icon={BACKEND_ICONS[type as BackendType] ? React.createElement(BACKEND_ICONS[type as BackendType]!, { className: 'w-6 h-6' }) : undefined}
/>
))}
</div>
</section>
);
})}
```
f. Keep the existing remote name TextFieldMD3 and RemoteNamePreview at the top. Keep the Next button at the bottom. Keep form validation logic unchanged.
g. Keep `data-testid="backend-cards"` on a wrapping div around all category sections so existing tests that query within it still work.
3. Import BACKEND_ICONS from '../icons/BackendIcons' and BackendCategory from '../../schemas/registry'.
</action>
<verify>
<automated>npx tsc --noEmit 2>&1 | tail -10 && npx vitest run src/components/wizard/BackendSelectionStep.test.tsx --reporter=verbose 2>&1 | tail -30</automated>
</verify>
<done>BackendSelectionStep renders 17 backends grouped by category with search bar. BackendCard shows icon when provided. Existing tests pass.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Add search filter and category collapse tests</name>
<files>src/components/wizard/BackendSelectionStep.test.tsx</files>
<behavior>
- Test 1: All three category headings render when no search query
- Test 2: Typing "ftp" in search shows FTP and SFTP, hides unrelated backends
- Test 3: Typing a query that matches no backends shows no category headings
- Test 4: Searching "Cloudflare" finds S3-Compatible (via description match)
- Test 5: All 17 backend cards render when no search active
- Test 6: Category heading is not rendered when all its backends are filtered out
</behavior>
<action>
Add new test cases to the existing BackendSelectionStep.test.tsx file. Use the existing test setup pattern (WizardProvider wrapper, render helper).
For search tests:
- Find the search input by placeholder "Search backends..."
- Use `fireEvent.change()` or `userEvent.type()` to enter search text
- Assert on presence/absence of backend card text and category headings
For category tests:
- Assert "Cloud Object Storage", "Cloud Drives", "Protocol-based" headings are present
- After filtering, assert missing headings are NOT in the document
Write tests first (RED), verify they fail, then ensure Task 1 implementation makes them pass (GREEN).
</action>
<verify>
<automated>npx vitest run src/components/wizard/BackendSelectionStep.test.tsx --reporter=verbose</automated>
</verify>
<done>6 new test cases pass covering search filtering, category heading visibility, cross-field search matching, and full backend card rendering.</done>
</task>
</tasks>
<verification>
- `npx vitest run` — full suite green
- `npx tsc --noEmit` — no type errors
- BackendSelectionStep renders categories, search works, icons display
- No empty category headings when search filters out all backends in a category
</verification>
<success_criteria>
- 17 backends rendered in 3 category sections with headings
- Search bar filters instantly across displayName, description, category, field labels
- Empty categories hidden (conditional rendering, not CSS)
- Backend cards show inline SVG icons
- Existing remote name validation unchanged
- All tests pass (existing + 6 new)
</success_criteria>
<output>
After completion, create `.planning/phases/13-add-remaining-rclone-remotes/13-03-SUMMARY.md`
</output>