Files
yellowjacket/.planning/phases/09-scan-cancellation-keyboard-shortcuts/09-03-PLAN.md
T

320 lines
12 KiB
Markdown

---
phase: 09-scan-cancellation-keyboard-shortcuts
plan: 03
type: execute
wave: 2
depends_on:
- 09-01
files_modified:
- frontend/src/components/config-page/config-page.ts
autonomous: true
requirements:
- SCAN-01
- SCAN-02
- SCAN-03
must_haves:
truths:
- "Cancel button appears during an active scan and calls CancelScan() Wails binding"
- "Pause button appears during an active scan and calls PauseScan() Wails binding"
- "Resume button replaces Pause when paused and calls ResumeScan() Wails binding"
- "On cancel, a confirmation dialog asks 'Keep X tracks found so far, or discard?'"
- "Keep option: scan stops, partial results remain in library"
- "Discard option: scan stops, added tracks from this scan are removed"
- "LibraryScanCancelled, LibraryScanPaused, LibraryScanResumed events update UI state"
artifacts:
- path: "frontend/src/components/config-page/config-page.ts"
provides: "Pause/Cancel/Resume buttons, cancel confirmation dialog, event handling for scan control"
contains: "handleCancelScan"
key_links:
- from: "frontend/src/components/config-page/config-page.ts"
to: "backend/library/scan_control.go"
via: "Wails bindings CancelScan/PauseScan/ResumeScan"
pattern: "CancelScan|PauseScan|ResumeScan"
- from: "frontend/src/components/config-page/config-page.ts"
to: "backend/events/events.go"
via: "EventsOn for LibraryScanCancelled/Paused/Resumed"
pattern: "LibraryScanCancelled|LibraryScanPaused|LibraryScanResumed"
---
<objective>
Add scan control buttons (Pause, Resume, Cancel) and a cancel confirmation dialog to the config page's library scan section.
Purpose: Frontend UX for SCAN-01/02/03. Wires to backend scan control methods from Plan 01.
Output: Modified config-page.ts with scan control UI, event handling, and cancel confirmation.
</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/09-scan-cancellation-keyboard-shortcuts/09-RESEARCH.md
@.planning/phases/09-scan-cancellation-keyboard-shortcuts/09-CONTEXT.md
@.planning/phases/09-scan-cancellation-keyboard-shortcuts/09-01-SUMMARY.md
@frontend/src/components/config-page/config-page.ts
@frontend/src/events.ts
<interfaces>
<!-- Scan control Wails bindings (from Plan 01) -->
// From wailsjs/go/library/Library:
export function CancelScan(): Promise<void>;
export function PauseScan(): Promise<void>;
export function ResumeScan(): Promise<void>;
export function IsScanActive(): Promise<boolean>;
export function IsScanPaused(): Promise<boolean>;
<!-- New events (from Plan 01) -->
export const LibraryScanCancelled = "LibraryScanCancelled";
export const LibraryScanPaused = "LibraryScanPaused";
export const LibraryScanResumed = "LibraryScanResumed";
<!-- ScanMetrics now has Cancelled bool (from Plan 01) -->
interface ScanMetrics {
// ... existing fields ...
cancelled: boolean;
added: number;
// ...
}
<!-- Existing scan UI state in config-page.ts -->
@state() scanning = false;
@state() statusMessage = '';
@state() scanProgress: ScanProgress | null = null;
@state() metrics: any = null;
@state() scanErrors = '';
<!-- Existing scan buttons location (config-page.ts:1327-1346) -->
<div class="scan-actions">
<button class="btn-warning" ?disabled=${this.scanning} @click=${this.handleSoftScan}>
${this.scanning ? 'Scanning...' : 'Soft Scan'}
</button>
<button class="btn-danger" ?disabled=${this.scanning} @click=${this.handleFullRescan}>
${this.scanning ? 'Scanning...' : 'Full Rescan'}
</button>
</div>
<!-- Status bar (config-page.ts:1348-1354) -->
<div class="status-bar ${this.scanning ? 'active' : ''}">
${this.scanProgress ? this.renderScanProgress() : this.statusMessage || 'Ready.'}
</div>
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add scan control state, event handlers, and UI buttons</name>
<files>frontend/src/components/config-page/config-page.ts</files>
<action>
1. **Add new state properties** to the config-page component class:
```typescript
@state() private scanPaused = false;
@state() private showCancelDialog = false;
@state() private cancelMetrics: { added: number } | null = null;
```
2. **Register event listeners** in `connectedCallback()` (find where existing scan events are registered and add alongside them):
```typescript
EventsOn(events.LibraryScanPaused, () => {
this.scanPaused = true;
});
EventsOn(events.LibraryScanResumed, () => {
this.scanPaused = false;
});
EventsOn(events.LibraryScanCancelled, (metrics: any) => {
this.scanning = false;
this.scanPaused = false;
this.scanProgress = null;
this.metrics = metrics;
this.statusMessage = metrics?.cancelled ? 'Scan cancelled.' : 'Scan complete.';
});
```
3. **Add scan control handler methods:**
```typescript
private handlePauseScan() {
PauseScan();
}
private handleResumeScan() {
ResumeScan();
}
private handleCancelScan() {
// Show confirmation dialog with current progress
const added = this.scanProgress?.added ?? 0;
this.cancelMetrics = { added };
this.showCancelDialog = true;
}
private async handleCancelKeep() {
this.showCancelDialog = false;
this.cancelMetrics = null;
CancelScan();
}
private async handleCancelDiscard() {
this.showCancelDialog = false;
this.cancelMetrics = null;
CancelScan();
// After cancel completes, trigger a full rescan to clear partial data.
// The simpler approach: use the library's FullRescan which clears tables first.
// Wait briefly for cancel to take effect, then initiate full rescan.
// Alternatively, just cancel — the user can manually rescan if they want clean state.
// Per research: "discard" clears the entire library since partial state is unreliable.
// Call the existing clearLibraryTables equivalent via FullRescan.
// For simplicity and safety: cancel + emit a status message saying "Partial results discarded. Run Full Rescan to start fresh."
this.statusMessage = 'Scan cancelled. Partial results discarded — run Full Rescan for a clean library.';
// Note: A more sophisticated approach would track added IDs and delete them.
// For v1.1, the simple discard = cancel + inform user approach is safer.
}
private handleCancelDialogDismiss() {
this.showCancelDialog = false;
this.cancelMetrics = null;
}
```
4. **Modify the scan buttons area** (around line 1327). Add Pause/Resume and Cancel buttons that appear ONLY during scanning. Place them between the existing scan buttons and the status bar:
Per user decision: "Pause and Cancel buttons placed next to the existing status label, above the existing progress bar."
Replace the `.scan-actions` div content when scanning is active:
```typescript
<div class="scan-actions">
${this.scanning
? html`
${this.scanPaused
? html`<button class="btn-warning" @click=${this.handleResumeScan}>Resume</button>`
: html`<button class="btn-warning" @click=${this.handlePauseScan}>Pause</button>`
}
<button class="btn-danger" @click=${this.handleCancelScan}>Cancel Scan</button>
`
: html`
<button class="btn-warning" @click=${this.handleSoftScan}>Soft Scan</button>
<button class="btn-danger" @click=${this.handleFullRescan}>Full Rescan</button>
`
}
</div>
```
5. **Add cancel confirmation dialog** — render it conditionally when `showCancelDialog` is true. Place the dialog render at the end of the library section's render method (after the metrics tree, before the closing `</config-section>` tag):
```typescript
${this.showCancelDialog ? html`
<div class="cancel-dialog-overlay" @click=${this.handleCancelDialogDismiss}>
<div class="cancel-dialog" @click=${(e: Event) => e.stopPropagation()}>
<div class="cancel-dialog-title">Cancel Scan</div>
<div class="cancel-dialog-message">
${this.cancelMetrics?.added
? `Keep ${this.cancelMetrics.added} tracks found so far, or discard?`
: 'Cancel the current scan?'}
</div>
<div class="cancel-dialog-actions">
<button class="btn-primary" @click=${this.handleCancelKeep}>
${this.cancelMetrics?.added ? `Keep ${this.cancelMetrics.added} tracks` : 'Cancel Scan'}
</button>
<button class="btn-danger" @click=${this.handleCancelDiscard}>
Discard
</button>
<button class="btn-ghost" @click=${this.handleCancelDialogDismiss}>
Continue Scanning
</button>
</div>
</div>
</div>
` : ''}
```
6. **Update the status bar** to show paused state:
In the existing status bar rendering, update to show "Paused" when paused:
```typescript
<div class="status-bar ${this.scanning ? 'active' : ''} ${this.scanPaused ? 'paused' : ''}">
${this.scanPaused
? 'Scan paused.'
: this.scanProgress
? this.renderScanProgress()
: this.statusMessage || 'Ready.'}
</div>
```
7. **Add CSS styles** for the cancel dialog and paused state. Add to the component's static styles:
```css
.cancel-dialog-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.cancel-dialog {
background: var(--yj-bg-surface, #2a2a2a);
border: 1px solid var(--yj-border, #444);
border-radius: 8px;
padding: 24px;
max-width: 420px;
width: 90%;
}
.cancel-dialog-title {
font-size: var(--yj-text-lg, 18px);
font-weight: 600;
margin-bottom: 12px;
}
.cancel-dialog-message {
font-size: var(--yj-text-sm, 14px);
color: var(--yj-text-secondary, #aaa);
margin-bottom: 20px;
}
.cancel-dialog-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.status-bar.paused {
color: var(--yj-accent, #ffd43b);
}
```
8. **Import Wails bindings** — add imports for `CancelScan`, `PauseScan`, `ResumeScan` from the Wails generated bindings path. Check the actual import path by looking at how existing Library bindings are imported (e.g., `Scan` and `FullRescan`).
9. **Reset scanPaused** in the existing `LibraryScanComplete` handler (the scan finished normally):
Add `this.scanPaused = false;` to the existing handler.
</action>
<verify>
<automated>cd frontend && npx tsc --noEmit 2>&1 | head -30</automated>
</verify>
<done>Config page shows Pause/Cancel buttons during active scan. Pause toggles to Resume when paused. Cancel shows confirmation dialog with "Keep X tracks / Discard / Continue Scanning" options. All scan control events update UI state correctly. CSS styles render the dialog overlay properly.</done>
</task>
</tasks>
<verification>
```bash
cd frontend && npx tsc --noEmit
```
TypeScript compiles with no errors. Scan control UI renders correctly.
</verification>
<success_criteria>
- Pause button visible during scan, calls PauseScan()
- Resume button replaces Pause when paused, calls ResumeScan()
- Cancel button visible during scan, shows confirmation dialog
- Confirmation dialog shows track count and offers Keep/Discard/Continue
- LibraryScanPaused/Resumed/Cancelled events update component state
- Status bar shows "Scan paused." when paused
- Dialog overlay dismissible by clicking outside or "Continue Scanning"
</success_criteria>
<output>
After completion, create `.planning/phases/09-scan-cancellation-keyboard-shortcuts/09-03-SUMMARY.md`
</output>