From acb84b91efb33cd4cf839a0ff9896ef83fd72cdc Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Wed, 2 Sep 2026 17:18:00 +0100 Subject: [PATCH] fix(maestro): pin activity rail to left edge, drop active-state pulse Rail now spans full row height flush to the left edge with square corners, instead of a depth-shifted inset bar. Active state stays solid green since colour alone already signals it; blink/flash stay for awaiting-input/error. --- docs/SIDEBAR.md | 16 +++++++++------- web/src/catalog.ts | 14 ++++++-------- web/src/styles.css | 12 +++++------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/docs/SIDEBAR.md b/docs/SIDEBAR.md index b3ee4ad..0bf2324 100644 --- a/docs/SIDEBAR.md +++ b/docs/SIDEBAR.md @@ -51,13 +51,15 @@ command. `` follows in the rail's colour when shown. There is no state word and no second badge: the rail exists so the state is still legible when a row is dimmed or the terminal renders the duration in the default fg. -In the **web client** the rail is a 5px bar positioned inside the row's own -indent gutter — it shifts outward with nesting depth exactly as the gutter -does — spanning the full card height. It carries motion, reserved for states -that need attention (DESIGN.md's "living signals" rule): a slow breathing -opacity while `Active`, a hard blink while `AwaitingInput`, a fast flash while -`Error`. `Idle` is solid and still, on purpose. `prefers-reduced-motion` -leaves every state as a solid bar. +In the **web client** the rail is a 5px bar pinned to the row's own left +edge, square corners, spanning the full card height edge to edge — it +shifts outward with nesting depth along with the row's own padding, since +it is positioned relative to the row. It carries motion only for states that +need attention (DESIGN.md's "living signals" rule): a hard blink while +`AwaitingInput`, a fast flash while `Error`. `Active` and `Idle` are both +solid and still — colour alone (green vs. the idle grey) is the signal for +`Active`, so it does not also pulse. `prefers-reduced-motion` leaves every +state as a solid bar. In the **TUI** the rail is a one-column-wide block glyph (`▌`) in the state's palette role, leading the existing indent gutter (`INDENT`/`META_INDENT`) on diff --git a/web/src/catalog.ts b/web/src/catalog.ts index 0a0a77c..8c6e600 100644 --- a/web/src/catalog.ts +++ b/web/src/catalog.ts @@ -68,13 +68,11 @@ const STATE_WORDS: Record = { error: "Error", }; -/** docs/SIDEBAR.md: the activity rail sits inside the row's own indent - * gutter, so it shifts outward with nesting depth exactly as that gutter - * does. `depth * 16 + 8` is the same expression `renderNode` uses for the - * row's own padding — the rail sits just inside it. */ -function activityRailMarkup(slug: string, stateWord: string, depth: number): string { - const left = depth * 16 + 2; - return ``; +/** docs/SIDEBAR.md: the activity rail is pinned to the row's own left edge, + * full height. The row's own padding (`renderNode`) shifts outward with + * nesting depth, carrying the rail with it. */ +function activityRailMarkup(slug: string, stateWord: string): string { + return ``; } function metaLine(s: Session, now: number): string { @@ -172,7 +170,7 @@ function renderNode(node: TreeNode, depth: number): HTMLElement { ); // docs/SIDEBAR.md: line 1 reserves action width, and line 2 is unconditional. row.innerHTML = ` - ${activityRailMarkup(slug, stateWord, depth)} + ${activityRailMarkup(slug, stateWord)} ${escapeHtml(name)} diff --git a/web/src/styles.css b/web/src/styles.css index 83fa9b8..b66cc2e 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -1393,23 +1393,21 @@ input.leaf-rename:focus { color: var(--danger); } -/* docs/SIDEBAR.md: the activity rail replaces the old inline dot. It sits - inside the row's own indent gutter (`.tree-leaf` is already positioned), - at a `left` set inline per nesting depth — see `activityRailMarkup` in +/* docs/SIDEBAR.md: the activity rail replaces the old inline dot. It is + pinned to the row's own left edge, full height, square corners — see catalog.ts. Idle stays solid and still on purpose: motion is reserved for states that need attention (DESIGN.md's "living signals" rule). */ .activity-rail { position: absolute; - top: 4px; - bottom: 4px; + top: 0; + bottom: 0; + left: 0; width: 5px; - border-radius: 2px; background: currentColor; } .activity-rail[data-activity="active"] { color: var(--live); - animation: pulse 2.4s var(--ease-out) infinite; } .activity-rail[data-activity="idle"] { color: var(--border-strong);