Files
ssh-manager/docs/superpowers/plans/2026-03-24-sftp-tabs.md
2026-03-24 17:52:28 +08:00

202 lines
6.8 KiB
Markdown

# SFTP Sidebar Tabs 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:** Add non-duplicated, session-scoped SFTP tabs in the sidebar so users can return to file sessions without losing tab entries.
**Architecture:** Introduce a dedicated Pinia store (`sftpTabs`) parallel to `terminalTabs`, then wire it into `ConnectionsView`, `MainLayout`, and `SftpView`. Keep `/sftp/:id` as the single SFTP route and use route-driven synchronization so tab switching updates the view correctly without stale state.
**Tech Stack:** Vue 3, TypeScript, Pinia, Vue Router, Vite, vue-tsc
---
## File Structure
- Create: `frontend/src/stores/sftpTabs.ts`
- Owns SFTP tab state and actions (`openOrFocus`, `activate`, `close`).
- Modify: `frontend/src/views/ConnectionsView.vue`
- Registers/focuses SFTP tab before routing to `/sftp/:id`.
- Modify: `frontend/src/layouts/MainLayout.vue`
- Renders SFTP sidebar tabs, handles click/close navigation behavior.
- Modify: `frontend/src/views/SftpView.vue`
- Keeps route param and SFTP tab state in sync via watcher.
- Verify: `frontend/src/router/index.ts`
- Route shape remains unchanged (`/sftp/:id`).
- Verify: `docs/superpowers/specs/2026-03-24-sftp-tabs-design.md`
- Source of truth for requirements and acceptance criteria.
### Task 1: Add Dedicated SFTP Tabs Store
**Files:**
- Create: `frontend/src/stores/sftpTabs.ts`
- Reference: `frontend/src/stores/terminalTabs.ts`
- [ ] **Step 1: Check current store pattern and workspace status**
Run `git status --short` and read `frontend/src/stores/terminalTabs.ts` to mirror established naming/style patterns.
- [ ] **Step 2: Add `SftpTab` model and store state**
Create `sftpTabs` store with:
```ts
export interface SftpTab {
id: string
connectionId: number
title: string
active: boolean
}
```
State and computed:
```ts
const tabs = ref<SftpTab[]>([])
const activeTabId = ref<string | null>(null)
const activeTab = computed(() => tabs.value.find(t => t.id === activeTabId.value) || null)
```
- [ ] **Step 3: Implement open/activate/close logic with dedup**
Implement actions analogous to terminal tabs:
- `openOrFocus(connection)` reuses existing tab by `connectionId`
- `activate(tabId)` flips active flags and updates `activeTabId`
- `close(tabId)` removes tab and activates neighbor when needed
- [ ] **Step 4: Build-check after new store**
Run in `frontend/`: `npm run build`
Expected: build succeeds and no type errors in new store.
### Task 2: Register SFTP Tabs from Connections Entry
**Files:**
- Modify: `frontend/src/views/ConnectionsView.vue`
- Use: `frontend/src/stores/sftpTabs.ts`
- [ ] **Step 1: Add store import and instance**
Import `useSftpTabsStore` and initialize `const sftpTabsStore = useSftpTabsStore()`.
- [ ] **Step 2: Update `openSftp(conn)` behavior**
Before routing, call:
```ts
sftpTabsStore.openOrFocus(conn)
router.push(`/sftp/${conn.id}`)
```
- [ ] **Step 3: Build-check for integration safety**
Run in `frontend/`: `npm run build`
Expected: build succeeds and `ConnectionsView` typing remains valid.
### Task 3: Render and Control SFTP Tabs in Sidebar
**Files:**
- Modify: `frontend/src/layouts/MainLayout.vue`
- Use: `frontend/src/stores/sftpTabs.ts`
- [ ] **Step 1: Add SFTP store wiring and computed values**
Add:
- `const sftpTabsStore = useSftpTabsStore()`
- `const sftpTabs = computed(() => sftpTabsStore.tabs)`
- route helper for SFTP context (for active styling and close-navigation logic)
- [ ] **Step 2: Add SFTP tab click and close handlers**
Implement:
- click handler: activate tab + `router.push(`/sftp/${tab.connectionId}`)` + close sidebar
- close handler:
- always close in store
- only navigate when current route is the closed tab's route
- if tabs remain, navigate to active tab route
- if no tabs remain, navigate `/connections`
- [ ] **Step 3: Render `文件` sidebar section near terminal section**
Add a section matching current visual style conventions:
- title row with `FolderOpen` icon and text `文件`
- list items for `sftpTabs`
- close button per item
- active class aligned with current route context
- [ ] **Step 4: Build-check and inspect no terminal regressions**
Run in `frontend/`: `npm run build`
Expected: build succeeds; no type/template errors in `MainLayout`.
### Task 4: Route-Driven Sync in SFTP View
**Files:**
- Modify: `frontend/src/views/SftpView.vue`
- Use: `frontend/src/stores/sftpTabs.ts`
- [ ] **Step 1: Add SFTP tabs store and route-param watcher**
Add watcher on `route.params.id` with `{ immediate: true }` so first load and tab switching share one code path.
- [ ] **Step 2: Consolidate initialization into watcher path**
Use watcher flow:
- parse and validate id; if invalid, show user-visible error feedback, do not create/open any SFTP tab, and navigate to `/connections`
- ensure connections loaded (fetch when needed)
- resolve connection; if missing, show user-visible error and route back to `/connections`
- call `sftpTabsStore.openOrFocus(connection)`
- refresh connection-bound SFTP view state for current route id
Avoid duplicate request/init logic split across both watcher and old mount flow.
- [ ] **Step 3: Keep existing file-management behavior unchanged**
Do not alter existing upload/download/delete/core SFTP operations; only route/tab synchronization behavior should change.
- [ ] **Step 4: Build-check after route-sync changes**
Run in `frontend/`: `npm run build`
Expected: build succeeds and SftpView compiles cleanly.
### Task 5: End-to-End Verification
**Files:**
- Verify runtime behavior in app UI
- Verify `frontend/src/router/index.ts` unchanged for route shape
- [ ] **Step 1: Run final build verification**
Run in `frontend/`: `npm run build`
Expected: `vue-tsc -b` and Vite build both pass.
- [ ] **Step 2: Manual behavior checks**
Verify all acceptance criteria:
1. Same-connection `文件` clicks do not create duplicate tabs.
2. Multiple SFTP tabs can be opened and switched.
3. Navigating away (for example back to connections) keeps SFTP tabs in sidebar during current session.
4. Closing active/non-active tabs follows designed route behavior.
5. Switching `/sftp/:id` between tabs updates header/file list without stale previous-connection state.
6. Invalid/nonexistent `/sftp/:id` creates no tab, shows visible feedback, and routes back to `/connections`.
7. Terminal tabs continue to open/switch/close normally.
- [ ] **Step 3: Commit if user requests**
If the user asks to commit, keep commit focused:
```bash
git add frontend/src/stores/sftpTabs.ts frontend/src/views/ConnectionsView.vue frontend/src/layouts/MainLayout.vue frontend/src/views/SftpView.vue
git commit -m "feat: add sidebar tabs for sftp sessions"
```