feat: pinned rail component + cross-session focus

M4 of pinned terminals (v0.17.0):
- New PinnedRail component: compact strip above the tab bar, renders
  pinned terminals from other sessions (pin icon + terminal icon + title
  + tooltip with kind/scope/cwd)
- Click navigates to the home session (ctx.sessions.open) and activates
  the tab (or raises its float) via the pinnedJumpRef effect
- Right-click menu: Unpin / Close Terminal; middle-click close
- IconPinOutline16 added to icons.tsx
- Rail styles in sidebar.module.css (token-driven, no-drag opt-out)
- Sidebar: collectPinnedTabs on store snapshot + session list, railRevision
  state forces re-render after reduceFor (which doesn't notify)
- Cross-session close: reduceFor(closeTab/closeFloatByTab) + ptyClose/
  agentPtyClose; cross-session unpin: reduceFor(setTabPin null)
This commit is contained in:
HuanLinOTO
2026-08-26 18:42:34 +08:00
parent c6a3e24599
commit 531bb53129
6 changed files with 531 additions and 8 deletions
+82
View File
@@ -458,6 +458,7 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
.toggleCluster,
.toggleButton,
.tabBar,
.pinnedRail,
.floatHeader {
-webkit-app-region: no-drag;
}
@@ -865,6 +866,87 @@ 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;
}
.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;
}
.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);
}
/* ── Explorer ──────────────────────────────────────────────────────────── */
.explorer {