feat: inline pinned tabs in TabBar (replaces PinnedRail)

M4 rewrite per user feedback: pinned terminals should display in the
Tabs bar itself, not a separate rail. Clicking activates in-place
(cross-session terminal rendering), no session jump.

Changes:
- Delete PinnedRail.tsx + tests/pinned-rail.spec.tsx
- pinned.ts: add createPinnedVirtualTab (virtual id = pinned:<sess>:<id>,
  meta.__pinnedHome = { sessionId, cwd, tabId }), injectPinnedIntoTree
  (appends virtual tabs to first leaf, overrides active), isPinnedVirtualId,
  parsePinnedVirtualId, isPinnedVirtualTab, getPinnedHomeScope
- tab-content-memo.ts: add effectiveTabId to memo key — TabContent passes
  the original tab id (not the virtual id) to the descriptor component so
  TerminalView's WS URL resolves to the home PTY
- Sidebar.tsx: augmented tree (injectPinnedIntoTree), activePinnedTabId
  state, wrappedActions (intercept virtual ids for activate/close/unpin/
  drag/float), renderTab passes home scope + effectiveTabId for pinned tabs,
  pinnedRevision replaces railRevision
- TabBar.tsx: pinned virtual tabs non-draggable, pin icon prefix, stripped
  context menu (Unpin/Close only, no float/close-others/left/right)
- sidebar.module.css: remove rail styles, add .pinnedTab (italic + tertiary)
- Docs: README/README_EN/AGENTS.md/design doc updated for inline approach

157/157 client-side tests pass; typecheck + build clean
This commit is contained in:
HuanLinOTO
2026-08-26 21:40:32 +08:00
parent acf639471d
commit e7c4578791
13 changed files with 416 additions and 547 deletions
+7 -75
View File
@@ -458,7 +458,6 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
.toggleCluster,
.toggleButton,
.tabBar,
.pinnedRail,
.floatHeader {
-webkit-app-region: no-drag;
}
@@ -866,85 +865,18 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
color: var(--dsw-alias-label-primary);
}
/* ── Pinned-terminal rail (v0.17.0+) ──────────────────────────────────── */
/* A compact strip above the tab bar showing pinned terminals from OTHER
sessions. Only renders when there is at least one visible pinned tab.
Hairline-separated from the tab bar below; token-driven so skins follow. */
.pinnedRail {
flex: none;
display: flex;
align-items: stretch;
gap: 0;
padding: 0 4px;
border-bottom: 1px solid var(--dsw-alias-border-l1);
background: var(--dsw-alias-bg-layer-1);
overflow-x: auto;
scrollbar-width: none;
}
.pinnedRail::-webkit-scrollbar {
display: none;
}
.pinnedRailLabel {
flex: none;
display: inline-flex;
align-items: center;
padding: 0 6px 0 4px;
font: var(--dsw-font-xxxs-strong-11);
color: var(--dsw-alias-label-tertiary);
white-space: nowrap;
}
/* ── Pinned-terminal tab (v0.17.0+) ───────────────────────────────────── */
/* Pinned virtual tabs (injected from other sessions) render inline in the
tab bar with a pin icon and a subtle tint to distinguish them from the
session's own tabs. Token-driven so skins follow. */
.pinnedTab {
flex: none;
display: inline-flex;
align-items: center;
gap: 4px;
max-width: 180px;
padding: 0 6px;
height: 28px;
font: var(--dsw-font-xxs-12);
color: var(--dsw-alias-label-secondary);
border-right: 1px solid var(--dsw-alias-border-l1);
background: transparent;
cursor: pointer;
user-select: none;
border-radius: 0;
color: var(--dsw-alias-label-tertiary);
font-style: italic;
}
.pinnedTab:hover {
background: var(--dsw-alias-interactive-bg-hover);
color: var(--dsw-alias-label-primary);
}
.pinnedTabTitle {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pinnedTabClose {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
padding: 0;
border: none;
border-radius: 3px;
background: transparent;
color: var(--dsw-alias-label-tertiary);
cursor: pointer;
flex: none;
}
.pinnedTabClose:hover {
background: var(--dsw-alias-interactive-bg-hover);
color: var(--dsw-alias-label-primary);
color: var(--dsw-alias-label-secondary);
}
/* ── Explorer ──────────────────────────────────────────────────────────── */