14 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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12-library-crud-data-integrity | 02 | execute | 2 |
|
|
false |
|
|
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.
<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>
@.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
From backend/library/crud.go (via Wails auto-generated bindings):
// @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):
export function GetAllLibraries(): Promise<sqlcgen.Library[]>; // via database queries
From backend/database/sql/queries/libraries.sql (existing):
// GetAllLibraries returns [{id, name, path, created_at}]
// CountAudioFilesByLibrary returns {count}
From frontend/src/events.ts (regenerated in Plan 01):
export const Events = {
// ...existing events...
LibraryAdded: "LibraryAdded",
LibraryRenamed: "LibraryRenamed",
LibraryRemoved: "LibraryRemoved",
} as const;
From frontend/src/components/config-page/config-page.ts (existing patterns):
// 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.
New state properties (add to class):
@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), callGetAllLibraries()from Wails bindings, then for each library callCountAudioFilesByLibrary(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 callsGetAllLibraries()and maps results, enriching each with aCountAudioFilesByLibrarycall. Store inthis.libraries. - Call
loadLibraries()on connectedCallback and after any CRUD event.
Event subscriptions (add to connectedCallback):
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:
-
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 (callRenameLibrary), 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 inthis.removalImpact, setsthis.removingLibraryId = lib.idto show the confirmation dialog - Click outside overflow menu closes it (add a document click listener)
- If
-
Add Library button — Below the list:
<button class="btn-primary" @click=${this.handleAddLibrary}>Add Library</button>handleAddLibrary: CallDirectoryPicker()from@go/frontendutil/FrontendUtil. If user selects a path, callAddLibrary(path). The backend auto-names from folder name and triggers scan. -
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 = trueto show a spinner. On completion: close dialog, show toast with summary, reload libraries.
-
Toast notification — A simple div at the bottom of the component:
${this.toastVisible ? html`<div class="toast">${this.toastMessage}</div>` : nothing}showToast(message: string)method: setsthis.toastMessage,this.toastVisible = true, thensetTimeout(() => 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.). cd /mnt/vault/dev/golang/yellowjacket && npx tsc --noEmit - 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
Task 2: Remove Libraries sidebar nav item and view routing frontend/src/components/sidebar/app-sidebar.ts frontend/index.ts Per user decision: "Remove the libraries tab from the sidebar list entirely."app-sidebar.ts:
- Remove
'libraries'from theViewtype union: change'home' | 'libraries' | 'playlists' | ...to'home' | 'playlists' | ... - Remove the
{ id: 'libraries', label: 'Libraries', icon: 'folder-open' }entry from the nav items array
index.ts:
- Remove the
case 'libraries':block that setsmainContent.innerHTML = '<library-manager></library-manager>' - 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.
cd /mnt/vault/dev/golang/yellowjacket && npx tsc --noEmit
- 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
Launch the app with wails dev and verify:
- Navigate to Settings — "Libraries" section shows existing library with name, path, and track count
- Click "Add Library" — folder picker opens. Select a folder with music. Library appears in list and scan starts.
- Click
...overflow menu — Rename, Rescan, Remove options appear - Click Rename — name becomes editable. Type new name, press Enter. Name updates.
- Press Escape while editing — rename is cancelled
- Click Remove on a test library — confirmation dialog shows real impact counts
- Click Remove in dialog — spinner shows, then toast notification with removal summary
- Sidebar no longer has "Libraries" nav item
- Scan controls (Soft Scan, Full Rescan, Scan All, Pause, Cancel) still work Manual verification — all 9 checks pass Library management UI works end-to-end: add, rename, remove with correct data lifecycle
<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>