--- phase: quick-001 plan: 01 type: execute wave: 1 depends_on: [] files_modified: - backend/frontendutil/frontendutil.go - backend/playlist/playlist.go - frontend/src/components/playlist-view/playlist-view.ts autonomous: true requirements: [MULTI-IMPORT] must_haves: truths: - "File picker allows selecting multiple M3U/M3U8 files at once" - "All selected playlists are imported sequentially into the database" - "Each imported playlist emits a PlaylistCreated event and appears in the UI" - "Cancelling the file picker (selecting nothing) is a no-op" - "Errors during individual imports are collected and reported" artifacts: - path: "backend/frontendutil/frontendutil.go" provides: "Multi-file picker returning []string" contains: "OpenMultipleFilesDialog" - path: "backend/playlist/playlist.go" provides: "ImportPlaylists batch method" contains: "func (s *Service) ImportPlaylists" - path: "frontend/src/components/playlist-view/playlist-view.ts" provides: "Updated import handler calling batch API" contains: "ImportPlaylists" key_links: - from: "frontend/src/components/playlist-view/playlist-view.ts" to: "backend/frontendutil/frontendutil.go" via: "Wails binding PlaylistFilePicker" pattern: "PlaylistFilePicker" - from: "frontend/src/components/playlist-view/playlist-view.ts" to: "backend/playlist/playlist.go" via: "Wails binding ImportPlaylists" pattern: "ImportPlaylists" --- Make the "Import Playlist" feature support selecting and importing multiple M3U/M3U8 files at once. Purpose: Users often have several playlist files to import — forcing one-at-a-time selection is tedious. Output: Updated backend methods, regenerated Wails bindings, and updated frontend handler. @/home/caleb/.config/Claude/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/Claude/get-shit-done/templates/summary.md @backend/frontendutil/frontendutil.go @backend/playlist/playlist.go @frontend/src/components/playlist-view/playlist-view.ts @frontend/wailsjs/go/frontendutil/FrontendUtil.d.ts @frontend/wailsjs/go/playlist/Service.d.ts From backend/frontendutil/frontendutil.go: ```go func (fe *FrontendUtil) PlaylistFilePicker() (string, error) // Uses runtime.OpenFileDialog — single file selection ``` From backend/playlist/playlist.go: ```go func (s *Service) ImportPlaylist(filePath string) (Summary, error) // Imports a single M3U/M3U8 file, creates DB entry, emits PlaylistCreated event type Summary struct { ID int64 `json:"ID"` Name string `json:"Name"` } var errEmptyFilePath = errors.New("file path cannot be empty") var errNoFilePaths = errors.New("no file paths provided") var errUnsupportedFileType = errors.New("unsupported file type") ``` From Wails runtime API: ```go func OpenMultipleFilesDialog(ctx context.Context, dialogOptions OpenDialogOptions) ([]string, error) ``` From frontend bindings: ```typescript // Current: export function PlaylistFilePicker(): Promise; export function ImportPlaylist(arg1: string): Promise; // After change (auto-generated): // PlaylistFilePicker(): Promise>; // ImportPlaylists(arg1: Array): Promise>; ``` Task 1: Update backend — multi-file picker and batch import backend/frontendutil/frontendutil.go backend/playlist/playlist.go 1. In `backend/frontendutil/frontendutil.go`, update `PlaylistFilePicker()`: - Change return type from `(string, error)` to `([]string, error)` - Replace `runtime.OpenFileDialog(...)` with `runtime.OpenMultipleFilesDialog(...)` using the same `runtime.OpenDialogOptions` (Title, Filters unchanged) - Update the log message to say "selecting playlist files" - Update the error message to "could not open file dialog: %w" (keep consistent) 2. In `backend/playlist/playlist.go`, add a new exported method `ImportPlaylists` that accepts a batch of file paths. Place it directly after the existing `ImportPlaylist` method (after line 794): ```go // ImportPlaylists imports multiple playlists from external M3U/M3U8 // files. Each file is imported sequentially using ImportPlaylist. // Errors from individual imports are collected; partial success is // possible. Returns the summaries of successfully imported playlists // and the first error encountered (if any). func (s *Service) ImportPlaylists( filePaths []string, ) ([]Summary, error) { if len(filePaths) == 0 { return nil, errNoFilePaths } summaries := make([]Summary, 0, len(filePaths)) var firstErr error for _, fp := range filePaths { summary, err := s.ImportPlaylist(fp) if err != nil { s.logger.Warn( "Failed to import playlist file", "path", fp, "err", err, ) if firstErr == nil { firstErr = fmt.Errorf( "import %q failed: %w", fp, err, ) } continue } summaries = append(summaries, summary) } return summaries, firstErr } ``` Key design decisions: - Sequential, NOT parallel — SQLite lock contention avoidance per research. - Partial success — continues importing remaining files even if one fails. - Returns first error + all successful summaries so the frontend can show what worked and what didn't. - Reuses existing `ImportPlaylist` — no logic duplication. - `errNoFilePaths` sentinel already exists (line 28). Do NOT modify the existing `ImportPlaylist` method signature or behavior — it remains available for single-file import internally. cd /mnt/vault/dev/golang/yellowjacket && go vet ./backend/frontendutil/... ./backend/playlist/... - `PlaylistFilePicker()` returns `([]string, error)` and uses `OpenMultipleFilesDialog` - `ImportPlaylists([]string) ([]Summary, error)` exists and delegates to `ImportPlaylist` per file - `go vet` passes for both packages Task 2: Regenerate Wails bindings and update frontend frontend/wailsjs/go/frontendutil/FrontendUtil.js frontend/wailsjs/go/frontendutil/FrontendUtil.d.ts frontend/wailsjs/go/playlist/Service.js frontend/wailsjs/go/playlist/Service.d.ts frontend/src/components/playlist-view/playlist-view.ts 1. Regenerate Wails bindings: ``` wails generate module ``` This will update the auto-generated files at: - `frontend/wailsjs/go/frontendutil/FrontendUtil.{js,d.ts}` — `PlaylistFilePicker` return type becomes `Promise>` - `frontend/wailsjs/go/playlist/Service.{js,d.ts}` — new `ImportPlaylists` binding appears 2. In `frontend/src/components/playlist-view/playlist-view.ts`, update the imports (around line 15-17): - Change `ImportPlaylist` to `ImportPlaylists` in the import from `@go/playlist/Service` 3. Update `handleImportPlaylist` method (starting at line 1874). Replace the entire method body: ```typescript private handleImportPlaylist = async () => { try { const filePaths = await PlaylistFilePicker(); if (!filePaths || filePaths.length === 0) return; this.importError = ''; await ImportPlaylists(filePaths); } catch (err) { console.error( 'Failed to import playlist:', err, ); this.importError = err instanceof Error ? err.message : String(err); setTimeout(() => { this.importError = ''; }, 6000); } }; ``` Key changes: - `PlaylistFilePicker()` now returns `string[]` — check for empty array instead of falsy string - Call `ImportPlaylists(filePaths)` instead of `ImportPlaylist(filePath)` - Error handling logic stays the same (toast with 6s auto-clear) - No need to manually refresh — each imported playlist fires `PlaylistCreated` event which triggers the existing reactive refresh via `PlaylistController` cd /mnt/vault/dev/golang/yellowjacket && wails generate module && cd frontend && npx tsc --noEmit - Wails bindings regenerated with new signatures - `FrontendUtil.d.ts` shows `PlaylistFilePicker(): Promise>` - `Service.d.ts` shows `ImportPlaylists(arg1: Array): Promise>` - Frontend imports `ImportPlaylists` (not `ImportPlaylist`) - `handleImportPlaylist` handles array of file paths - TypeScript compiles with no errors 1. `go vet ./backend/...` — no issues 2. `wails generate module` — succeeds 3. `npx tsc --noEmit` (from frontend/) — no type errors 4. Build check: `go build ./...` — compiles successfully - Multi-file selection dialog opens when clicking Import - Backend accepts and processes array of file paths sequentially - Frontend correctly passes array to new ImportPlaylists binding - All code compiles and type-checks cleanly After completion, create `.planning/quick/001-multi-playlist-import-support/001-SUMMARY.md`