From 7f52dce29629321b5dddbabcb5fd428e431c8c7d Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Sat, 28 Feb 2026 13:18:49 -0500 Subject: [PATCH] docs(quick-001): plan multi-playlist import support --- .../001-PLAN.md | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 .planning/quick/001-multi-playlist-import-support/001-PLAN.md diff --git a/.planning/quick/001-multi-playlist-import-support/001-PLAN.md b/.planning/quick/001-multi-playlist-import-support/001-PLAN.md new file mode 100644 index 0000000..322e144 --- /dev/null +++ b/.planning/quick/001-multi-playlist-import-support/001-PLAN.md @@ -0,0 +1,266 @@ +--- +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` +