feat(sidebar): mark the active workspace with an accent rail and wash - #914
Merged
Conversation
The active card carries only a brighter label and a 14px accent glyph today, and that glyph is the same hue as the pulsing status dots on every working workspace — so the static marker loses to the animated ones. Adds an accent rail spanning the whole card plus a subtle wash that stops above an expanded file tree, so the highlight costs the same whether a folder is collapsed or a large tree is open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The active card carried only a brighter label and a 14px accent glyph, both losing to the pulsing status dots on every working workspace below it. It now reads as a region: a 2px accent rail down the card's full height with a wash on the workspace's own rows. The wash lands via a child combinator, so an expanded file tree — which renders into .sidebar-project-files, not a row — stays on the plain sidebar surface and the highlight costs the same at any tree depth. Three cascade traps, all verified in a real browser rather than by inspection: the sticky header needs the wash composited onto an opaque base or it ties at (0,3,0) and wins with a flat fill; its hover needs the same or it goes translucent and lets rows scroll through it; and the rail needs z-index 3 to clear that opaque header, which otherwise paints over its top. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the three cascade traps in these selectors — the sticky header's composited background, its hover, and the rail's z-index — plus the transition that makes a naive getComputedStyle read return a mid-flight value. None of them are catchable by the sidebar's jsdom tests, which never load theme.css. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The sidebar could not tell you which workspace you were actually in. The active
card carried three quiet signals — a brighter label, a 14px accent glyph
(
WorkspaceGlyph.tsx:22), and a sticky header — andtheme.cssrecorded thedecision explicitly: "The active row carries no fill — selection is shown by
label color alone."
Two things defeated that. The accent glyph is the same hue as the
status-dot--activedots on every working workspace, and those pulse — astatic 14px icon loses to three animated ones. And a
--sidebar-active-bgtokenalready existed for exactly this purpose (
theme.css:135, mapped fromlist.inactiveSelectionBackground) with nothing in the sidebar using it.The active card now reads as a region: a 2px accent rail down its full
height plus a wash on the workspace's own rows.
The rail and the wash have different extents on purpose. The rail runs past an
expanded file tree so the region still reads as one; the wash lands only on
direct
.sidebar-item-rowchildren, and an expanded tree renders into.sidebar-project-files— not a row — so it stays on the plain sidebar surface.The highlight therefore costs the same whether a folder is collapsed or a
thousand-line tree is open.
CSS only. No component changes:
sidebar-project-group--activewas already onthe card.
Three cascade traps, all recorded in
renderer.mdNone are catchable by the sidebar's unit tests, since jsdom never loads
theme.css.--bg-sidebarthrougha
linear-gradientlayer. It ties with the card's wash rule at(0,3,0)andcomes later, so a flat opaque fill wins and leaves the header reading
unhighlighted while its children stay washed.
(0,3,1)and would otherwise replace the opaque base with a translucentfill, letting rows scroll visibly through the header.
z-index: 3to clear the header's2. That headerbackground is opaque by necessity and otherwise paints over the rail's top,
so the rail appears to start at the first folder row instead of the top of
the card. This one was not predicted — the screenshot caught it.
Rows inside the active card also need their own
:hover:--list-hover-bgisaccent at 5% against the 7% wash and loses on specificity anyway, so without it
the active card stops responding to the pointer entirely.
Testing
npm run screenshot:component ProjectSidebar --theme royal-dark, before and after, confirms the rail spans the card, thewash stops above the file tree, and no label shifted horizontally.
--emit-html+ playwright):header 7%→12% over an opaque
rgb(9,13,24)in both resting and hover states,repo row 7%→12%, inactive rows untouched at transparent→5%, rail 2px accent at
z-index: 3over the header's2. Note these must be read after the row'sbackgroundtransition settles, or the value is a mid-flight interpolation —that misled me once and is now documented.
npx vitest run src/renderer/components/sidebar— 12 files, 118 tests, pass.npm test— 2911 passed, 1 failed:src/main/app/debug-log.test.ts, amain-process timing test with a 40ms sleep. It passes in isolation (8/8) and
has no reference to
theme.cssor the sidebar; it flakes under full-suitecontention. Not caused by this change.
npm run typecheck— clean across web, node, plugins.bash scripts/wiki-lint.sh—renderer.mdupdated here and not stale.Rebased onto latest
mainand re-verified after, since #912 (label shimmer)and #911 both touch these files. The shimmer applies
background-clip: texttothe label — a different element than the row wash — and the two render together
without conflict.
Design spec and implementation plan are included as the first two commits.
🤖 Generated with Claude Code