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>
247 lines
10 KiB
Markdown
247 lines
10 KiB
Markdown
---
|
|
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>
|