202 lines
6.8 KiB
Markdown
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"
|
|
```
|