docs: 添加 superpowers 规格与计划文档

记录 Remote->Many 多文件与相关实现计划/设计,便于后续追踪与复盘。
This commit is contained in:
liumangmang
2026-03-24 13:43:08 +08:00
parent acac45b692
commit c8fa3de679
3 changed files with 1822 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
# Remote -> Many Multi-File (Files Only) Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Allow Transfers -> `Remote -> Many` to select multiple source files (files only) and transfer them to many targets.
**Architecture:** Frontend-only change. Expand `sourcePaths x targetConnectionIds` into a queue of items; each item uses existing backend `/sftp/transfer-remote/tasks` and SSE progress tracking.
**Tech Stack:** Vue 3 + TypeScript (Vite), Pinia, Spring Boot backend unchanged.
---
## File Map
Modify:
- `frontend/src/views/TransfersView.vue` (Remote -> Many UI + state)
- `frontend/src/components/SftpFilePickerModal.vue` (add multi-select mode)
- `frontend/src/stores/transfers.ts` (support `sourcePaths[]`, `targetMode`, improve SSE unsubscription)
No backend changes required.
---
### Task 1: Add multi-select mode to SftpFilePickerModal
**Files:**
- Modify: `frontend/src/components/SftpFilePickerModal.vue`
- [ ] **Step 1: Update component API (props + emits)**
- Add optional prop: `multiple?: boolean` (default `false`).
- Keep existing event: `select` for single selection.
- Add new event: `select-many` for multi selection (payload: `string[]`).
- [ ] **Step 2: Add selection state and deterministic ordering**
- Track selected paths in selection-time order (array) + a set for O(1) membership.
- File click behavior:
- Directory: navigate into directory.
- File:
- if `multiple`: toggle selection.
- else: emit `select(path)` and close (keep existing).
- Persist selection across directory navigation within the modal session.
- If a file is toggled off and later toggled on again, re-add it at the end of the array (selection-time order).
- [ ] **Step 2.1: Add a visible selection indicator**
- When `multiple`, render a checkbox in each file row (checked = selected).
- Directory rows never render a checkbox.
- [ ] **Step 3: Add modal footer controls**
- Add footer actions when `multiple`:
- `Confirm (N)` (disabled when `N=0`) emits `select-many(paths)` then closes.
- `Cancel` closes without emitting.
- Add a "Selected (N)" compact summary area with remove (`x`) for each selected file.
- [ ] **Step 4: Manual verification**
- Run: `npm --prefix frontend run build`
- Expected: build succeeds.
- Manual:
- Open picker, select multiple files across different directories, ensure selections persist.
- Remove items in the "Selected" summary.
- Confirm emits all paths in selection-time order.
- Verify search still filters entries correctly.
- Verify hidden-files toggle still works as before.
- [ ] **Step 5: Commit (optional)**
- If doing commits:
- Run: `git add frontend/src/components/SftpFilePickerModal.vue`
- Run: `git commit -m "feat: add multi-select mode to SFTP file picker"`
---
### Task 2: Update TransfersView Remote -> Many UI for multi-file selection
**Files:**
- Modify: `frontend/src/views/TransfersView.vue`
- [ ] **Step 1: Update remote source model**
- Replace `remoteSourcePath: string` with `remoteSourcePaths: string[]`.
- UI:
- Show count + list of selected paths (basename + full path tooltip).
- Provide remove per item + clear all.
- [ ] **Step 1.1: Add client-side validation for typed paths**
- Before starting Remote -> Many:
- validate each `remoteSourcePaths[i]` is non-empty
- validate it does not end with `/` (heuristic directory indicator)
- If invalid: show inline error and block Start.
- [ ] **Step 2: Wire picker in multi mode**
- Call `<SftpFilePickerModal :multiple="true" ... />`.
- Listen for `select-many` and merge paths into `remoteSourcePaths`:
- append + de-duplicate by full path while preserving first-seen order.
- [ ] **Step 3: Add target path mode toggle**
- Add UI control: `Directory` (default) vs `Exact Path`.
- Behavior:
- `Directory` treats input as directory; normalize to end with `/`; append source filename.
- `Exact Path` uses raw input as full file path (only allowed when exactly one source file is selected).
- Disable `Exact Path` when `remoteSourcePaths.length > 1` (force directory mode).
- [ ] **Step 3.1: Update Start button enablement**
- Require `remoteSourceConnectionId != null`.
- Require `remoteSourcePaths.length > 0`.
- Require `remoteSelectedTargets.length > 0`.
- Also require validation in Step 1.1 passes.
- [ ] **Step 4: Add basename collision warning**
- When `Directory` mode and `remoteSourcePaths` contains duplicate basenames:
- show a warning dialog/banner before starting (acknowledge to proceed).
- message mentions overwrite will happen on each selected target connection.
- [ ] **Step 5: Source connection change behavior**
- When `remoteSourceConnectionId` changes:
- clear `remoteSourcePaths`
- remove source id from `remoteSelectedTargets` if present
- [ ] **Step 6: Manual verification**
- Run: `npm --prefix frontend run build`
- Manual:
- Pick 2 files, choose 2 targets -> queue shows 4 items.
- Toggle Directory/Exact Path:
- multi-file forces Directory
- single-file allows Exact Path
- Change source connection -> selected files cleared.
- [ ] **Step 7: Commit (optional)**
- If doing commits:
- Run: `git add frontend/src/views/TransfersView.vue frontend/src/components/SftpFilePickerModal.vue`
- Run: `git commit -m "feat: allow selecting multiple remote source files"`
---
### Task 3: Update transfers store to support multi-file Remote -> Many + safe SSE unsubscription
**Files:**
- Modify: `frontend/src/stores/transfers.ts`
- [ ] **Step 1: Update store API and types**
- Change `startRemoteToMany` params:
- from: `sourcePath: string`
- to: `sourcePaths: string[]`
- Add param: `targetMode: 'dir' | 'path'`.
- [ ] **Step 2: Implement target path resolution in store**
- For each `sourcePath`:
- compute `filename` as basename.
- if `targetMode === 'dir'`: normalize `targetDirOrPath` to end with `/` and append filename.
- if `targetMode === 'path'`: require `sourcePaths.length === 1` and use raw `targetDirOrPath`.
- [ ] **Step 3: Expand items as `sourcePaths x targets`**
- Create a `TransferItem` per pair.
- Label includes both source path and target connection.
- Concurrency limiter applies across the full list.
- [ ] **Step 4: Fix SSE subscription cleanup**
- In `waitForRemoteTransfer` and upload-wait logic:
- ensure the returned unsubscribe is called on terminal states (success/error/cancel).
- still store unsubscribe in the run controller so `cancelRun` can close any active subscriptions.
- [ ] **Step 5: Manual verification**
- Run: `npm --prefix frontend run build`
- Manual:
- Start a small run and ensure completion does not leave SSE connections open (observe via browser network if needed).
- Cancel a run and confirm subscriptions close promptly.
- [ ] **Step 6: Commit (optional)**
- If doing commits:
- Run: `git add frontend/src/stores/transfers.ts`
- Run: `git commit -m "fix: close SSE subscriptions for transfer tasks"`
---
### Task 4: End-to-end verification
**Files:**
- Verify: `frontend/src/views/TransfersView.vue`
- Verify: `frontend/src/components/SftpFilePickerModal.vue`
- Verify: `frontend/src/stores/transfers.ts`
- [ ] **Step 1: Frontend build**
- Run: `npm --prefix frontend run build`
- Expected: success.
- [ ] **Step 2: Smoke test (manual)**
- Start app (if needed):
- Backend: `mvn -f backend/pom.xml spring-boot:run`
- Frontend: `npm --prefix frontend run dev`
- If deps aren't installed:
- Frontend: `npm --prefix frontend install`
- Use Transfers:
- Remote -> Many: pick 2-3 files, 2 targets, Directory mode.
- Verify files arrive at target directory with correct names.
- Verify errors are per-item, run continues for other items.
- [ ] **Step 3: Commit (optional)**
- If doing a final commit:
- Run: `git add frontend/src/views/TransfersView.vue frontend/src/components/SftpFilePickerModal.vue frontend/src/stores/transfers.ts`
- Run: `git commit -m "feat: support multi-file Remote -> Many transfers"`