From 01a8dfc3a67ed5b2261d321a9e58dbd4d240b8ce Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Tue, 18 Aug 2026 08:33:33 +0800 Subject: [PATCH] fix(client): decouple the turn strip from the category palette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The turn strip's per-turn palette was positionally identical to the six category colors (turn 1 indigo = system, turn 2 amber = tools, ...). Sitting 4px under the bars, the strip read as a detached full-width bottom segment that never aligned with the bars above — the bars looked broken, especially in multi-turn sessions. The strip now uses a neutral two-tone zebra (adjacent turns still read as separate ranges) with secondary-label text; the in-turn bar tint goes neutral too. Verified against the live GUI: every composition bar's segments measure pixel-aligned; the strip now clearly reads as an axis. --- src/client/components/trendChart.tsx | 9 ++++++--- src/client/styles.ts | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/client/components/trendChart.tsx b/src/client/components/trendChart.tsx index 4cbc9e1..ab58909 100644 --- a/src/client/components/trendChart.tsx +++ b/src/client/components/trendChart.tsx @@ -83,8 +83,11 @@ export function makeTrendChart(kit: ViewKit): (props: TrendChartProps) => ReactN // compressing. The turn strip below mirrors the same column grid. const BAR_W = 14 const BAR_GAP = 2 - // Turn strip palette: one color per turn, cycling for long histories. - const TURN_COLORS = ['#6366f1', '#f59e0b', '#22c55e', '#a855f7', '#3b82f6', '#14b8a6', '#ef4444', '#ec4899'] + // Turn strip fills: a neutral zebra, deliberately DISJOINT from the + // category palette — the strip sits directly under the bars, and the old + // per-turn palette (identical to the six category colors) made it read as + // a detached, misaligned bottom segment of the composition bars. + const TURN_FILLS = ['rgba(128,128,128,0.12)', 'rgba(128,128,128,0.26)'] return function TrendChart(props: TrendChartProps): ReactNS.ReactElement { const requests = props.requests @@ -267,7 +270,7 @@ export function makeTrendChart(kit: ViewKit): (props: TrendChartProps) => ReactN className={'lc-turn' + (on ? ' lc-turn-on' : '')} style={{ width: blockW + 'px', - background: TURN_COLORS[gi % TURN_COLORS.length], + background: TURN_FILLS[gi % TURN_FILLS.length], }} title={'T' + grp.turn} onMouseEnter={() => { props.onHoverTurn(grp.turn) }} diff --git a/src/client/styles.ts b/src/client/styles.ts index 3a8bb5c..7c50052 100644 --- a/src/client/styles.ts +++ b/src/client/styles.ts @@ -73,13 +73,16 @@ export const STYLES = [ '.lc-bar:hover { background: var(--dsw-alias-bg-layer-2); }', '.lc-bar-selected { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }', '.lc-bar-hovered { outline: 1px dashed var(--dsw-alias-brand-primary); outline-offset: 1px; }', - '.lc-bar-in-turn { background: rgba(99,102,241,0.14); }', + '.lc-bar-in-turn { background: rgba(128,128,128,0.14); }', '.lc-bar-stack { display: flex; flex-direction: column-reverse; width: 100%; }', '.lc-bar-stack > div { width: 100%; }', '.lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }', + // Turn strip: NEUTRAL zebra fills (inline, see TURN_FILLS) with secondary + // text — never the category palette, or the strip reads as a detached + // bottom segment of the bars above it. '.lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; margin-top: 4px; }', - '.lc-turn { flex: none; box-sizing: border-box; text-align: center; font-size: 10px; line-height: 14px; font-weight: 600; color: #fff; border-radius: 3px; height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; transition: filter 120ms, opacity 120ms; }', - '.lc-turn-on { filter: brightness(1.35); box-shadow: 0 0 0 1px rgba(255,255,255,0.4); }', + '.lc-turn { flex: none; box-sizing: border-box; text-align: center; font-size: 10px; line-height: 14px; font-weight: 600; color: var(--dsw-alias-label-secondary); border-radius: 3px; height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; transition: filter 120ms, opacity 120ms; }', + '.lc-turn-on { filter: brightness(1.35); color: var(--dsw-alias-label-primary); }', '.lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }', '.lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }', '.lc-detail-head b { color: var(--dsw-alias-label-primary); }',