320 lines
12 KiB
Markdown
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>
|