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.
This commit is contained in:
Miguel Palhas
2026-09-02 17:18:00 +01:00
parent 21fc20b30a
commit acb84b91ef
3 changed files with 20 additions and 22 deletions
+9 -7
View File
@@ -51,13 +51,15 @@ command. `<duration>` 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
+6 -8
View File
@@ -68,13 +68,11 @@ const STATE_WORDS: Record<string, string> = {
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 `<span class="activity-rail" data-activity="${slug}" style="left:${left}px" role="img" title="${escapeHtml(stateWord)}" aria-label="${escapeHtml(stateWord)}"></span>`;
/** 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 `<span class="activity-rail" data-activity="${slug}" role="img" title="${escapeHtml(stateWord)}" aria-label="${escapeHtml(stateWord)}"></span>`;
}
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)}
<span class="card-line1">
<span class="leaf-name">${escapeHtml(name)}</span>
<span class="leaf-actions">
+5 -7
View File
@@ -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);