feat(ui): add collapsible Worktree and Tags panels to left sidebar

Add two new sidebar components (WorktreePanel, TagsPanel) and integrate them
into the compact left navigation layout:

- Wire up imports and rendering in App.svelte, including collapse toggles and
  resize handles for both panels. Persisted heights (localStorage) and keyboard
  resizing are supported; heights are clamped between 80 and 420px with a 140px
  default.
- Extend buildLeftSidebarRows and left-sidebar grid/template styles to include
  the new panels and reduce panel-handle thickness. Add extensive CSS for the
  compact accordion navigation, worktree and tags lists.
- Move tag and worktree management UI out of BranchPanel (remove tag props),
  and add dedicated handlers in App.svelte for the new panels.
- Refactor worktree loading: introduce a worktreeLoadId to guard async
  refreshWorktrees() calls and avoid race conditions. refreshWorktrees(path?)
  now takes an optional path and updates worktree state only when the request
  is still relevant.
- Small behavioral tweaks: stash panel default collapsed state changed to true
  and the explorer resize-handle visibility condition adjusted.

This commit only adds the UI/UX integration and local persistence for the new
panels and their resizing/refresh behavior.
This commit is contained in:
2026-09-16 22:58:48 +02:00
parent 66c85321ea
commit db4e58e039
7 changed files with 540 additions and 236 deletions
+80
View File
@@ -9173,3 +9173,83 @@ section > header.page-header.page-header {
border-color: var(--dialog-close-hover-ink);
background: color-mix(in srgb, var(--dialog-close-hover-ink) 12%, var(--app-dialog-chrome));
}
/* Compact accordion navigation (layout A). */
.left-sidebar { overflow-y: auto; overflow-x: hidden; }
.left-sidebar > .panel { min-width: 0; }
.left-sidebar .section-head {
display: flex; align-items: center; justify-content: space-between; gap: 6px;
min-height: 42px; padding: 6px 10px;
background: var(--color-surface-solid);
}
.left-sidebar .sidebar-section-title {
display: flex; align-items: center; gap: 9px; min-width: 0; margin: 0;
color: var(--color-ink-muted); font-size: 11px; font-weight: 750;
line-height: 20px; letter-spacing: .055em; text-transform: uppercase;
}
.left-sidebar .sidebar-section-title svg { flex-shrink: 0; }
.left-sidebar .sidebar-section-title svg { width: 16px; height: 16px; stroke-width: 1.75; color: var(--color-ink-muted); }
.left-sidebar :is(.branch-head-actions, .stash-head-actions, .explorer-head-actions) { gap: 3px; }
.left-sidebar :is(.branch-create-toggle, .stash-toggle, .explorer-bulk-button) {
width: 24px; min-width: 24px; height: 24px; min-height: 24px; flex-basis: 24px;
border-color: transparent; background: transparent; border-radius: 3px;
}
.left-sidebar :is(.branch-create-toggle, .stash-toggle, .explorer-bulk-button):hover:not(:disabled) {
background: var(--color-surface-hover); color: var(--color-ink);
}
.left-sidebar .left-panel-resize-handle { min-height: 6px; }
.left-sidebar .branch-list, .left-sidebar .explorer-list { padding: 4px 0; }
.left-sidebar .branch-group-toggle {
min-height: 30px; padding: 5px 10px; border-radius: 0;
font-size: 12px; font-weight: 500; letter-spacing: normal; text-transform: none;
}
.left-sidebar .branch-row {
min-height: 34px; padding: 5px 10px; padding-left: calc(14px + var(--branch-indent, 0px));
border-radius: 0; margin-top: 0;
}
.left-sidebar .branch-row.current { border-color: transparent; box-shadow: inset 3px 0 0 var(--color-primary); }
.left-sidebar .branch-info strong { font-size: 12px; font-weight: 550; }
.left-sidebar .branch-row.current strong { font-weight: 700; }
.left-sidebar .branch-folder-row { min-height: 32px; border-radius: 0; margin-top: 0; }
.left-sidebar .stash-panel { display: flex; flex-direction: column; min-height: 0; }
.left-sidebar .stash-panel .section-head, .left-sidebar .stash-create { flex-shrink: 0; }
.left-sidebar .stash-list { flex: 1; min-height: 0; overflow: auto; }
.left-sidebar .stash-empty { flex: 1; padding: 16px 10px; }
.left-sidebar .stash-create { grid-template-columns: minmax(0, 1fr) auto; }
.left-sidebar .stash-input { grid-column: 1 / -1; }
.left-sidebar .explorer-tool-action, .left-sidebar .explorer-action-divider { display: none; }
/* File actions remain available through the file context menu. */
.left-sidebar .worktree-panel { display: grid; grid-template-rows: 42px minmax(0, 1fr); min-height: 0; overflow: hidden; }
.left-sidebar .worktree-panel.collapsed { grid-template-rows: 42px; }
.sidebar-worktree-list { min-height: 0; overflow: auto; padding: 4px 0; }
.sidebar-worktree-row {
display: flex; align-items: center; gap: 8px; width: 100%; min-height: 46px;
padding: 6px 10px; border: 0; border-radius: 0; background: transparent;
color: var(--color-ink-muted); text-align: left;
}
.sidebar-worktree-row.current {
background: color-mix(in srgb, var(--color-primary) 11%, var(--color-surface-solid));
box-shadow: inset 3px 0 0 var(--color-primary);
}
.sidebar-worktree-row:hover:not(:disabled) { background: var(--color-surface-hover); }
.sidebar-worktree-row > svg { flex-shrink: 0; }
.sidebar-worktree-info { display: grid; gap: 2px; flex: 1; min-width: 0; }
.sidebar-worktree-info strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--color-ink); }
.sidebar-worktree-info > span { display: flex; align-items: center; gap: 4px; overflow: hidden; white-space: nowrap; font-size: 10px; color: var(--color-ink-dim); }
.sidebar-worktree-info > span svg { flex-shrink: 0; }
.sidebar-worktree-error { display: grid; gap: 8px; padding: 10px; font-size: 11px; overflow-wrap: anywhere; color: var(--color-ink-muted); }
.sidebar-worktree-error button { justify-self: start; }
/* All collapsed sections share exactly the same header and icon geometry. */
.left-sidebar > .panel { border: 0; }
.left-sidebar > .panel + .panel { border-top: 0; }
.left-sidebar .section-head { height: 42px; min-height: 42px; box-sizing: border-box; }
.left-sidebar > .panel.collapsed { height: 42px; min-height: 42px; }
.left-sidebar .section-head button svg { width: 14px; height: 14px; stroke-width: 1.75; }
.left-sidebar .section-head .pill-count { min-width: 24px; justify-content: center; }
.left-sidebar .tags-panel { display: grid; grid-template-rows: 42px minmax(0, 1fr); min-height: 0; overflow: hidden; }
.left-sidebar .tags-panel.collapsed { grid-template-rows: 42px; }
.sidebar-tags-list { min-height: 0; overflow: auto; padding: 4px 0; }
.left-sidebar .tags-panel .tag-create-form { grid-template-columns: auto minmax(0, 1fr) auto auto; margin: 4px 8px; }
.left-sidebar .tags-panel .tag-create-form input[aria-label="Tag message"] { grid-column: 2 / -1; grid-row: 2; }