Files
2026-04-01 16:04:24 +02:00

10 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
13-add-remaining-rclone-remotes 03 execute 3
13-01
13-02
src/components/wizard/BackendSelectionStep.tsx
src/components/ui/BackendCard.tsx
src/components/wizard/BackendSelectionStep.test.tsx
true
REMOTE-03
truths artifacts key_links
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
path provides contains
src/components/wizard/BackendSelectionStep.tsx Category-grouped, searchable backend selection with icons CATEGORY_ORDER
path provides
src/components/ui/BackendCard.tsx Backend card with optional icon prop
path provides
src/components/wizard/BackendSelectionStep.test.tsx Tests for search filter and category collapse
from to via pattern
src/components/wizard/BackendSelectionStep.tsx src/schemas/registry.ts BACKEND_REGISTRY iteration with category grouping BACKEND_REGISTRY.*category
from to via pattern
src/components/wizard/BackendSelectionStep.tsx src/components/icons/BackendIcons.tsx BACKEND_ICONS import for card rendering BACKEND_ICONS
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.

<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>

@.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 @.planning/phases/13-add-remaining-rclone-remotes/13-02-SUMMARY.md

From src/schemas/registry.ts (after Plan 01):

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):

interface IconProps { className?: string; }
export const BACKEND_ICONS: Partial<Record<BackendType, React.FC<IconProps>>>;

From src/components/ui/TextFieldMD3.tsx:

interface TextFieldMD3Props {
  id: string;
  label: string;
  registration: UseFormRegisterReturn;
  error?: FieldError;
  required?: boolean;
  helpText?: string;
  // ... more props
}

From src/components/ui/BackendCard.tsx (current):

interface BackendCardProps {
  name: string;
  description: string;
  selected?: boolean;
  onClick: () => void;
}
Task 1: Add icon prop to BackendCard and build category/search into BackendSelectionStep src/components/ui/BackendCard.tsx, src/components/wizard/BackendSelectionStep.tsx 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: `
{icon}{name}
` - If icon is undefined/null, render name without the icon wrapper (no empty space)
  1. 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:

    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:

    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:

    {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.

  2. Import BACKEND_ICONS from '../icons/BackendIcons' and BackendCategory from '../../schemas/registry'. npx tsc --noEmit 2>&1 | tail -10 && npx vitest run src/components/wizard/BackendSelectionStep.test.tsx --reporter=verbose 2>&1 | tail -30 BackendSelectionStep renders 17 backends grouped by category with search bar. BackendCard shows icon when provided. Existing tests pass.

Task 2: Add search filter and category collapse tests src/components/wizard/BackendSelectionStep.test.tsx - 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 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). npx vitest run src/components/wizard/BackendSelectionStep.test.tsx --reporter=verbose 6 new test cases pass covering search filtering, category heading visibility, cross-field search matching, and full backend card rendering.

- `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

<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>
After completion, create `.planning/phases/13-add-remaining-rclone-remotes/13-03-SUMMARY.md`