From d7808e8405e42fff08931d0505b2105866f7fff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B3=E5=A4=A9=E8=B1=AA?= Date: Sun, 20 Sep 2026 13:02:10 +0800 Subject: [PATCH] feat(ui): give the running session row an unmistakable working mark The session rail's only in-progress cue was the 8px StatusDot's opacity breathe - too subtle to read as "running", especially in peripheral vision, and `prefers-reduced-motion` removed even that, leaving a running row visually identical to a settled one. The signal gutter now wraps the dot in a spinning accent arc while a turn is working (`data-working`, set from the same `isPulsing` fact that already drives the pulse, so live responding and the persisted running fallback both get it). The rotation reuses the existing `maka-spin` keyframe; under reduced motion the animation drops and the arc stays as a static ring, which keeps a motion-independent mark on the row. The dot, its colour vocabulary, and the row's one-dot design are untouched; the arc is absolutely positioned in the existing fixed gutter, so rows do not shift. Fixes #5519 Generated-by: GLM-5.3-Flash (ZCode) --- .../session-history-working-mark.test.tsx | 86 +++++++++++++++++++ packages/ui/src/session-history-list.tsx | 9 +- packages/ui/src/styles.css | 29 +++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/__tests__/session-history-working-mark.test.tsx diff --git a/packages/ui/src/__tests__/session-history-working-mark.test.tsx b/packages/ui/src/__tests__/session-history-working-mark.test.tsx new file mode 100644 index 0000000000..9df348a4f4 --- /dev/null +++ b/packages/ui/src/__tests__/session-history-working-mark.test.tsx @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { parseHTML } from 'linkedom'; +import { renderToStaticMarkup } from 'react-dom/server'; +import type { SessionSummary } from '@maka/core/session'; +import { LocaleProvider } from '../locale-context.js'; +import { SessionHistoryList } from '../session-history-list.js'; +import { SessionRailProvider, type SessionRailData } from '../session-rail-context.js'; + +function Rail(props: Partial & { sessions: readonly SessionSummary[] }) { + const data: SessionRailData = { + groupVariant: 'conversation', + onSelectSession: () => undefined, + ...props, + }; + return ( + + + + + + ); +} + +const session: SessionSummary = { + id: 'session-1', + name: 'Release notes', + isFlagged: false, + isArchived: false, + labels: [], + hasUnread: false, + status: 'active', + backend: 'ai-sdk', + llmConnectionSlug: 'test-connection', + connectionLocked: true, + model: 'test-model', + permissionMode: 'ask', +}; + +test('the responding row’s signal carries the working mark', () => { + const dom = parseHTML( + renderToStaticMarkup( + , + ), + ); + assert.equal( + dom.document.querySelector('.maka-session-row-signal')?.getAttribute('data-working'), + 'true', + ); +}); + +test('a persisted running row keeps the working mark without live turns', () => { + const dom = parseHTML( + renderToStaticMarkup(), + ); + assert.equal( + dom.document.querySelector('.maka-session-row-signal')?.getAttribute('data-working'), + 'true', + ); +}); + +test('a settled row carries no working mark', () => { + const dom = parseHTML(renderToStaticMarkup()); + const signal = dom.document.querySelector('.maka-session-row-signal'); + assert.ok(signal, 'the signal gutter always renders'); + assert.equal(signal.getAttribute('data-working'), null); +}); diff --git a/packages/ui/src/session-history-list.tsx b/packages/ui/src/session-history-list.tsx index 924e235868..306952fde3 100644 --- a/packages/ui/src/session-history-list.tsx +++ b/packages/ui/src/session-history-list.tsx @@ -797,7 +797,14 @@ const SessionNavRow = memo(function SessionNavRow(props: { // whether or not it has a dot, so state reads as one column down the // rail instead of a mark that drifts with each title's length. icon={ - + {signal ? (