diff --git a/src/ui/assets/provider-logos/claude.svg b/src/ui/assets/provider-logos/claude.svg
new file mode 100644
index 00000000..a85b80fa
--- /dev/null
+++ b/src/ui/assets/provider-logos/claude.svg
@@ -0,0 +1 @@
+
diff --git a/src/ui/assets/provider-logos/copilot-dark.svg b/src/ui/assets/provider-logos/copilot-dark.svg
new file mode 100644
index 00000000..d09df805
--- /dev/null
+++ b/src/ui/assets/provider-logos/copilot-dark.svg
@@ -0,0 +1 @@
+
diff --git a/src/ui/assets/provider-logos/cursor-dark.svg b/src/ui/assets/provider-logos/cursor-dark.svg
new file mode 100644
index 00000000..d50421b5
--- /dev/null
+++ b/src/ui/assets/provider-logos/cursor-dark.svg
@@ -0,0 +1 @@
+
diff --git a/src/ui/assets/provider-logos/openai-dark.svg b/src/ui/assets/provider-logos/openai-dark.svg
new file mode 100644
index 00000000..7e19c92d
--- /dev/null
+++ b/src/ui/assets/provider-logos/openai-dark.svg
@@ -0,0 +1 @@
+
diff --git a/src/ui/assets/provider-logos/opencode-dark.svg b/src/ui/assets/provider-logos/opencode-dark.svg
new file mode 100644
index 00000000..62e10df4
--- /dev/null
+++ b/src/ui/assets/provider-logos/opencode-dark.svg
@@ -0,0 +1 @@
+
diff --git a/src/ui/assets/provider-logos/pi-on-dark.svg b/src/ui/assets/provider-logos/pi-on-dark.svg
new file mode 100644
index 00000000..7b55eca7
--- /dev/null
+++ b/src/ui/assets/provider-logos/pi-on-dark.svg
@@ -0,0 +1 @@
+
diff --git a/src/ui/card-types.test.ts b/src/ui/card-types.test.ts
index 3905ecd9..1648f025 100644
--- a/src/ui/card-types.test.ts
+++ b/src/ui/card-types.test.ts
@@ -78,6 +78,16 @@ test("a workspace card expands when it contains provider metadata", () => {
);
});
+test("a workspace card with details opens immediately", () => {
+ assert.equal(
+ isInitiallyExpandedCard({
+ tool: "open_workspace",
+ skills: [{ name: "research" }],
+ }),
+ true,
+ );
+});
+
test("a workspace card expands when it contains agent metadata", () => {
assert.equal(
isExpandableCard({
diff --git a/src/ui/card-types.ts b/src/ui/card-types.ts
index 107f3063..3d238083 100644
--- a/src/ui/card-types.ts
+++ b/src/ui/card-types.ts
@@ -187,6 +187,7 @@ export function isExpandableCard(card: ToolResultCard): boolean {
}
export function isInitiallyExpandedCard(card: ToolResultCard): boolean {
+ if (card.tool === "open_workspace") return isExpandableCard(card);
if (isReviewTool(card.tool)) return isExpandableCard(card);
if (isPatchTool(card.tool)) {
return card.files?.length === 1 && isExpandableCard(card);
diff --git a/src/ui/icons.ts b/src/ui/icons.ts
index 1713e0f8..022105d0 100644
--- a/src/ui/icons.ts
+++ b/src/ui/icons.ts
@@ -1,6 +1,6 @@
import {
- Blocks,
Bot,
+ Blocks,
ChevronDown,
CircleAlert,
Cpu,
@@ -52,6 +52,20 @@ export const toolIcons = {
export type ToolIcon = IconNode;
+const providerLogos = {
+ claude: new URL("./assets/provider-logos/claude.svg", import.meta.url).href,
+ codex: new URL("./assets/provider-logos/openai-dark.svg", import.meta.url).href,
+ copilot: new URL("./assets/provider-logos/copilot-dark.svg", import.meta.url).href,
+ cursor: new URL("./assets/provider-logos/cursor-dark.svg", import.meta.url).href,
+ opencode: new URL("./assets/provider-logos/opencode-dark.svg", import.meta.url).href,
+ pi: new URL("./assets/provider-logos/pi-on-dark.svg", import.meta.url).href,
+} as const;
+
+export function getProviderLogo(name: string): string | undefined {
+ const normalizedName = name.trim().toLowerCase() as keyof typeof providerLogos;
+ return providerLogos[normalizedName];
+}
+
export function renderIcon(icon: ToolIcon, className = "icon-svg"): SVGElement {
return createElement(icon, {
class: className,
diff --git a/src/ui/workspace-app.css b/src/ui/workspace-app.css
index 8a6ef00b..bee72228 100644
--- a/src/ui/workspace-app.css
+++ b/src/ui/workspace-app.css
@@ -313,6 +313,9 @@ body {
}
.workspace-key {
+ display: flex;
+ min-height: 22px;
+ align-items: center;
color: var(--color-text-tertiary, #a3a3aa);
font-size: var(--font-text-sm-size, 12px);
font-weight: 500;
@@ -374,6 +377,7 @@ body {
max-width: 100%;
min-height: 24px;
align-items: center;
+ gap: 5px;
overflow: hidden;
padding: 3px 8px;
border: 1px solid color-mix(in srgb, var(--tool-accent) 16%, var(--tool-card-divider));
@@ -386,127 +390,98 @@ body {
white-space: nowrap;
}
-.workspace-chip.muted {
- border-style: dashed;
- color: var(--color-text-tertiary, #a3a3aa);
- opacity: 0.72;
-}
-
-.workspace-row-disclosure {
- align-items: start;
+.workspace-chip-logo {
+ display: block;
+ width: 13px;
+ height: 13px;
+ flex: 0 0 auto;
+ object-fit: contain;
}
-.workspace-disclosure {
- display: grid;
+.workspace-chip-label {
min-width: 0;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 8px;
- align-items: start;
-}
-
-.workspace-skills-list {
- max-height: 24px;
-}
-
-.workspace-disclosure.expanded {
- grid-template-columns: minmax(0, 1fr);
-}
-
-.workspace-skills-disclosure.expanded .workspace-skills-list {
- max-height: none;
- flex-wrap: wrap;
- overflow: visible;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.workspace-disclosure-toggle {
+.workspace-agent-profile {
+ display: inline-flex;
+ max-width: 100%;
min-height: 24px;
- padding: 3px 9px;
- border: 1px solid color-mix(in srgb, var(--tool-accent) 28%, var(--tool-card-divider));
- border-radius: 9999px;
+ align-items: center;
+ gap: 5px;
+ overflow: hidden;
+ padding: 3px 2px 4px;
+ border: 0;
+ border-bottom: 1px solid color-mix(in srgb, var(--tool-accent) 34%, var(--tool-card-divider));
+ border-radius: 0;
background: transparent;
- color: var(--tool-accent);
- cursor: pointer;
- font: inherit;
- font-size: var(--font-text-sm-size, 11px);
+ color: var(--color-text-secondary, #c7c7ce);
+ font-size: 11px;
line-height: 1.25;
white-space: nowrap;
}
-.workspace-disclosure-toggle:hover {
- background: color-mix(in srgb, var(--tool-accent) 10%, transparent);
-}
-
-.workspace-disclosure-toggle:focus-visible {
- outline: 2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);
- outline-offset: 2px;
+.workspace-agent-profile-logo {
+ display: block;
+ width: 14px;
+ height: 14px;
+ flex: 0 0 auto;
+ object-fit: contain;
}
-.workspace-disclosure.expanded .workspace-disclosure-toggle {
- justify-self: start;
+.workspace-agent-profile:hover {
+ border-bottom-color: var(--tool-accent);
+ color: var(--color-text-primary, #f5f5f6);
}
-.workspace-value-list {
- display: grid;
- min-width: 0;
- max-height: calc(1.45em);
- overflow: hidden;
- color: var(--color-text-secondary, #c7c7ce);
- font-size: var(--font-text-sm-size, 12px);
- line-height: 1.45;
+.workspace-agent-profile.muted {
+ border-bottom-style: dashed;
+ color: var(--color-text-tertiary, #a3a3aa);
+ opacity: 0.72;
}
-.workspace-value-list.mono {
- font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
+.workspace-provider-logo {
+ display: inline-grid;
+ width: 20px;
+ height: 24px;
+ flex: 0 0 auto;
+ place-items: center;
+ cursor: help;
}
-.workspace-value-item {
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+.workspace-provider-logo-image {
+ display: block;
+ width: 16px;
+ height: 16px;
+ object-fit: contain;
}
-.workspace-disclosure.expanded .workspace-value-list {
- max-height: none;
- gap: 5px;
- overflow: visible;
+.workspace-provider-logo.muted {
+ opacity: 0.62;
}
-.workspace-instruction-summary {
- display: flex;
- min-width: 0;
- height: 24px;
- align-items: center;
- gap: 6px;
- overflow: hidden;
+.workspace-chip.muted {
+ border-style: dashed;
+ color: var(--color-text-tertiary, #a3a3aa);
+ opacity: 0.72;
}
-.workspace-instruction-summary-item {
- display: inline-flex;
- min-width: 0;
- flex: 0 1 auto;
- align-items: center;
- gap: 5px;
- overflow: hidden;
- padding: 2px 7px 2px 5px;
- border: 1px solid color-mix(in srgb, var(--tool-accent) 14%, var(--tool-card-divider));
- border-radius: 9999px;
- background: color-mix(in srgb, var(--tool-accent) 6%, transparent);
- color: var(--color-text-secondary, #c7c7ce);
- font-size: var(--font-text-sm-size, 11px);
- line-height: 1.25;
+.workspace-skills-row,
+.workspace-instructions-row,
+.workspace-agents-row {
+ align-items: start;
}
-.workspace-instruction-summary-item.available {
- border-style: dashed;
- color: var(--color-text-tertiary, #a3a3aa);
+.workspace-skills-list {
+ flex-wrap: wrap;
+ overflow: visible;
}
-.workspace-instruction-summary-name {
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+.workspace-agents-list {
+ flex-wrap: wrap;
+ overflow: visible;
}
.workspace-instruction-status {
@@ -535,7 +510,7 @@ body {
}
.workspace-instruction-list {
- display: none;
+ display: grid;
min-width: 0;
overflow: hidden;
border: 1px solid var(--tool-card-divider);
@@ -543,12 +518,38 @@ body {
background: color-mix(in srgb, var(--tool-card-body-bg) 88%, transparent);
}
-.workspace-instructions-disclosure.expanded .workspace-instruction-summary {
- display: none;
+.workspace-instructions-content {
+ display: block;
+ min-width: 0;
}
-.workspace-instructions-disclosure.expanded .workspace-instruction-list {
- display: grid;
+.workspace-instructions-toggle {
+ display: flex;
+ width: 100%;
+ min-height: 32px;
+ align-items: center;
+ justify-content: center;
+ padding: 6px 10px;
+ border: 0;
+ border-top: 1px solid var(--tool-card-divider);
+ border-radius: 0 0 9px 9px;
+ background: color-mix(in srgb, var(--tool-card-body-bg) 72%, transparent);
+ color: var(--tool-accent);
+ cursor: pointer;
+ font: inherit;
+ font-size: var(--font-text-sm-size, 11px);
+ font-weight: 550;
+ line-height: 1.25;
+ white-space: nowrap;
+}
+
+.workspace-instructions-toggle:hover {
+ background: var(--tool-card-hover-bg);
+}
+
+.workspace-instructions-toggle:focus-visible {
+ outline: 2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);
+ outline-offset: 2px;
}
.workspace-instruction-item + .workspace-instruction-item {
@@ -906,7 +907,7 @@ body {
.workspace-row > .workspace-key,
.workspace-row > .workspace-value,
.workspace-row > .workspace-chip-list,
- .workspace-row > .workspace-disclosure {
+ .workspace-row > .workspace-instructions-content {
grid-column: 2;
}
}
diff --git a/src/ui/workspace-app.tsx b/src/ui/workspace-app.tsx
index b5a69de0..eab9c501 100644
--- a/src/ui/workspace-app.tsx
+++ b/src/ui/workspace-app.tsx
@@ -20,7 +20,7 @@ import {
type ToolName,
type ToolResultCard,
} from "./card-types.js";
-import { renderIcon, toolIcons, type ToolIcon } from "./icons.js";
+import { getProviderLogo, renderIcon, toolIcons, type ToolIcon } from "./icons.js";
import {
getToolDisplay,
getToolHeaderSummary,
@@ -49,9 +49,7 @@ let errorMessage: string | null = null;
let currentPayload: MountedPayload | null = null;
let currentPayloadContainer: HTMLElement | null = null;
let openWorkspaceInstructionKey: string | null = null;
-
-type WorkspaceDisclosureKey = "instructions" | "skills" | "agents";
-const expandedWorkspaceDisclosures = new Set();
+let showAvailableWorkspaceInstructions = false;
const maybeAppRoot = document.querySelector("#app");
@@ -83,8 +81,8 @@ async function boot(): Promise {
card = null;
expanded = false;
reviewFilesExpanded = false;
- expandedWorkspaceDisclosures.clear();
openWorkspaceInstructionKey = null;
+ showAvailableWorkspaceInstructions = false;
errorMessage = "No result card is available for this tool result.";
render();
return;
@@ -94,8 +92,8 @@ async function boot(): Promise {
card = nextCard;
expanded = isInitiallyExpandedCard(nextCard);
reviewFilesExpanded = false;
- expandedWorkspaceDisclosures.clear();
openWorkspaceInstructionKey = null;
+ showAvailableWorkspaceInstructions = false;
errorMessage = null;
render();
};
@@ -107,7 +105,7 @@ async function boot(): Promise {
};
applyHostContext();
// Workspace details inherit host variables directly. Rebuilding their DOM on
- // iframe resize would reset an in-progress disclosure interaction.
+ // iframe resize would reset an in-progress instruction preview interaction.
if (card?.tool !== "open_workspace") renderPayloadIfNeeded();
};
@@ -524,32 +522,48 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v
}
const providers = card.agentProviders ?? [];
- if (providers.length > 0) {
- const providerChips: WorkspaceChip[] = [];
- for (const provider of providers) {
- const unavailable = provider.available === false;
- providerChips.push({
- label: provider.name ?? "Unknown provider",
- tone: unavailable ? "muted" : undefined,
- title: unavailable ? provider.reason ?? "Provider unavailable" : undefined,
- });
- }
- appendWorkspaceChipRow(rows, "Providers", providerChips, toolIcons.providers);
- }
-
- const agentChips = (card.agents ?? []).map((agent) => {
+ const agents = card.agents ?? [];
+ const agentChips: WorkspaceChip[] = agents.map((agent) => {
const name = agent.name ?? "Unnamed agent";
+ const providerName = agent.provider?.trim();
const unavailable = agent.providerAvailable === false;
- return {
- label: agent.provider ? `${name} ยท ${agent.provider}` : name,
- tone: unavailable ? "muted" as const : undefined,
- title: unavailable
+ const title = [
+ agent.description,
+ providerName ? `Provider: ${providerName}` : undefined,
+ agent.model ? `Model: ${agent.model}` : undefined,
+ agent.thinking ? `Thinking: ${agent.thinking}` : undefined,
+ unavailable
? agent.providerUnavailableReason ?? "Provider unavailable"
: undefined,
+ ].filter((value): value is string => Boolean(value)).join("\n");
+ return {
+ label: name,
+ logo: providerName ? getProviderLogo(providerName) : undefined,
+ profile: true,
+ tone: unavailable ? "muted" as const : undefined,
+ title: title || undefined,
};
});
+ const providerChips: WorkspaceChip[] = providers.map((provider) => {
+ const name = provider.name?.trim() || "Unknown provider";
+ const unavailable = provider.available === false;
+ const logo = getProviderLogo(name);
+ return {
+ label: name,
+ logo,
+ bareLogo: Boolean(logo),
+ ariaLabel: name,
+ tone: unavailable ? "muted" as const : undefined,
+ title: unavailable ? provider.reason ?? "Provider unavailable" : name,
+ };
+ });
+
if (agentChips.length > 0) {
- appendWorkspaceChipRow(rows, "Agents", agentChips, toolIcons.agents);
+ const chipList = renderWorkspaceChips([...agentChips, ...providerChips]);
+ chipList.classList.add("workspace-agents-list");
+ appendWorkspaceRow(rows, "Agents", chipList, toolIcons.agents, "workspace-agents-row");
+ } else if (providerChips.length > 0) {
+ appendWorkspaceChipRow(rows, "Providers", providerChips, toolIcons.providers);
}
if (rows.childElementCount > 0) details.append(rows);
@@ -563,6 +577,10 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v
interface WorkspaceChip {
label: string;
+ logo?: string;
+ profile?: boolean;
+ bareLogo?: boolean;
+ ariaLabel?: string;
title?: string;
tone?: "muted";
}
@@ -575,18 +593,6 @@ interface WorkspaceInstruction {
status: "loaded" | "available";
}
-interface WorkspaceDisclosureRowOptions {
- label: string;
- icon: ToolIcon;
- disclosureKey: WorkspaceDisclosureKey;
- content: HTMLElement[];
- expandable?: boolean;
- rowClassName?: string;
- disclosureClassName?: string;
- collapsedLabel?: string;
- onCollapse?: () => void;
-}
-
function appendWorkspaceInstructions(
container: HTMLElement,
loadedFiles: NonNullable,
@@ -615,62 +621,42 @@ function appendWorkspaceInstructions(
status: "available",
});
}
- const instructions: WorkspaceInstruction[] = [...loaded, ...available];
-
- if (instructions.length === 0) return;
-
- const canPreview = loaded.some((file) => file.content !== undefined);
- const canExpand = instructions.length > 1 || canPreview;
- const summary = renderWorkspaceInstructionSummary(instructions);
- const list = canExpand ? renderWorkspaceInstructionList(instructions) : undefined;
- appendWorkspaceDisclosureRow(container, {
- label: "Instructions",
- icon: toolIcons.instructions,
- disclosureKey: "instructions",
- content: list ? [summary, list] : [summary],
- expandable: canExpand,
- rowClassName: "workspace-instructions-row",
- disclosureClassName: "workspace-instructions-disclosure",
- collapsedLabel: instructions.length === 1 ? "View" : undefined,
- onCollapse: list
- ? () => {
- openWorkspaceInstructionKey = null;
- syncWorkspaceInstructionPreviews(list);
- }
- : undefined,
- });
-}
-
-function renderWorkspaceInstructionSummary(
- instructions: WorkspaceInstruction[],
-): HTMLElement {
- const summary = element("span", { className: "workspace-instruction-summary" });
- const basenameCounts = new Map();
- for (const instruction of instructions) {
- const basename = workspacePathBasename(instruction.label);
- basenameCounts.set(basename, (basenameCounts.get(basename) ?? 0) + 1);
- }
-
- for (const instruction of instructions) {
- const basename = workspacePathBasename(instruction.label);
- const item = element("span", {
- className: `workspace-instruction-summary-item ${instruction.status}`,
- title: instruction.path
- ? `${instructionStatusLabel(instruction.status)}: ${instruction.path}`
- : instructionStatusLabel(instruction.status),
+ if (loaded.length === 0 && available.length === 0) return;
+
+ const instructions = showAvailableWorkspaceInstructions
+ ? [...loaded, ...available]
+ : loaded;
+ const list = renderWorkspaceInstructionList(instructions);
+
+ if (available.length > 0) {
+ const showAll = showAvailableWorkspaceInstructions;
+ const toggle = element("button", {
+ className: "workspace-instructions-toggle",
+ type: "button",
+ text: showAll ? "Show less" : "View all",
+ ariaLabel: showAll
+ ? "Show only loaded instruction files"
+ : `View all ${available.length} available instruction files`,
+ ariaExpanded: String(showAll),
});
- item.append(
- renderWorkspaceInstructionStatus(instruction.status),
- element("span", {
- className: "workspace-instruction-summary-name",
- text: basenameCounts.get(basename) === 1
- ? basename
- : workspaceCompactPath(instruction.label),
- }),
- );
- summary.append(item);
+ toggle.addEventListener("click", () => {
+ showAvailableWorkspaceInstructions = !showAvailableWorkspaceInstructions;
+ if (!showAvailableWorkspaceInstructions) openWorkspaceInstructionKey = null;
+ render();
+ });
+ list.append(toggle);
}
- return summary;
+
+ const content = element("div", { className: "workspace-instructions-content" });
+ content.append(list);
+
+ appendWorkspaceRow(
+ container,
+ "Instructions",
+ content,
+ toolIcons.instructions,
+ "workspace-instructions-row",
+ );
}
function renderWorkspaceInstructionList(
@@ -777,11 +763,6 @@ function workspacePathBasename(path: string): string {
return parts.at(-1) ?? path;
}
-function workspaceCompactPath(path: string): string {
- const parts = path.replaceAll("\\", "/").split("/").filter(Boolean);
- return parts.slice(-2).join("/") || path;
-}
-
function appendWorkspaceTextRow(
container: HTMLElement,
label: string,
@@ -797,37 +778,6 @@ function appendWorkspaceTextRow(
appendWorkspaceRow(container, label, content, icon);
}
-function appendWorkspaceTextListRow(
- container: HTMLElement,
- label: string,
- values: string[],
- icon: ToolIcon,
- disclosureKey: WorkspaceDisclosureKey,
- mono = false,
-): void {
- if (values.length === 1) {
- appendWorkspaceTextRow(container, label, values[0], icon, mono);
- return;
- }
-
- const list = element("span", {
- className: `workspace-value-list${mono ? " mono" : ""}`,
- });
- for (const value of values) {
- list.append(element("span", {
- className: "workspace-value-item",
- text: value,
- title: value,
- }));
- }
- appendWorkspaceDisclosureRow(container, {
- label,
- icon,
- disclosureKey,
- content: [list],
- });
-}
-
function appendWorkspaceChipRow(
container: HTMLElement,
label: string,
@@ -842,8 +792,11 @@ function appendWorkspaceRow(
label: string,
content: HTMLElement,
icon: ToolIcon,
+ rowClassName?: string,
): void {
- const row = element("div", { className: "workspace-row" });
+ const row = element("div", {
+ className: ["workspace-row", rowClassName].filter(Boolean).join(" "),
+ });
row.append(
renderWorkspaceRowIcon(icon),
element("span", { className: "workspace-key", text: label }),
@@ -858,91 +811,12 @@ function appendWorkspaceSkills(
): void {
const skillChips = skills.map((skill) => ({
label: skill.name ?? skill.path ?? "Unnamed skill",
- title: skill.path,
+ title: [skill.path, skill.description].filter(Boolean).join("\n\n") || undefined,
}));
- if (skillChips.length === 1) {
- appendWorkspaceChipRow(container, "Skills", skillChips, toolIcons.skills);
- return;
- }
const chipList = renderWorkspaceChips(skillChips);
chipList.classList.add("workspace-skills-list");
- appendWorkspaceDisclosureRow(container, {
- label: "Skills",
- icon: toolIcons.skills,
- disclosureKey: "skills",
- content: [chipList],
- disclosureClassName: "workspace-skills-disclosure",
- });
-}
-
-function appendWorkspaceDisclosureRow(
- container: HTMLElement,
- options: WorkspaceDisclosureRowOptions,
-): void {
- const expandable = options.expandable ?? true;
- const initiallyExpanded = expandable && expandedWorkspaceDisclosures.has(options.disclosureKey);
- const row = element("div", {
- className: [
- "workspace-row",
- "workspace-row-disclosure",
- options.rowClassName,
- initiallyExpanded ? "expanded" : undefined,
- ].filter(Boolean).join(" "),
- });
- const disclosure = element("span", {
- className: [
- "workspace-disclosure",
- options.disclosureClassName,
- initiallyExpanded ? "expanded" : undefined,
- ].filter(Boolean).join(" "),
- });
- disclosure.append(...options.content);
-
- if (expandable) {
- const toggle = renderWorkspaceDisclosureToggle(
- initiallyExpanded,
- (nextExpanded) => {
- disclosure.classList.toggle("expanded", nextExpanded);
- row.classList.toggle("expanded", nextExpanded);
- if (nextExpanded) {
- expandedWorkspaceDisclosures.add(options.disclosureKey);
- } else {
- expandedWorkspaceDisclosures.delete(options.disclosureKey);
- options.onCollapse?.();
- }
- },
- options.collapsedLabel,
- );
- disclosure.append(toggle);
- }
-
- row.append(
- renderWorkspaceRowIcon(options.icon),
- element("span", { className: "workspace-key", text: options.label }),
- disclosure,
- );
- container.append(row);
-}
-
-function renderWorkspaceDisclosureToggle(
- expanded: boolean,
- onToggle: (expanded: boolean) => void,
- collapsedLabel = "View all",
-): HTMLButtonElement {
- const toggle = element("button", {
- className: "workspace-disclosure-toggle",
- type: "button",
- text: expanded ? "Show less" : collapsedLabel,
- ariaExpanded: String(expanded),
- });
- toggle.addEventListener("click", () => {
- const nextExpanded = toggle.getAttribute("aria-expanded") !== "true";
- toggle.setAttribute("aria-expanded", String(nextExpanded));
- toggle.textContent = nextExpanded ? "Show less" : collapsedLabel;
- onToggle(nextExpanded);
- });
- return toggle;
+ appendWorkspaceRow(container, "Skills", chipList, toolIcons.skills, "workspace-skills-row");
}
function renderWorkspaceRowIcon(icon: ToolIcon): HTMLElement {
@@ -957,11 +831,38 @@ function renderWorkspaceRowIcon(icon: ToolIcon): HTMLElement {
function renderWorkspaceChips(chips: WorkspaceChip[]): HTMLElement {
const list = element("span", { className: "workspace-chip-list" });
for (const chip of chips) {
- list.append(element("span", {
- className: `workspace-chip${chip.tone ? ` ${chip.tone}` : ""}`,
- text: chip.label,
+ const bareLogo = Boolean(chip.bareLogo && chip.logo);
+ const item = element("span", {
+ className: [
+ bareLogo
+ ? "workspace-provider-logo"
+ : chip.profile
+ ? "workspace-agent-profile"
+ : "workspace-chip",
+ chip.tone,
+ ].filter(Boolean).join(" "),
title: chip.title,
- }));
+ });
+ if (bareLogo) {
+ item.setAttribute("role", "img");
+ item.setAttribute("aria-label", chip.ariaLabel ?? chip.label);
+ }
+ if (chip.logo) {
+ const logo = document.createElement("img");
+ logo.className = bareLogo
+ ? "workspace-provider-logo-image"
+ : chip.profile
+ ? "workspace-agent-profile-logo"
+ : "workspace-chip-logo";
+ logo.src = chip.logo;
+ logo.alt = "";
+ logo.setAttribute("aria-hidden", "true");
+ item.append(logo);
+ }
+ if (!bareLogo) {
+ item.append(element("span", { className: "workspace-chip-label", text: chip.label }));
+ }
+ list.append(item);
}
return list;
}
@@ -1008,4 +909,3 @@ function element(
}
return node;
}
-