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

6.8 KiB

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:

export interface SftpTab {
  id: string
  connectionId: number
  title: string
  active: boolean
}

State and computed:

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:

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:

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"