291 lines
14 KiB
Markdown
291 lines
14 KiB
Markdown
---
|
|
phase: 12-library-crud-data-integrity
|
|
plan: 02
|
|
type: execute
|
|
wave: 2
|
|
depends_on: [12-01]
|
|
files_modified:
|
|
- frontend/src/components/config-page/config-page.ts
|
|
- frontend/src/components/sidebar/app-sidebar.ts
|
|
- frontend/index.ts
|
|
autonomous: false
|
|
requirements: [LIB-01, LIB-02, LIB-03, LIB-06]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "User sees a library list in the settings page showing name, path, and track count for each library"
|
|
- "User can click 'Add Library' to open a folder picker, library auto-names from folder and scan starts"
|
|
- "User can rename a library inline (click name or overflow menu) with Enter to save, Escape to cancel"
|
|
- "User sees a confirmation dialog with real impact counts before library removal"
|
|
- "User sees a toast notification with removal summary after library is removed"
|
|
- "The sidebar no longer has a 'Libraries' navigation item"
|
|
artifacts:
|
|
- path: "frontend/src/components/config-page/config-page.ts"
|
|
provides: "Library management section with list, add, rename, remove, toast"
|
|
contains: "renderLibraryList"
|
|
- path: "frontend/src/components/sidebar/app-sidebar.ts"
|
|
provides: "Sidebar without 'libraries' nav item"
|
|
- path: "frontend/index.ts"
|
|
provides: "No 'libraries' view case in router"
|
|
key_links:
|
|
- from: "frontend/src/components/config-page/config-page.ts"
|
|
to: "@go/library/Library"
|
|
via: "Wails bindings for AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact"
|
|
pattern: "AddLibrary|RenameLibrary|RemoveLibrary|GetRemovalImpact"
|
|
- from: "frontend/src/components/config-page/config-page.ts"
|
|
to: "frontend/src/events.ts"
|
|
via: "EventsOn for LibraryAdded, LibraryRenamed, LibraryRemoved"
|
|
pattern: "Events\\.Library(Added|Renamed|Removed)"
|
|
---
|
|
|
|
<objective>
|
|
Replace the config-page library section with a full library management UI: library list with track counts, Add Library button with folder picker, inline rename, remove with impact dialog and toast, overflow menus. Remove sidebar "Libraries" nav item and its view routing.
|
|
|
|
Purpose: Users can manage their music libraries entirely from the settings page per user decisions.
|
|
Output: Updated config-page with library CRUD UI, cleaned-up sidebar and router.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md
|
|
@/home/caleb/.config/opencode/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/12-library-crud-data-integrity/12-RESEARCH.md
|
|
@.planning/phases/12-library-crud-data-integrity/12-CONTEXT.md
|
|
@.planning/phases/12-library-crud-data-integrity/12-01-SUMMARY.md
|
|
|
|
@frontend/src/components/config-page/config-page.ts
|
|
@frontend/src/components/sidebar/app-sidebar.ts
|
|
@frontend/src/components/library-manager/library-manager.ts
|
|
@frontend/index.ts
|
|
@frontend/src/store/library-store.ts
|
|
@frontend/src/events.ts
|
|
|
|
<interfaces>
|
|
<!-- Backend Wails bindings available after Plan 01 -->
|
|
|
|
From backend/library/crud.go (via Wails auto-generated bindings):
|
|
```typescript
|
|
// @go/library/Library
|
|
export function AddLibrary(path: string): Promise<library.Library>;
|
|
export function RenameLibrary(id: number, newName: string): Promise<void>;
|
|
export function RemoveLibrary(id: number): Promise<library.RemovalSummary>;
|
|
export function GetRemovalImpact(id: number): Promise<library.RemovalImpact>;
|
|
```
|
|
|
|
From backend/library/query.go (existing bindings):
|
|
```typescript
|
|
export function GetAllLibraries(): Promise<sqlcgen.Library[]>; // via database queries
|
|
```
|
|
|
|
From backend/database/sql/queries/libraries.sql (existing):
|
|
```typescript
|
|
// GetAllLibraries returns [{id, name, path, created_at}]
|
|
// CountAudioFilesByLibrary returns {count}
|
|
```
|
|
|
|
From frontend/src/events.ts (regenerated in Plan 01):
|
|
```typescript
|
|
export const Events = {
|
|
// ...existing events...
|
|
LibraryAdded: "LibraryAdded",
|
|
LibraryRenamed: "LibraryRenamed",
|
|
LibraryRemoved: "LibraryRemoved",
|
|
} as const;
|
|
```
|
|
|
|
From frontend/src/components/config-page/config-page.ts (existing patterns):
|
|
```typescript
|
|
// ConfigPage uses @state() decorators for reactive state
|
|
// renderXxxSection() methods for each settings section
|
|
// EventsOn() in connectedCallback for event subscriptions
|
|
// config-field component for form fields
|
|
// Scan state tracking: scanning, scanPaused, scanProgress, etc.
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Replace config-page library section with library management UI</name>
|
|
<files>
|
|
frontend/src/components/config-page/config-page.ts
|
|
</files>
|
|
<action>
|
|
Replace the existing `renderLibrarySection()` method in config-page.ts with a full library management UI. The section currently shows a single directory path field + rescan button. Replace it with:
|
|
|
|
**New state properties (add to class):**
|
|
```typescript
|
|
@state() private libraries: Array<{id: number; name: string; path: string; trackCount: number}> = [];
|
|
@state() private editingLibraryId: number | null = null;
|
|
@state() private editingName: string = '';
|
|
@state() private removingLibraryId: number | null = null;
|
|
@state() private removalImpact: {trackCount: number; playlistsAffected: number; queueItemCount: number} | null = null;
|
|
@state() private isRemoving: boolean = false;
|
|
@state() private toastMessage: string = '';
|
|
@state() private toastVisible: boolean = false;
|
|
@state() private activeMenuId: number | null = null;
|
|
```
|
|
|
|
**Load library list:**
|
|
- In `connectedCallback` (or existing initialization), call `GetAllLibraries()` from Wails bindings, then for each library call `CountAudioFilesByLibrary(lib.id)` to get track counts (or add a new Go method that returns libraries with counts — but simpler to loop since there are typically 1-5 libraries).
|
|
- Actually, better approach: Create a `loadLibraries()` method that calls `GetAllLibraries()` and maps results, enriching each with a `CountAudioFilesByLibrary` call. Store in `this.libraries`.
|
|
- Call `loadLibraries()` on connectedCallback and after any CRUD event.
|
|
|
|
**Event subscriptions (add to connectedCallback):**
|
|
```typescript
|
|
EventsOn(Events.LibraryAdded, () => this.loadLibraries());
|
|
EventsOn(Events.LibraryRenamed, () => this.loadLibraries());
|
|
EventsOn(Events.LibraryRemoved, () => this.loadLibraries());
|
|
```
|
|
|
|
**Remove old library config state:**
|
|
Remove the `directoryPath` state property, `loadLibraryConfig()` method, `GetLibraryDirectory` and `SetLibraryDirectory` imports (these are legacy single-directory methods). Remove the old `config-field` for Library Directory.
|
|
|
|
**renderLibrarySection() — complete replacement:**
|
|
The section heading should be "Libraries" (not "Library"). Use `<config-section heading="Libraries">`.
|
|
|
|
Content:
|
|
1. **Library list** — For each library in `this.libraries`, render a row:
|
|
- If `this.editingLibraryId === lib.id`: render an input field with the editing name, Enter to save (call `RenameLibrary`), Escape to cancel
|
|
- Else: render `<span class="library-name">${lib.name}</span>`, `<span class="library-path">${lib.path}</span>`, `<span class="library-count">${lib.trackCount} tracks</span>`, and an overflow `...` button
|
|
- The overflow button toggles `this.activeMenuId` — when active, shows a dropdown with: Rename, Rescan, Remove
|
|
- Rename: sets `this.editingLibraryId = lib.id; this.editingName = lib.name`
|
|
- Rescan: calls `ScanLibrary(lib.id)` from existing Wails bindings
|
|
- Remove: calls `GetRemovalImpact(lib.id)`, stores result in `this.removalImpact`, sets `this.removingLibraryId = lib.id` to show the confirmation dialog
|
|
- Click outside overflow menu closes it (add a document click listener)
|
|
|
|
2. **Add Library button** — Below the list:
|
|
```html
|
|
<button class="btn-primary" @click=${this.handleAddLibrary}>Add Library</button>
|
|
```
|
|
`handleAddLibrary`: Call `DirectoryPicker()` from `@go/frontendutil/FrontendUtil`. If user selects a path, call `AddLibrary(path)`. The backend auto-names from folder name and triggers scan.
|
|
|
|
3. **Removal confirmation dialog** — Shown when `this.removingLibraryId !== null`:
|
|
- Overlay with dialog box (same pattern as cancel scan dialog in library-manager.ts)
|
|
- Title: "Remove Library"
|
|
- Message: `Remove '${libraryName}'? This will delete ${impact.trackCount} tracks, affect ${impact.playlistsAffected} playlists, and remove ${impact.queueItemCount} queue items.`
|
|
- Two buttons: "Cancel" (closes dialog) and "Remove" (calls `RemoveLibrary(id)`)
|
|
- When "Remove" is clicked: set `this.isRemoving = true` to show a spinner. On completion: close dialog, show toast with summary, reload libraries.
|
|
|
|
4. **Toast notification** — A simple div at the bottom of the component:
|
|
```html
|
|
${this.toastVisible ? html`<div class="toast">${this.toastMessage}</div>` : nothing}
|
|
```
|
|
`showToast(message: string)` method: sets `this.toastMessage`, `this.toastVisible = true`, then `setTimeout(() => this.toastVisible = false, 4000)`.
|
|
After successful removal: `this.showToast("Removed '${name}' (${summary.tracksDeleted} tracks deleted)")`.
|
|
|
|
**Scan actions integration:**
|
|
Keep the existing scan actions (Soft Scan, Full Rescan, Scan All Libraries, Pause, Resume, Cancel) below the library list — they operate on the currently scanning library. The progress bar and scan status remain unchanged.
|
|
|
|
Remove the old library directory `config-field` and `SetLibraryDirectory` logic entirely.
|
|
|
|
**Styling (add to static styles):**
|
|
- `.library-list` — flex column with gap
|
|
- `.library-row` — flex row with items center, padding, border-bottom, hover state
|
|
- `.library-name` — flex: 1, clickable for rename
|
|
- `.library-path` — color: dimmed, font-size smaller, truncate with ellipsis
|
|
- `.library-count` — color: dimmed
|
|
- `.overflow-btn` — cursor pointer, no border, background transparent, letter-spacing for "···"
|
|
- `.overflow-menu` — absolute position, background surface, border, shadow, z-index, list items with hover
|
|
- `.edit-input` — styled text input for inline rename
|
|
- `.removal-dialog-overlay` — fixed full screen, background semi-transparent
|
|
- `.removal-dialog` — centered box, background surface, padding, rounded corners
|
|
- `.toast` — fixed bottom center, background surface, padding, border-radius, box-shadow, animation (fade in/out via CSS transition on opacity)
|
|
- `.spinner` — simple CSS spinner (border animation)
|
|
|
|
Use design tokens where applicable (--yj-text-sm for paths/counts, etc.).
|
|
</action>
|
|
<verify>
|
|
<automated>cd /mnt/vault/dev/golang/yellowjacket && npx tsc --noEmit</automated>
|
|
</verify>
|
|
<done>
|
|
- Config-page shows library list with name, path, track count per library
|
|
- Add Library button opens folder picker and creates library
|
|
- Inline rename with Enter/Escape works
|
|
- Overflow menu shows Rename, Rescan, Remove actions
|
|
- Removal dialog shows real impact counts
|
|
- Toast notification shows after removal
|
|
- Old single-directory library config UI is removed
|
|
- TypeScript compiles with no errors
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Remove Libraries sidebar nav item and view routing</name>
|
|
<files>
|
|
frontend/src/components/sidebar/app-sidebar.ts
|
|
frontend/index.ts
|
|
</files>
|
|
<action>
|
|
Per user decision: "Remove the libraries tab from the sidebar list entirely."
|
|
|
|
**app-sidebar.ts:**
|
|
1. Remove `'libraries'` from the `View` type union: change `'home' | 'libraries' | 'playlists' | ...` to `'home' | 'playlists' | ...`
|
|
2. Remove the `{ id: 'libraries', label: 'Libraries', icon: 'folder-open' }` entry from the nav items array
|
|
|
|
**index.ts:**
|
|
1. Remove the `case 'libraries':` block that sets `mainContent.innerHTML = '<library-manager></library-manager>'`
|
|
2. Remove the `import '@components/library-manager/library-manager.ts'` import (the component is no longer used)
|
|
|
|
Note: Do NOT delete the `library-manager.ts` file itself — it may still be referenced elsewhere or useful for reference. Just remove its import and routing.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /mnt/vault/dev/golang/yellowjacket && npx tsc --noEmit</automated>
|
|
</verify>
|
|
<done>
|
|
- Sidebar does not show "Libraries" nav item
|
|
- Clicking where Libraries was no longer routes to library-manager view
|
|
- library-manager component import removed from index.ts
|
|
- TypeScript compiles with no errors
|
|
</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 3: Verify library management UI end-to-end</name>
|
|
<files>frontend/src/components/config-page/config-page.ts</files>
|
|
<action>
|
|
Human verification of the complete library management UI.
|
|
|
|
Launch the app with `wails dev` and verify:
|
|
1. Navigate to Settings — "Libraries" section shows existing library with name, path, and track count
|
|
2. Click "Add Library" — folder picker opens. Select a folder with music. Library appears in list and scan starts.
|
|
3. Click `...` overflow menu — Rename, Rescan, Remove options appear
|
|
4. Click Rename — name becomes editable. Type new name, press Enter. Name updates.
|
|
5. Press Escape while editing — rename is cancelled
|
|
6. Click Remove on a test library — confirmation dialog shows real impact counts
|
|
7. Click Remove in dialog — spinner shows, then toast notification with removal summary
|
|
8. Sidebar no longer has "Libraries" nav item
|
|
9. Scan controls (Soft Scan, Full Rescan, Scan All, Pause, Cancel) still work
|
|
</action>
|
|
<verify>Manual verification — all 9 checks pass</verify>
|
|
<done>Library management UI works end-to-end: add, rename, remove with correct data lifecycle</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
1. `npx tsc --noEmit` — TypeScript compiles with no errors
|
|
2. `wails dev` — app launches without errors
|
|
3. Library list shows in settings with correct data
|
|
4. Add/rename/remove flows work end-to-end
|
|
5. Sidebar has no "Libraries" item
|
|
6. Scan controls still function
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Library management UI replaces old single-directory config in settings page
|
|
- All CRUD operations work: add (with folder picker + auto-scan), rename (inline edit), remove (with confirmation + toast)
|
|
- Sidebar "Libraries" nav item is removed
|
|
- No TypeScript compilation errors
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/12-library-crud-data-integrity/12-02-SUMMARY.md`
|
|
</output>
|