diff --git a/App/frontend/desktop/src/pages/plugin-conversation-controls.tsx b/App/frontend/desktop/src/pages/plugin-conversation-controls.tsx
index f62cd3b2f..43131009d 100644
--- a/App/frontend/desktop/src/pages/plugin-conversation-controls.tsx
+++ b/App/frontend/desktop/src/pages/plugin-conversation-controls.tsx
@@ -104,13 +104,15 @@ export function PinnedPluginCommandBar(props: {
// of the top-bar page and pending material collection calls.
const recording = target.command.topbar === true;
const open = recording ? props.recordingOpen : props.active.has(`${target.plugin.id}:${target.command.capabilityId}`);
+ const legalShortcut = target.plugin.id === "legal-labor"
+ && (target.command.command === "/legal-guide" || target.command.command === "/legal-record");
return (
");
});
it("renders the complete decoded Windows error in tool details", () => {
@@ -1629,7 +1628,7 @@ describe("AgentThreadMessages", () => {
// header, separate from the always-visible final answer bubble below it.
expect(html).toContain('aria-expanded="false"');
expect(html).toContain('data-icon="chevron-right"');
- expect(html).toContain("思考一下");
+ expect(html).toContain("简短思考");
expect(html).not.toContain("data-reasoning-key=");
expect(html).toContain("最终回答");
// Reasoning body is only rendered when the user expands the collapsed cluster.
@@ -1681,7 +1680,7 @@ describe("AgentThreadMessages", () => {
// Reasoning finished but answer is still streaming → cluster collapses (reasoning not running).
expect(html).toContain('aria-expanded="false"');
expect(html).toContain('data-icon="chevron-right"');
- expect(html).toContain("思考一下");
+ expect(html).toContain("简短思考");
expect(html).not.toContain("data-reasoning-key=");
expect(html).toContain("正在回答");
// Reasoning body is only rendered when the user expands the collapsed cluster.
@@ -1711,7 +1710,7 @@ describe("AgentThreadMessages", () => {
);
- expect(html).toContain("思考一下");
+ expect(html).toContain("简短思考");
expect(html).toContain("data-activity-key=");
expect(html).toContain("完成。");
expect(html).not.toContain("data-reasoning-key=");
diff --git a/App/frontend/desktop/src/pages/tests/conversation-features.interaction.test.tsx b/App/frontend/desktop/src/pages/tests/conversation-features.interaction.test.tsx
index aeb8ada00..f6e238e30 100644
--- a/App/frontend/desktop/src/pages/tests/conversation-features.interaction.test.tsx
+++ b/App/frontend/desktop/src/pages/tests/conversation-features.interaction.test.tsx
@@ -4,7 +4,7 @@ import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { I18nProvider } from "../../i18n/i18n-provider.js";
import { PluginUiProvider, usePluginUi } from "../../app/plugin-ui-context.js";
-import { conversationFeaturePanelOverlays, recordingArchiveMaterials, useConversationFeatures } from "../conversation-features.js";
+import { conversationFeaturePanelOverlays, recordingArchiveMaterials, recordingShortcutOpen, useConversationFeatures } from "../conversation-features.js";
import type { AgentChatMessage } from "../../state/agent-chat-slice.js";
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
@@ -91,6 +91,13 @@ describe("conversation feature integration", () => {
expect(features.pluginRecordingSession?.status).toBe("idle");
});
+ it("does not keep the recording shortcut active for a completed interview", () => {
+ expect(recordingShortcutOpen(false, { status: "recording" }, false)).toBe(true);
+ expect(recordingShortcutOpen(false, { status: "done" }, false)).toBe(false);
+ expect(recordingShortcutOpen(false, { status: "recording" }, true)).toBe(false);
+ expect(recordingShortcutOpen(true, { status: "done" }, false)).toBe(true);
+ });
+
it("overlays a standalone artifact preview when both columns cannot fit", () => {
expect(conversationFeaturePanelOverlays(659)).toBe(true);
expect(conversationFeaturePanelOverlays(660)).toBe(false);
diff --git a/App/frontend/desktop/src/styles.css b/App/frontend/desktop/src/styles.css
index 2673ad028..a727dbe7f 100644
--- a/App/frontend/desktop/src/styles.css
+++ b/App/frontend/desktop/src/styles.css
@@ -3730,6 +3730,40 @@ body.memmy-thread-panel-fullscreen .app-frame-main {
margin-bottom: 0;
}
+/* A quiet running turn sweeps its last visible line after three seconds. */
+.agent-stall-shimmer {
+ background-image:
+ linear-gradient(90deg, transparent, var(--color-action-sky-hover), transparent),
+ linear-gradient(var(--agent-stall-ink), var(--agent-stall-ink));
+ background-repeat: no-repeat;
+ background-size: var(--agent-stall-band) var(--agent-stall-height), 100% 100%;
+ background-position: var(--agent-stall-from) var(--agent-stall-y), 0 0;
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+ animation: agent-stall-sweep 1.8s linear infinite;
+}
+
+.agent-stall-shimmer :where(code, .agent-activity-diff, .agent-activity-diff *) {
+ -webkit-text-fill-color: currentColor;
+}
+
+@keyframes agent-stall-sweep {
+ from {
+ background-position: var(--agent-stall-from) var(--agent-stall-y), 0 0;
+ }
+ to {
+ background-position: var(--agent-stall-to) var(--agent-stall-y), 0 0;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .agent-stall-shimmer {
+ animation: none;
+ background-position: calc((var(--agent-stall-from) + var(--agent-stall-to)) / 2) var(--agent-stall-y), 0 0;
+ }
+}
+
.agent-thinking-pulse {
display: inline-block;
width: 6px;
@@ -4301,14 +4335,6 @@ button.agent-activity-file-link.agent-activity-timeline-item__detail {
margin: 8px 0 4px;
}
-.agent-activity-segment__live-thought-label {
- margin: 0;
- padding: 2px 0;
- color: color-mix(in srgb, var(--color-text-ink) 50%, transparent);
- font-size: 15px;
- line-height: 26.25px;
-}
-
/* Thinking-chain rail: a 2px hairline-coloured bar spanning the full height
of the reasoning text, 12px clear of the glyphs. */
.agent-reasoning-panel__body > .agent-message-content.agent-message-content--reasoning,