Files
yellowjacket/.planning/phases/12-library-crud-data-integrity/12-02-PLAN.md
T
2026-03-12 17:46:08 -04:00

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
12-01
frontend/src/components/config-page/config-page.ts
frontend/src/components/sidebar/app-sidebar.ts
frontend/index.ts
false
LIB-01
LIB-02
LIB-03
LIB-06
truths artifacts key_links
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
path provides contains
frontend/src/components/config-page/config-page.ts Library management section with list, add, rename, remove, toast renderLibraryList
path provides
frontend/src/components/sidebar/app-sidebar.ts Sidebar without 'libraries' nav item
path provides
frontend/index.ts No 'libraries' view case in router
from to via pattern
frontend/src/components/config-page/config-page.ts @go/library/Library Wails bindings for AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact AddLibrary|RenameLibrary|RemoveLibrary|GetRemovalImpact
from to via pattern
frontend/src/components/config-page/config-page.ts frontend/src/events.ts EventsOn for LibraryAdded, LibraryRenamed, LibraryRemoved Events.Library(Added|Renamed|Removed)
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.

<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.
Task 1: Replace config-page library section with library management UI frontend/src/components/config-page/config-page.ts 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):

@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):

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:

    <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:

    ${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.). 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:

  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. 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

Task 3: Verify library management UI end-to-end frontend/src/components/config-page/config-page.ts 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 Manual verification — all 9 checks pass Library management UI works end-to-end: add, rename, remove with correct data lifecycle
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

<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>
After completion, create `.planning/phases/12-library-crud-data-integrity/12-02-SUMMARY.md`