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:
@@ -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>
|
||||
Reference in New Issue
Block a user