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

5.4 KiB

2026-03-24 SFTP Tabs Design

Background

The current connection list provides both 终端 and 文件 actions. 终端 has persistent sidebar tabs, but 文件 (SFTP) opens a route directly and has no sidebar tab lifecycle. Users lose the quick return path after navigating away, which feels inconsistent.

Goal

  • Add sidebar tabs for SFTP sessions near the existing terminal tabs.
  • Ensure SFTP tabs are unique per connection (no duplicates).
  • Keep tab state only for the current in-memory session.

Non-Goals

  • No persistence across full page refresh (no localStorage/sessionStorage).
  • No refactor that merges terminal and SFTP tab models into one generic tab system.
  • No route restructuring; keep /sftp/:id as the SFTP route entry.

Current State

  • Terminal tabs are managed by frontend/src/stores/terminalTabs.ts and rendered in frontend/src/layouts/MainLayout.vue.
  • ConnectionsView opens terminal via terminalTabs.openOrFocus(conn) and then routes to /terminal.
  • ConnectionsView opens SFTP by routing directly to /sftp/:id, with no tab store.
  • SftpView currently has no tab registration step.

Selected Approach

Introduce a dedicated SFTP tab store and render a new sidebar tab section in MainLayout, following the same interaction model as terminal tabs while keeping route behavior centered on /sftp/:id.

1. New store: sftpTabs

Create frontend/src/stores/sftpTabs.ts with a model parallel to terminal tabs:

  • tabs: SftpTab[]
  • activeTabId: string | null
  • activeTab computed
  • openOrFocus(connection)
  • activate(tabId)
  • close(tabId)

Each tab includes:

  • id: string
  • connectionId: number
  • title: string
  • active: boolean

Dedup rule:

  • openOrFocus(connection) must reuse an existing tab when connectionId matches.

2. Connection list behavior

Update frontend/src/views/ConnectionsView.vue:

  • In openSftp(conn), call sftpTabs.openOrFocus(conn) before router.push(/sftp/${conn.id}).

Result:

  • Clicking 文件 on the same connection repeatedly does not create duplicate tabs.

3. Sidebar rendering and controls

Update frontend/src/layouts/MainLayout.vue:

  • Import and use useSftpTabsStore.
  • Add computed sftpTabs.
  • Add a new sidebar section (near terminal tabs) titled 文件.
  • Render each tab as a clickable item with close button.

Interactions:

  • Click tab: activate and navigate to /sftp/:connectionId.
  • Close tab:
    • Always remove the tab from store.
    • Only trigger route navigation when the current route is the closed tab's route (/sftp/:connectionId):
      • If another SFTP tab remains, navigate to /sftp/:newActiveConnectionId.
      • If no SFTP tabs remain, navigate to /connections.
    • If closing a non-active tab, or closing from a non-SFTP route, remove only (no route change).

Highlighting:

  • Keep active style tied to both tab active state and current SFTP route context.

4. Route-entry consistency

Update frontend/src/views/SftpView.vue:

  • Import and use useSftpTabsStore.
  • Watch route.params.id with { immediate: true } so logic runs on first load and on /sftp/:id param changes (tab-to-tab switching).
  • On each id change:
    • Parse id to number; if invalid, navigate to /connections.
    • Ensure connections are loaded (fetch when needed).
    • Resolve the matching connection; if not found, show error feedback and navigate to /connections.
    • Call sftpTabs.openOrFocus(connection) and then refresh SFTP view state for that connection.
  • Consolidate route-driven initialization into this watcher (avoid a separate onMounted path-init flow for the same concern) so first load and param switching use one code path and do not trigger duplicate requests.

This keeps behavior consistent for direct navigation and sidebar tab switching, without duplicate tabs.

Behavior Kept Unchanged

  • Existing terminal tab logic and terminal workspace lifecycle.
  • Existing SFTP route path (/sftp/:id) and core file-management interactions remain unchanged, except for tab registration/sync and invalid-or-missing connection route handling described above.
  • Authentication and router guards.
  • UI visual language (slate/cyan styling).

Acceptance Criteria

  • 文件 opens/activates an SFTP tab in sidebar.
  • Repeatedly opening 文件 for the same connection does not create duplicate tabs.
  • SFTP tabs remain available when navigating back to connections or other pages within the same session.
  • Closing active/non-active SFTP tabs follows the navigation rules above.
  • Terminal tabs continue working exactly as before.
  • Switching between existing SFTP tabs (for example /sftp/1 and /sftp/2) updates connection header and file list correctly, without stale data from the previous connection.
  • Direct navigation to an invalid or nonexistent /sftp/:id does not create a tab and returns the user to /connections with visible feedback.

Verification

  • Run npm run build in frontend/.
  • Manual verification:
    1. Open SFTP for one connection multiple times and confirm single tab.
    2. Open SFTP for multiple connections and switch between tabs.
    3. Navigate away and return via sidebar SFTP tabs.
    4. Close active and inactive SFTP tabs and verify resulting route behavior.
    5. Re-check terminal tab open/switch/close behavior for regressions.
    6. Open an invalid/nonexistent /sftp/:id and verify no tab is created, visible error feedback appears, and navigation returns to /connections.