mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
fix(session-ui): drop the agent square from subagent rows
Remove the static per-agent square icon and its agent colour code from subagent rows. Delegating and running rows show the neutral dot-grid spinner in the same leading slot, and finished rows show no icon.
This commit is contained in:
@@ -5,9 +5,9 @@ story("shows a delegating row while subagent input streams", async ({ mount }) =
|
||||
const timeline = await mount("current-session-research-agents--agent-research", { args: { scenario: "delegation" } })
|
||||
const delegating = timeline.locator('[data-component="task-tool-delegating"]')
|
||||
await expect(delegating).toBeVisible()
|
||||
const shimmer = delegating.locator('[data-component="text-shimmer"]')
|
||||
await expect(shimmer).toHaveAttribute("aria-label", "Delegating agent…")
|
||||
await expect(shimmer).toHaveCSS("line-height", "16px")
|
||||
await expect(delegating).toHaveText("Delegating agent…")
|
||||
await expect(delegating).toHaveCSS("line-height", "16px")
|
||||
await expect(delegating.locator('[data-component="session-progress-indicator-v2"]')).toBeVisible()
|
||||
await expect(timeline.locator('[data-component="task-tool-card"]')).toHaveCount(0)
|
||||
await expect(timeline.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
|
||||
})
|
||||
|
||||
@@ -283,18 +283,6 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
[data-component="task-tool-spinner"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
[data-component="spinner"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="task-tool-action"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -338,7 +326,7 @@
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: var(--letter-spacing-normal);
|
||||
text-transform: capitalize;
|
||||
color: var(--task-agent-color-fallback, var(--text-strong));
|
||||
color: var(--text-strong);
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-subtitle"] {
|
||||
@@ -382,18 +370,6 @@
|
||||
background: var(--v2-background-bg-layer-02);
|
||||
}
|
||||
|
||||
[data-component="task-tool-spinner"],
|
||||
[data-component="task-tool-icon"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
[data-slot="icon-svg"] {
|
||||
color: var(--task-agent-color, var(--v2-icon-icon-base));
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="task-tool-title"] {
|
||||
height: 20px;
|
||||
font-family: var(--v2-font-family-sans, "Inter", sans-serif);
|
||||
@@ -420,10 +396,6 @@
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
[data-component="session-progress-indicator-v2"] {
|
||||
color: var(--task-agent-color, light-dark(var(--v2-text-text-base), #ffffff));
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
border-color: transparent;
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { TextShimmer } from "@opencode/ui/text-shimmer"
|
||||
import { For, type JSX } from "solid-js"
|
||||
import { CurrentSessionProviders } from "../storybook/current-session-story"
|
||||
import { storyDocument, storyTool } from "../storybook/current-session-scenarios"
|
||||
import { timelinePresets } from "../timeline/detail"
|
||||
import { SessionTimeline } from "../timeline/session-timeline"
|
||||
|
||||
export default {
|
||||
title: "OpenCode/Work/Subagent row variants",
|
||||
id: "current-subagent-row-variants",
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Design alternatives for child-Session rows without the old agent square. Production is the first variant; the others are static story-only sketches.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const productionDocument = storyDocument(
|
||||
[
|
||||
storyTool("variant_glob", "glob", "completed", { pattern: "src/timeline/**/*.ts" }),
|
||||
storyTool("variant_grep", "grep", "completed", { pattern: "timelineDetail" }),
|
||||
storyTool("variant_read", "read", "completed", { path: "src/timeline/projection.ts" }),
|
||||
storyTool(
|
||||
"variant_explore",
|
||||
"subagent",
|
||||
"completed",
|
||||
{ agent: "explore", description: "Map the timeline grouping rules", prompt: "Find where tool rows are grouped." },
|
||||
{ output: "Grouping lives in the timeline projection.", metadata: { sessionID: "session_child_explore" } },
|
||||
),
|
||||
storyTool(
|
||||
"variant_test",
|
||||
"subagent",
|
||||
"running",
|
||||
{ agent: "test", description: "Run the focused timeline tests", prompt: "Run the timeline tests." },
|
||||
{ metadata: { sessionID: "session_child_test" } },
|
||||
),
|
||||
storyTool("variant_delegating", "subagent", "streaming", {}, { raw: "" }),
|
||||
],
|
||||
true,
|
||||
)
|
||||
|
||||
const sketchRows = [
|
||||
{ title: "Explore", subtitle: "Map the timeline grouping rules", running: false },
|
||||
{ title: "Test", subtitle: "Run the focused timeline tests", running: true },
|
||||
]
|
||||
|
||||
function Frame(props: { title: string; children: JSX.Element }) {
|
||||
return (
|
||||
<section class="mx-auto w-full max-w-[720px] px-6 py-4">
|
||||
<h2 class="mb-3 text-[13px] leading-text-compact font-[530] text-v2-text-text-muted">{props.title}</h2>
|
||||
{props.children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function SketchContext() {
|
||||
return (
|
||||
<div class="h-9 flex items-center text-[13px] leading-text-compact text-v2-text-text-base">
|
||||
<span class="text-v2-text-text-muted">Used 3 </span>Glob, Grep, Read
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ShimmerWhileRunning() {
|
||||
return (
|
||||
<div class="flex flex-col gap-2 ps-6">
|
||||
<SketchContext />
|
||||
<For each={sketchRows}>
|
||||
{(row) => (
|
||||
<div data-component="task-tool-card" class="w-fit max-w-full">
|
||||
<div data-component="task-tool-surface">
|
||||
<div data-slot="basic-tool-tool-info-main" class="flex items-center">
|
||||
<span data-component="task-tool-title">
|
||||
<TextShimmer as="span" text={row.title} active={row.running} />
|
||||
</span>
|
||||
<span data-slot="basic-tool-tool-subtitle">{row.subtitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PlainLabel() {
|
||||
return (
|
||||
<div class="flex flex-col ps-6">
|
||||
<SketchContext />
|
||||
<For each={sketchRows}>
|
||||
{(row) => (
|
||||
<div class="h-9 flex items-center gap-2 text-[13px] leading-text-compact tracking-[-0.04px]">
|
||||
<span class="font-[530] text-v2-text-text-base">
|
||||
<TextShimmer as="span" text={row.title} active={row.running} />
|
||||
</span>
|
||||
<span class="font-[440] text-v2-text-text-muted">{row.subtitle}</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Variants = {
|
||||
render: () => (
|
||||
<div class="min-h-screen bg-v2-background-bg-base py-4">
|
||||
<Frame title="A. Dot-matrix spinner while running, no icon when done (production)">
|
||||
<CurrentSessionProviders document={productionDocument}>
|
||||
<SessionTimeline
|
||||
document={productionDocument}
|
||||
timelineDetail={{ ...timelinePresets[2].value, subagents: { placement: "separate" } }}
|
||||
/>
|
||||
</CurrentSessionProviders>
|
||||
</Frame>
|
||||
<Frame title="B. No icon, shimmering title while running (sketch)">
|
||||
<ShimmerWhileRunning />
|
||||
</Frame>
|
||||
<Frame title="C. Plain label without a pill (sketch)">
|
||||
<PlainLabel />
|
||||
</Frame>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@@ -229,83 +229,14 @@ function displayDirectory(path: string | undefined) {
|
||||
|
||||
import { resolveFileDiff } from "../components/session-diff"
|
||||
|
||||
const agentTones = new Map([
|
||||
["ask", "var(--icon-agent-ask-base)"],
|
||||
["build", "var(--icon-agent-build-base)"],
|
||||
["docs", "var(--icon-agent-docs-base)"],
|
||||
["plan", "var(--icon-agent-plan-base)"],
|
||||
])
|
||||
|
||||
const v2AgentTones = new Map([
|
||||
["build", "var(--v2-agent-build-solid)"],
|
||||
["explore", "var(--v2-agent-explore-solid)"],
|
||||
["plan", "var(--v2-agent-plan-solid)"],
|
||||
["review", "var(--v2-agent-review-solid)"],
|
||||
["writer", "var(--v2-agent-writer-solid)"],
|
||||
])
|
||||
|
||||
const agentThemeColors = new Map([
|
||||
["primary", "var(--text-interactive-base)"],
|
||||
["secondary", "var(--text-base)"],
|
||||
["accent", "var(--icon-info-base)"],
|
||||
["success", "var(--icon-success-base)"],
|
||||
["warning", "var(--icon-warning-base)"],
|
||||
["error", "var(--icon-critical-base)"],
|
||||
["info", "var(--icon-info-base)"],
|
||||
])
|
||||
|
||||
const v2AgentThemeColors = new Map([
|
||||
["primary", "var(--v2-text-text-accent)"],
|
||||
["secondary", "var(--v2-text-text-muted)"],
|
||||
["accent", "var(--v2-icon-icon-accent)"],
|
||||
["success", "var(--v2-state-fg-success)"],
|
||||
["warning", "var(--v2-state-fg-warning)"],
|
||||
["error", "var(--v2-state-fg-danger)"],
|
||||
["info", "var(--v2-state-fg-info)"],
|
||||
])
|
||||
|
||||
const agentPalette = [
|
||||
"var(--icon-agent-ask-base)",
|
||||
"var(--icon-agent-build-base)",
|
||||
"var(--icon-agent-docs-base)",
|
||||
"var(--icon-agent-plan-base)",
|
||||
"var(--syntax-info)",
|
||||
"var(--syntax-success)",
|
||||
"var(--syntax-warning)",
|
||||
"var(--syntax-property)",
|
||||
"var(--syntax-constant)",
|
||||
"var(--text-diff-add-base)",
|
||||
"var(--text-diff-delete-base)",
|
||||
"var(--icon-warning-base)",
|
||||
]
|
||||
|
||||
function tone(name: string) {
|
||||
let hash = 0
|
||||
|
||||
for (const char of name) hash = (hash * 31 + char.charCodeAt(0)) >>> 0
|
||||
|
||||
return agentPalette[hash % agentPalette.length]
|
||||
}
|
||||
|
||||
function taskAgent(raw: string | undefined, list?: readonly { name: string; color?: string }[]) {
|
||||
if (!raw) return {}
|
||||
function taskAgentName(raw: string | undefined, list?: readonly { name: string }[]) {
|
||||
if (!raw) return undefined
|
||||
const key = raw.toLowerCase()
|
||||
const item = list?.find((entry) => entry.name === raw || entry.name.toLowerCase() === key)
|
||||
const v2Tone = item?.color ? undefined : v2AgentTones.get(key)
|
||||
const color = agentColor(item?.color, agentThemeColors) ?? agentTones.get(key) ?? tone(key)
|
||||
const v2Color = agentColor(item?.color, v2AgentThemeColors) ?? v2Tone ?? color
|
||||
|
||||
return {
|
||||
name: item?.name ?? `${raw[0].toUpperCase()}${raw.slice(1)}`,
|
||||
color,
|
||||
v2Color,
|
||||
}
|
||||
}
|
||||
|
||||
function agentColor(value: string | undefined, themeColors: ReadonlyMap<string, string>) {
|
||||
if (!value) return undefined
|
||||
|
||||
return themeColors.get(value) ?? value
|
||||
return (
|
||||
list?.find((entry) => entry.name === raw || entry.name.toLowerCase() === key)?.name ??
|
||||
`${raw[0].toUpperCase()}${raw.slice(1)}`
|
||||
)
|
||||
}
|
||||
|
||||
const webSearchProviderNames = new Map([
|
||||
@@ -1758,10 +1689,7 @@ ToolRegistry.register({
|
||||
() => metadata().sessionID || taskSession(input().description, data.sessionID, data.store.session),
|
||||
)
|
||||
|
||||
const agent = createMemo(() => taskAgent(input().agent, data.store.agent))
|
||||
const title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
|
||||
const tone = createMemo(() => agent().color)
|
||||
const v2Tone = createMemo(() => agent().v2Color)
|
||||
const title = createMemo(() => taskAgentName(input().agent, data.store.agent) ?? i18n.t("ui.tool.agent.default"))
|
||||
|
||||
const background = createMemo(() => {
|
||||
return metadata().background === true || (props.status === "completed" && metadata().status === "running")
|
||||
@@ -1813,29 +1741,12 @@ ToolRegistry.register({
|
||||
}
|
||||
|
||||
const trigger = () => (
|
||||
<div
|
||||
data-component="task-tool-card"
|
||||
style={{
|
||||
"--task-agent-color": v2Tone(),
|
||||
"--task-agent-color-fallback": tone(),
|
||||
}}
|
||||
>
|
||||
<div data-component="task-tool-card">
|
||||
<div data-component="task-tool-surface">
|
||||
<div data-slot="basic-tool-tool-info-structured">
|
||||
<div data-slot="basic-tool-tool-info-main">
|
||||
<Show
|
||||
when={running()}
|
||||
fallback={
|
||||
<span data-component="task-tool-icon">
|
||||
<Icon name="subagent" size="normal" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span data-component="task-tool-spinner" style={{ color: tone() ?? "var(--icon-interactive-base)" }}>
|
||||
<SessionProgressIndicatorV2
|
||||
style={{ color: v2Tone() ?? "light-dark(var(--v2-text-text-base), #ffffff)" }}
|
||||
/>
|
||||
</span>
|
||||
<Show when={running()}>
|
||||
<SessionProgressIndicatorV2 />
|
||||
</Show>
|
||||
<span data-component="task-tool-title">{title()}</span>
|
||||
<Show when={subtitle()}>
|
||||
@@ -1871,10 +1782,10 @@ ToolRegistry.register({
|
||||
>
|
||||
<div
|
||||
data-component="task-tool-delegating"
|
||||
class="flex h-9 w-fit max-w-full items-center gap-2 rounded-[8px] bg-v2-background-bg-layer-02 p-2.5 text-[13px] font-[530] leading-text-compact tracking-[-0.04px]"
|
||||
class="flex h-9 w-fit max-w-full items-center gap-2 rounded-[8px] bg-v2-background-bg-layer-02 px-3 text-[13px] font-[530] leading-text-compact tracking-[-0.04px] text-v2-text-text-base"
|
||||
>
|
||||
<Icon name="subagent" size="normal" class="shrink-0 text-v2-icon-icon-faint" />
|
||||
<TextShimmer text={i18n.t("ui.tool.agent.delegating")} class="min-w-0 truncate" />
|
||||
<SessionProgressIndicatorV2 />
|
||||
<span class="min-w-0 truncate">{i18n.t("ui.tool.agent.delegating")}</span>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
||||
@@ -52,7 +52,6 @@ export const additionalIcons = {
|
||||
"magnifying-glass-menu": `<path d="M2.08325 10.0002H4.58325M2.08325 5.41683H5.41659M2.08325 14.5835H5.41659M16.4583 13.9585L18.7499 16.2502M17.9166 10.0002C17.9166 12.9917 15.4915 15.4168 12.4999 15.4168C9.50838 15.4168 7.08325 12.9917 7.08325 10.0002C7.08325 7.00862 9.50838 4.5835 12.4999 4.5835C15.4915 4.5835 17.9166 7.00862 17.9166 10.0002Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
"window-cursor": `<path d="M17.5 10.4167V3.75H2.5V17.0833H10.4166M17.9166 13.5897L11.6666 11.6667L13.5897 17.9167L15.032 15.0321L17.9166 13.5897Z" stroke="currentColor" stroke-linecap="square"/><path d="M5.41675 6.125C5.71576 6.12518 5.95777 6.36795 5.95777 6.66699C5.95759 6.96589 5.71565 7.20783 5.41675 7.20801C5.11771 7.20801 4.87494 6.966 4.87476 6.66699C4.87476 6.36784 5.1176 6.125 5.41675 6.125ZM8.33276 6.125C8.63191 6.125 8.87476 6.36784 8.87476 6.66699C8.87458 6.966 8.63181 7.20801 8.33276 7.20801C8.03386 7.20783 7.79192 6.96589 7.79175 6.66699C7.79175 6.36795 8.03376 6.12518 8.33276 6.125ZM11.2498 6.125C11.5489 6.125 11.7917 6.36784 11.7917 6.66699C11.7916 6.966 11.5488 7.20801 11.2498 7.20801C10.9507 7.20801 10.7079 6.966 10.7078 6.66699C10.7078 6.36784 10.9506 6.125 11.2498 6.125Z" fill="currentColor" stroke="currentColor" stroke-width="0.25" stroke-linecap="square"/>`,
|
||||
task: `<path d="M9.99992 2.0835V17.9168M7.08325 3.75016H2.08325V16.2502H7.08325M12.9166 16.2502H17.9166V3.75016H12.9166" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
subagent: `<path d="M4 4.5C4 4.22386 4.22386 4 4.5 4H11.5C11.7761 4 12 4.22386 12 4.5V11.5C12 11.7761 11.7761 12 11.5 12H4.5C4.22386 12 4 11.7761 4 11.5V4.5Z" fill="currentColor"/><path d="M14.5 1C14.7761 1 15 1.22386 15 1.5V14.5C15 14.7761 14.7761 15 14.5 15H1.5C1.22386 15 1 14.7761 1 14.5V1.5C1 1.22386 1.22386 1 1.5 1H14.5ZM2 14H14V2H2V14Z" fill="currentColor"/>`,
|
||||
stop: `<rect x="5" y="5" width="10" height="10" fill="currentColor"/>`,
|
||||
status: `<path d="M2 10V18H18V10M2 10V2H18V10M2 10H18M5 6H9M5 14H9" stroke="currentColor"/>`,
|
||||
"status-active": `<path d="M18 2H2V10H18V2Z" fill="currentColor" fill-opacity="0.1"/>
|
||||
@@ -114,7 +113,6 @@ export const additionalIcons = {
|
||||
export function additionalIconViewBox(name: keyof typeof additionalIcons) {
|
||||
return name === "magnifying-glass" ||
|
||||
name === "arrow-undo-down" ||
|
||||
name === "subagent" ||
|
||||
name === "notifications" ||
|
||||
name === "appearance" ||
|
||||
name === "extensions" ||
|
||||
|
||||
Reference in New Issue
Block a user