Files
ClaudeDo/docs/superpowers/specs/2026-05-29-repo-import-list-helper-design.md
mika kuns 7869c2a979 docs: add repo import list helper design spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:26:32 +02:00

6.2 KiB

Repo Import List Helper — Design

Date: 2026-05-29 Status: Approved (pending spec review)

Problem

Creating lists is one-at-a-time: click + New list, then open List Settings to set the working directory. Users with many repos under a few parent folders want to wire them all up in one pass.

Goal

A "list helper" that scans one or more parent folders for git repos, presents them as a checklist, and bulk-creates a list (with WorkingDir pre-filled) for each ticked repo.

Entry Points

  1. Help menu — the title-bar dropdown in MainWindow.axaml that contains About…, Worktrees…, etc. Add a new MenuItem Add repos as lists… wired to a command on MainWindowViewModel.
  2. Lists island — a small folder icon button beside the existing + New list button in ListsIslandView.axaml, wired to a command on ListsIslandViewModel.

Both open the same modal.

Components

RepoScanner (new, ClaudeDo.Ui/Services or ClaudeDo.Data)

Pure filesystem helper, no git library. Given a parent folder path, enumerates immediate subdirectories and returns those that contain a .git entry (directory or file). Kept separate from the VM so it is unit-testable.

IReadOnlyList<RepoCandidate> Scan(string parentFolder)
record RepoCandidate(string Name, string FullPath)
  • Skips the parent itself; only immediate children are considered (non-recursive).
  • .git may be a directory (normal repo) or a file (worktree/submodule) — both count.
  • Returns empty on missing/unreadable folder rather than throwing.

RepoImportModalViewModel (new, ClaudeDo.Ui/ViewModels/Modals)

Follows the existing modal-VM pattern (CloseAction, resolved from DI).

Dependencies:

  • IDbContextFactory<ClaudeDoDbContext> — load existing lists' WorkingDir values (for the "already added" check) and create new ListEntity rows. Same dependency ListsIslandViewModel already uses.

State:

  • ObservableCollection<RepoImportItemViewModel> Repos — the combined checklist.
  • A set of parent folder paths already scanned (to de-dupe re-adds).
  • CreateCount — computed count of ticked-and-new rows (drives the confirm button label).

Commands:

  • AddFolderAsync — invokes the folder picker (via view code-behind callback, see below), scans each chosen folder with RepoScanner, appends new candidates. De-dupes by full path (case-insensitive) against rows already present.
  • CreateAsync — for each ticked, non-existing row, create a ListEntity via ListRepository.AddAsync (Name = folder name, WorkingDir = full path, DefaultCommitType = CommitTypeRegistry.DefaultType, fresh Guid id, CreatedAt = now). Then CloseAction().
  • CancelCloseAction().

On load, fetch all existing lists once and capture their WorkingDirs into a case-insensitive set; each appended candidate whose path is in that set is marked AlreadyAdded.

RepoImportItemViewModel (new)

  • Name, FullPath (display).
  • AlreadyAdded (bool) — true if a list already points at this path.
  • IsChecked ([ObservableProperty]) — defaults true for new repos. For already-added rows it is forced true and the checkbox is disabled.
  • CanToggle => !AlreadyAdded (binds to checkbox IsEnabled).

RepoImportModalView (new, ClaudeDo.Ui/Views/Modals)

A Window styled like the other modals (header bar, body, footer), shown via ShowDialog(owner).

  • Header: title ADD REPOS AS LISTS + close button.
  • Top of body: Add folder… button.
  • Body: scrollable ItemsControl over Repos. Each row = CheckBox (IsChecked two-way, IsEnabled = CanToggle) + repo name + dim full path + (already added) label when AlreadyAdded.
  • Footer: Create {CreateCount} lists button (disabled when CreateCount == 0) + Cancel.
  • Folder picker lives in the code-behind (mirrors ListSettingsModalView.BrowseClicked): OpenFolderPickerAsync with AllowMultiple = true, results handed to the VM's AddFolderAsync.

Data Flow

  1. User opens the modal from either entry point → modal loads existing lists' WorkingDirs.
  2. User clicks Add folder… → picks one or more parent folders → RepoScanner finds repos → rows appended (de-duped), already-added rows shown ticked+disabled.
  3. User adjusts ticks → clicks Create N lists.
  4. VM creates one ListEntity per ticked-new row via ListRepository.
  5. Modal closes → the caller reloads the Lists island so new lists appear:
    • Lists-island entry point: ListsIslandViewModel.LoadAsync().
    • Help-menu entry point: MainWindowViewModel reloads its Lists (the ListsIslandViewModel instance) after the modal closes.

DI / Wiring

  • Register RepoImportModalViewModel (transient) alongside other modal VMs.
  • Register RepoScanner if implemented as an injected service; a static helper needs no registration.
  • ListsIslandViewModel gains Func<RepoImportModalViewModel, Task>? ShowRepoImportModal and an OpenRepoImportCommand, wired in ListsIslandView.axaml.cs (mirrors ShowListSettingsModal).
  • MainWindowViewModel gains the same Func + an OpenRepoImportCommand, wired in MainWindow.axaml.cs.

Error Handling

  • Unreadable / missing folders: RepoScanner returns empty, no crash.
  • Re-adding a folder already scanned: de-duped by path, no duplicate rows.
  • Two ticked repos sharing a folder name: both created (list names are not unique) — acceptable.
  • List creation failure (rare): best-effort per the existing pattern; do not block remaining creations.

Testing

  • RepoScanner unit tests (the testable seam): a temp directory tree with a mix of git repos (.git dir), a .git-file repo, plain folders, and an empty/missing parent. Assert only the repo subfolders are returned and missing folders yield empty.
  • VM-level "already added" logic and CreateCount can be exercised if a test seam is convenient, but the filesystem scanner is the primary unit under test. UI wiring verified manually.

Out of Scope (YAGNI)

  • Recursive / deep scanning.
  • Inline editing of the list name before creation.
  • Setting model / system prompt / agent during import (tuned later per-list in List Settings).
  • Picking repo folders directly (only parent-folder scan, per decision).