From 1e3d548c0b727232c35ecf24c136446b5ec83f48 Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 03:11:16 -0700 Subject: [PATCH 1/8] Align hover-card model metadata with composer --- plugins/thread-hover-cards/app.tsx | 4 ++-- plugins/thread-hover-cards/dist/app.js | 22 +++++++++++++------- plugins/thread-hover-cards/styles.ts | 20 ++++++++++++------ plugins/thread-hover-cards/test/app.test.mjs | 18 +++++++++++++++- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/plugins/thread-hover-cards/app.tsx b/plugins/thread-hover-cards/app.tsx index 7fda893..bbfbbc5 100644 --- a/plugins/thread-hover-cards/app.tsx +++ b/plugins/thread-hover-cards/app.tsx @@ -807,8 +807,8 @@ function renderSummary(card: HTMLElement, summary: ThreadSummary): void { ? REASONING_LABELS[summary.provider.reasoningLevel] : null; provider.title = reasoningLabel - ? `${summary.provider.displayName} · ${modelLabel} · ${reasoningLabel} reasoning` - : `${summary.provider.displayName} · ${modelLabel}`; + ? `${summary.provider.displayName}: ${modelLabel} · ${reasoningLabel} reasoning` + : `${summary.provider.displayName}: ${modelLabel}`; const providerIdentity = element( "div", "bb-thread-hover-card__provider-identity", diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index ccd26d2..fa654e8 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -613,7 +613,7 @@ var HOVER_CARD_CSS = String.raw` .bb-thread-hover-card__provider { flex: 1 1 auto; - gap: 0.3125rem; + gap: 0.25rem; color: var(--muted-foreground); } @@ -625,10 +625,18 @@ var HOVER_CARD_CSS = String.raw` overflow: hidden; } +.bb-thread-hover-card__provider-model, +.bb-thread-hover-card__reasoning { + font-size: 0.75rem; + line-height: 1.25; +} + .bb-thread-hover-card__reasoning { flex: none; - color: color-mix(in srgb, var(--muted-foreground) 76%, transparent); - font-size: 0.625rem; + color: var( + --subtle-foreground, + color-mix(in srgb, var(--muted-foreground) 76%, transparent) + ); white-space: nowrap; } @@ -715,9 +723,9 @@ var HOVER_CARD_CSS = String.raw` } .bb-thread-hover-card__provider-icon { - width: 0.8125rem; - height: 0.8125rem; - color: color-mix(in srgb, var(--muted-foreground) 82%, transparent); + width: 1rem; + height: 1rem; + color: var(--muted-foreground); object-fit: contain; } @@ -1737,7 +1745,7 @@ function renderSummary(card, summary) { summary.provider.id ); const reasoningLabel = summary.provider.reasoningLevel ? REASONING_LABELS[summary.provider.reasoningLevel] : null; - provider.title = reasoningLabel ? `${summary.provider.displayName} \xB7 ${modelLabel} \xB7 ${reasoningLabel} reasoning` : `${summary.provider.displayName} \xB7 ${modelLabel}`; + provider.title = reasoningLabel ? `${summary.provider.displayName}: ${modelLabel} \xB7 ${reasoningLabel} reasoning` : `${summary.provider.displayName}: ${modelLabel}`; const providerIdentity = element( "div", "bb-thread-hover-card__provider-identity" diff --git a/plugins/thread-hover-cards/styles.ts b/plugins/thread-hover-cards/styles.ts index eed88c2..9481654 100644 --- a/plugins/thread-hover-cards/styles.ts +++ b/plugins/thread-hover-cards/styles.ts @@ -77,7 +77,7 @@ export const HOVER_CARD_CSS = String.raw` .bb-thread-hover-card__provider { flex: 1 1 auto; - gap: 0.3125rem; + gap: 0.25rem; color: var(--muted-foreground); } @@ -89,10 +89,18 @@ export const HOVER_CARD_CSS = String.raw` overflow: hidden; } +.bb-thread-hover-card__provider-model, +.bb-thread-hover-card__reasoning { + font-size: 0.75rem; + line-height: 1.25; +} + .bb-thread-hover-card__reasoning { flex: none; - color: color-mix(in srgb, var(--muted-foreground) 76%, transparent); - font-size: 0.625rem; + color: var( + --subtle-foreground, + color-mix(in srgb, var(--muted-foreground) 76%, transparent) + ); white-space: nowrap; } @@ -179,9 +187,9 @@ export const HOVER_CARD_CSS = String.raw` } .bb-thread-hover-card__provider-icon { - width: 0.8125rem; - height: 0.8125rem; - color: color-mix(in srgb, var(--muted-foreground) 82%, transparent); + width: 1rem; + height: 1rem; + color: var(--muted-foreground); object-fit: contain; } diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index 51beb9e..5fa9205 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -406,7 +406,7 @@ globalThis.fetch = async (url, init) => { displayName: "Codex", id: "codex", logoUrl: null, - model: "GPT-5.6-Sol", + model: "gpt-5.6-sol", reasoningLevel: "xhigh", }, repository: isLocal @@ -516,6 +516,18 @@ assert.match( style.textContent, /\.bb-thread-hover-card__provider-model\.bb-thread-hover-card__truncate \{[\s\S]*?flex: 0 1 auto/, ); +assert.match( + style.textContent, + /\.bb-thread-hover-card__provider-model,[\s\S]*?\.bb-thread-hover-card__reasoning \{[\s\S]*?font-size: 0\.75rem;[\s\S]*?line-height: 1\.25/, +); +assert.match( + style.textContent, + /\.bb-thread-hover-card__reasoning \{[\s\S]*?--subtle-foreground/, +); +assert.match( + style.textContent, + /\.bb-thread-hover-card__provider-icon \{[\s\S]*?width: 1rem;[\s\S]*?height: 1rem;[\s\S]*?color: var\(--muted-foreground\)/, +); assert.doesNotMatch(style.textContent, /--font-mono/); assert.match(style.textContent, /\.bb-thread-hover-card__context/); assert.match( @@ -658,6 +670,10 @@ assert.equal( card.querySelector(".bb-thread-hover-card__reasoning")?.textContent, "Extra High", ); +assert.equal( + card.querySelector(".bb-thread-hover-card__provider")?.title, + "Codex: 5.6-Sol · Extra High reasoning", +); assert.equal( card.querySelector(".bb-thread-hover-card__provider-model")?.parentElement, card.querySelector(".bb-thread-hover-card__provider-identity"), From 2e8428215a164593cffd76a05b44d80d64a4eb6c Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 03:22:59 -0700 Subject: [PATCH 2/8] Remove misleading auto permission icon --- plugins/thread-hover-cards/app.tsx | 22 ++++++++++++-------- plugins/thread-hover-cards/dist/app.js | 20 ++++++++++-------- plugins/thread-hover-cards/test/app.test.mjs | 5 +++-- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/plugins/thread-hover-cards/app.tsx b/plugins/thread-hover-cards/app.tsx index bbfbbc5..96e2482 100644 --- a/plugins/thread-hover-cards/app.tsx +++ b/plugins/thread-hover-cards/app.tsx @@ -526,19 +526,23 @@ function permissionMetadata(summary: ThreadSummary): HTMLSpanElement | null { : summary.permissionMode === "accept-edits" || summary.permissionMode === "workspace-write" ? { definition: FolderEditIcon, name: "FolderEditIcon" } - : { definition: ViewIcon, name: "ViewIcon" }; + : summary.permissionMode === "readonly" + ? { definition: ViewIcon, name: "ViewIcon" } + : null; const access = element("span", "bb-thread-hover-card__access"); access.dataset.permissionMode = summary.permissionMode!; access.setAttribute("aria-label", `Permission: ${label}`); access.title = `Permission: ${label}`; - access.append( - icon( - permissionIcon.definition, - permissionIcon.name, - "bb-thread-hover-card__icon bb-thread-hover-card__permission-icon", - ), - document.createTextNode(label), - ); + if (permissionIcon) { + access.append( + icon( + permissionIcon.definition, + permissionIcon.name, + "bb-thread-hover-card__icon bb-thread-hover-card__permission-icon", + ), + ); + } + access.append(document.createTextNode(label)); return access; } diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index fa654e8..37ca0a2 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -1528,19 +1528,21 @@ function permissionMetadata(summary) { const permissionIcon = summary.permissionMode === "full" ? { definition: SquareUnlock02Icon, name: "SquareUnlock02Icon" - } : summary.permissionMode === "accept-edits" || summary.permissionMode === "workspace-write" ? { definition: FolderEditIcon, name: "FolderEditIcon" } : { definition: ViewIcon, name: "ViewIcon" }; + } : summary.permissionMode === "accept-edits" || summary.permissionMode === "workspace-write" ? { definition: FolderEditIcon, name: "FolderEditIcon" } : summary.permissionMode === "readonly" ? { definition: ViewIcon, name: "ViewIcon" } : null; const access = element("span", "bb-thread-hover-card__access"); access.dataset.permissionMode = summary.permissionMode; access.setAttribute("aria-label", `Permission: ${label}`); access.title = `Permission: ${label}`; - access.append( - icon( - permissionIcon.definition, - permissionIcon.name, - "bb-thread-hover-card__icon bb-thread-hover-card__permission-icon" - ), - document.createTextNode(label) - ); + if (permissionIcon) { + access.append( + icon( + permissionIcon.definition, + permissionIcon.name, + "bb-thread-hover-card__icon bb-thread-hover-card__permission-icon" + ) + ); + } + access.append(document.createTextNode(label)); return access; } function nextInlinePattern(source) { diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index 5fa9205..46808e3 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -1083,10 +1083,11 @@ assert.equal( card.querySelector(".bb-thread-hover-card__access")?.textContent, "Auto", ); -assert.ok( +assert.equal( card .querySelector(".bb-thread-hover-card__access") - ?.querySelector('[data-icon="ViewIcon"]'), + ?.querySelector("[data-icon]"), + null, ); assert.equal( card.querySelector(".bb-thread-hover-card__access")?.parentElement, From 103ef0d7ff3636add0a660ffdd4f2caf680e0c3e Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 03:32:01 -0700 Subject: [PATCH 3/8] Match Claude model labels to composer --- plugins/thread-hover-cards/app.tsx | 20 ++++++++-- plugins/thread-hover-cards/dist/app.js | 15 +++++-- plugins/thread-hover-cards/test/app.test.mjs | 42 +++++++++++++++++--- 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/plugins/thread-hover-cards/app.tsx b/plugins/thread-hover-cards/app.tsx index 96e2482..1496c1e 100644 --- a/plugins/thread-hover-cards/app.tsx +++ b/plugins/thread-hover-cards/app.tsx @@ -483,6 +483,23 @@ function refreshRunTime(card: HTMLElement): void { } function formatModelLabel(value: string, providerId: string): string { + if (providerId === "claude-code") { + return value + .replace(/^claude[-_\s]+/i, "") + .split(/[-_]/) + .map((part) => { + if (/^\d+(\.\d+)*$/.test(part)) return part; + if (/^[a-z]+$/i.test(part)) { + return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); + } + return part; + }) + .join(" ") + .replace(/(\d+)\[(\d+)([km])\]$/i, (_match, model, size, unit) => + `${model} (${size}${String(unit).toUpperCase()})`, + ); + } + const formatted = value .split("-") .map((part) => { @@ -496,9 +513,6 @@ function formatModelLabel(value: string, providerId: string): string { .join("-"); if (providerId === "codex") return formatted.replace(/^GPT-/i, ""); - if (providerId === "claude-code") { - return formatted.replace(/^Claude\s+/i, ""); - } return formatted; } diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index 37ca0a2..8df6c0f 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -1500,6 +1500,18 @@ function refreshRunTime(card) { } } function formatModelLabel(value, providerId) { + if (providerId === "claude-code") { + return value.replace(/^claude[-_\s]+/i, "").split(/[-_]/).map((part) => { + if (/^\d+(\.\d+)*$/.test(part)) return part; + if (/^[a-z]+$/i.test(part)) { + return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); + } + return part; + }).join(" ").replace( + /(\d+)\[(\d+)([km])\]$/i, + (_match, model, size, unit) => `${model} (${size}${String(unit).toUpperCase()})` + ); + } const formatted = value.split("-").map((part) => { if (part.toLowerCase() === "gpt") return "GPT"; if (/^\d+(\.\d+)*$/.test(part)) return part; @@ -1509,9 +1521,6 @@ function formatModelLabel(value, providerId) { return part; }).join("-"); if (providerId === "codex") return formatted.replace(/^GPT-/i, ""); - if (providerId === "claude-code") { - return formatted.replace(/^Claude\s+/i, ""); - } return formatted; } function permissionLabel(permissionMode) { diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index 46808e3..30b6cb2 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -211,6 +211,7 @@ globalThis.fetch = async (url, init) => { ); } const isLocal = request.threadId === "thr_local"; + const isClaude = request.threadId === "thr_claude"; const hasNoPullRequest = request.threadId === "thr_no_pr"; const pullRequestUnavailable = request.threadId === "thr_pr_unavailable"; const isDraftPullRequest = request.threadId === "thr_draft_pr"; @@ -393,7 +394,7 @@ globalThis.fetch = async (url, init) => { : hasNoPullRequest ? null : "**Agent update**—implementing concise hover cards for foo_bar_baz and \\_literal\\_", - permissionMode: isLocal + permissionMode: isLocal || isClaude ? "auto" : isDraftPullRequest ? "accept-edits" @@ -403,11 +404,11 @@ globalThis.fetch = async (url, init) => { ? { kind: "absent" } : { kind: "pending" }, provider: { - displayName: "Codex", - id: "codex", + displayName: isClaude ? "Claude" : "Codex", + id: isClaude ? "claude-code" : "codex", logoUrl: null, - model: "gpt-5.6-sol", - reasoningLevel: "xhigh", + model: isClaude ? "claude-sonnet-5" : "gpt-5.6-sol", + reasoningLevel: isClaude ? "medium" : "xhigh", }, repository: isLocal ? { @@ -1950,6 +1951,37 @@ assert.equal( false, ); +nestedThread.dispatchEvent( + new window.PointerEvent("pointerout", { + bubbles: true, + pointerType: "mouse", + relatedTarget: window.document.body, + }), +); +await new Promise((resolve) => setTimeout(resolve, 140)); +nestedThread.dataset.sidebarThreadId = "thr_claude"; +hoverOver(nestedThread); +await new Promise((resolve) => setTimeout(resolve, 20)); +assert.equal( + window.document.querySelector(".bb-thread-hover-card__provider-model") + ?.textContent, + "Sonnet 5", +); +assert.equal( + window.document.querySelector(".bb-thread-hover-card__reasoning")?.textContent, + "Medium", +); +assert.equal( + window.document.querySelector(".bb-thread-hover-card__access")?.textContent, + "Auto", +); +assert.equal( + window.document + .querySelector(".bb-thread-hover-card__access") + ?.querySelector("[data-icon]"), + null, +); + globalThis.__bbThreadHoverCards?.dispose(); assert.equal(window.document.getElementById("bb-thread-hover-card-styles"), null); assert.equal(window.document.getElementById("bb-section-hover-card-styles"), null); From 6fc2a8aab0b9ab744b7f98cfea4b79be94b3c988 Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 04:09:48 -0700 Subject: [PATCH 4/8] Align permission metadata with model row --- plugins/thread-hover-cards/dist/app.js | 14 ++++++-------- plugins/thread-hover-cards/styles.ts | 14 ++++++-------- plugins/thread-hover-cards/test/app.test.mjs | 10 +++++++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index 8df6c0f..88742c3 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -626,12 +626,14 @@ var HOVER_CARD_CSS = String.raw` } .bb-thread-hover-card__provider-model, -.bb-thread-hover-card__reasoning { +.bb-thread-hover-card__reasoning, +.bb-thread-hover-card__access { font-size: 0.75rem; line-height: 1.25; } -.bb-thread-hover-card__reasoning { +.bb-thread-hover-card__reasoning, +.bb-thread-hover-card__access { flex: none; color: var( --subtle-foreground, @@ -829,16 +831,12 @@ var HOVER_CARD_CSS = String.raw` } .bb-thread-hover-card__access { - flex: none; gap: 0.1875rem; - color: color-mix(in srgb, var(--muted-foreground) 76%, transparent); - font-size: 0.625rem; - white-space: nowrap; } .bb-thread-hover-card__permission-icon { - width: 0.625rem; - height: 0.625rem; + width: 0.75rem; + height: 0.75rem; color: currentColor; } diff --git a/plugins/thread-hover-cards/styles.ts b/plugins/thread-hover-cards/styles.ts index 9481654..9048f40 100644 --- a/plugins/thread-hover-cards/styles.ts +++ b/plugins/thread-hover-cards/styles.ts @@ -90,12 +90,14 @@ export const HOVER_CARD_CSS = String.raw` } .bb-thread-hover-card__provider-model, -.bb-thread-hover-card__reasoning { +.bb-thread-hover-card__reasoning, +.bb-thread-hover-card__access { font-size: 0.75rem; line-height: 1.25; } -.bb-thread-hover-card__reasoning { +.bb-thread-hover-card__reasoning, +.bb-thread-hover-card__access { flex: none; color: var( --subtle-foreground, @@ -293,16 +295,12 @@ export const HOVER_CARD_CSS = String.raw` } .bb-thread-hover-card__access { - flex: none; gap: 0.1875rem; - color: color-mix(in srgb, var(--muted-foreground) 76%, transparent); - font-size: 0.625rem; - white-space: nowrap; } .bb-thread-hover-card__permission-icon { - width: 0.625rem; - height: 0.625rem; + width: 0.75rem; + height: 0.75rem; color: currentColor; } diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index 30b6cb2..aa2002a 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -519,11 +519,11 @@ assert.match( ); assert.match( style.textContent, - /\.bb-thread-hover-card__provider-model,[\s\S]*?\.bb-thread-hover-card__reasoning \{[\s\S]*?font-size: 0\.75rem;[\s\S]*?line-height: 1\.25/, + /\.bb-thread-hover-card__provider-model,[\s\S]*?\.bb-thread-hover-card__reasoning,[\s\S]*?\.bb-thread-hover-card__access \{[\s\S]*?font-size: 0\.75rem;[\s\S]*?line-height: 1\.25/, ); assert.match( style.textContent, - /\.bb-thread-hover-card__reasoning \{[\s\S]*?--subtle-foreground/, + /\.bb-thread-hover-card__reasoning,[\s\S]*?\.bb-thread-hover-card__access \{[\s\S]*?--subtle-foreground/, ); assert.match( style.textContent, @@ -554,7 +554,11 @@ assert.match( assert.match(style.textContent, /\.bb-thread-hover-card__pr-status/); assert.match( style.textContent, - /\.bb-thread-hover-card__access \{[\s\S]*?flex: none;[\s\S]*?white-space: nowrap/, + /\.bb-thread-hover-card__reasoning,[\s\S]*?\.bb-thread-hover-card__access \{[\s\S]*?flex: none;[\s\S]*?--subtle-foreground,[\s\S]*?white-space: nowrap/, +); +assert.match( + style.textContent, + /\.bb-thread-hover-card__permission-icon \{[\s\S]*?width: 0\.75rem;[\s\S]*?height: 0\.75rem/, ); assert.match( style.textContent, From cbda4ed6889a37202ea2842c22c1ef16ead163d5 Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 04:25:16 -0700 Subject: [PATCH 5/8] Add permission mode icons to hover cards --- plugins/thread-hover-cards/app.tsx | 9 ++++--- plugins/thread-hover-cards/dist/app.js | 26 +++++++++++++++++++- plugins/thread-hover-cards/icons.ts | 25 +++++++++++++++++++ plugins/thread-hover-cards/test/app.test.mjs | 10 +++++--- 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/plugins/thread-hover-cards/app.tsx b/plugins/thread-hover-cards/app.tsx index 1496c1e..5df29bb 100644 --- a/plugins/thread-hover-cards/app.tsx +++ b/plugins/thread-hover-cards/app.tsx @@ -14,6 +14,7 @@ import { Loading03Icon, OpenAiIcon, PiIcon, + SecurityCheckIcon, SourceCodeIcon, SquareUnlock02Icon, ViewIcon, @@ -540,9 +541,11 @@ function permissionMetadata(summary: ThreadSummary): HTMLSpanElement | null { : summary.permissionMode === "accept-edits" || summary.permissionMode === "workspace-write" ? { definition: FolderEditIcon, name: "FolderEditIcon" } - : summary.permissionMode === "readonly" - ? { definition: ViewIcon, name: "ViewIcon" } - : null; + : summary.permissionMode === "auto" + ? { definition: SecurityCheckIcon, name: "SecurityCheckIcon" } + : summary.permissionMode === "readonly" + ? { definition: ViewIcon, name: "ViewIcon" } + : null; const access = element("span", "bb-thread-hover-card__access"); access.dataset.permissionMode = summary.permissionMode!; access.setAttribute("aria-label", `Permission: ${label}`); diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index 88742c3..e71c5ed 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -241,6 +241,30 @@ var SquareUnlock02Icon = [ } ] ]; +var SecurityCheckIcon = [ + [ + "path", + { + d: "M18.7088 3.49534C16.8165 2.55382 14.5009 2 12 2C9.4991 2 7.1835 2.55382 5.29116 3.49534C4.36318 3.95706 3.89919 4.18792 3.4496 4.91378C3 5.63965 3 6.34248 3 7.74814V11.2371C3 16.9205 7.54236 20.0804 10.173 21.4338C10.9067 21.8113 11.2735 22 12 22C12.7265 22 13.0933 21.8113 13.8269 21.4338C16.4576 20.0804 21 16.9205 21 11.2371L21 7.74814C21 6.34249 21 5.63966 20.5504 4.91378C20.1008 4.18791 19.6368 3.95706 18.7088 3.49534Z", + stroke: "currentColor", + strokeLinecap: "round", + strokeLinejoin: "round", + strokeWidth: "1.5", + key: "0" + } + ], + [ + "path", + { + d: "M9 11.5C9 11.5 10.4079 11.7519 11 13.5C11 13.5 12.5 10.5 15 9.5", + stroke: "currentColor", + strokeLinecap: "round", + strokeLinejoin: "round", + strokeWidth: "1.5", + key: "1" + } + ] +]; var FolderEditIcon = [ [ "path", @@ -1535,7 +1559,7 @@ function permissionMetadata(summary) { const permissionIcon = summary.permissionMode === "full" ? { definition: SquareUnlock02Icon, name: "SquareUnlock02Icon" - } : summary.permissionMode === "accept-edits" || summary.permissionMode === "workspace-write" ? { definition: FolderEditIcon, name: "FolderEditIcon" } : summary.permissionMode === "readonly" ? { definition: ViewIcon, name: "ViewIcon" } : null; + } : summary.permissionMode === "accept-edits" || summary.permissionMode === "workspace-write" ? { definition: FolderEditIcon, name: "FolderEditIcon" } : summary.permissionMode === "auto" ? { definition: SecurityCheckIcon, name: "SecurityCheckIcon" } : summary.permissionMode === "readonly" ? { definition: ViewIcon, name: "ViewIcon" } : null; const access = element("span", "bb-thread-hover-card__access"); access.dataset.permissionMode = summary.permissionMode; access.setAttribute("aria-label", `Permission: ${label}`); diff --git a/plugins/thread-hover-cards/icons.ts b/plugins/thread-hover-cards/icons.ts index b483d7c..6b4e384 100644 --- a/plugins/thread-hover-cards/icons.ts +++ b/plugins/thread-hover-cards/icons.ts @@ -224,6 +224,31 @@ export const SquareUnlock02Icon = [ ], ] as const; +export const SecurityCheckIcon = [ + [ + "path", + { + d: "M18.7088 3.49534C16.8165 2.55382 14.5009 2 12 2C9.4991 2 7.1835 2.55382 5.29116 3.49534C4.36318 3.95706 3.89919 4.18792 3.4496 4.91378C3 5.63965 3 6.34248 3 7.74814V11.2371C3 16.9205 7.54236 20.0804 10.173 21.4338C10.9067 21.8113 11.2735 22 12 22C12.7265 22 13.0933 21.8113 13.8269 21.4338C16.4576 20.0804 21 16.9205 21 11.2371L21 7.74814C21 6.34249 21 5.63966 20.5504 4.91378C20.1008 4.18791 19.6368 3.95706 18.7088 3.49534Z", + stroke: "currentColor", + strokeLinecap: "round", + strokeLinejoin: "round", + strokeWidth: "1.5", + key: "0", + }, + ], + [ + "path", + { + d: "M9 11.5C9 11.5 10.4079 11.7519 11 13.5C11 13.5 12.5 10.5 15 9.5", + stroke: "currentColor", + strokeLinecap: "round", + strokeLinejoin: "round", + strokeWidth: "1.5", + key: "1", + }, + ], +] as const; + export const FolderEditIcon = [ [ "path", diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index aa2002a..e87a52d 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -1091,8 +1091,9 @@ assert.equal( assert.equal( card .querySelector(".bb-thread-hover-card__access") - ?.querySelector("[data-icon]"), - null, + ?.querySelector("[data-icon]") + ?.getAttribute("data-icon"), + "SecurityCheckIcon", ); assert.equal( card.querySelector(".bb-thread-hover-card__access")?.parentElement, @@ -1982,8 +1983,9 @@ assert.equal( assert.equal( window.document .querySelector(".bb-thread-hover-card__access") - ?.querySelector("[data-icon]"), - null, + ?.querySelector("[data-icon]") + ?.getAttribute("data-icon"), + "SecurityCheckIcon", ); globalThis.__bbThreadHoverCards?.dispose(); From e8291518cdaa9a02786bf5655f80c9a511ac2987 Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 04:41:44 -0700 Subject: [PATCH 6/8] Separate permission metadata in hover cards --- plugins/thread-hover-cards/dist/app.js | 1 + plugins/thread-hover-cards/styles.ts | 1 + plugins/thread-hover-cards/test/app.test.mjs | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index e71c5ed..87e53fd 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -856,6 +856,7 @@ var HOVER_CARD_CSS = String.raw` .bb-thread-hover-card__access { gap: 0.1875rem; + margin-left: 0.25rem; } .bb-thread-hover-card__permission-icon { diff --git a/plugins/thread-hover-cards/styles.ts b/plugins/thread-hover-cards/styles.ts index 9048f40..e362252 100644 --- a/plugins/thread-hover-cards/styles.ts +++ b/plugins/thread-hover-cards/styles.ts @@ -296,6 +296,7 @@ export const HOVER_CARD_CSS = String.raw` .bb-thread-hover-card__access { gap: 0.1875rem; + margin-left: 0.25rem; } .bb-thread-hover-card__permission-icon { diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index e87a52d..6a7ea8a 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -556,6 +556,10 @@ assert.match( style.textContent, /\.bb-thread-hover-card__reasoning,[\s\S]*?\.bb-thread-hover-card__access \{[\s\S]*?flex: none;[\s\S]*?--subtle-foreground,[\s\S]*?white-space: nowrap/, ); +assert.match( + style.textContent, + /\.bb-thread-hover-card__access \{[\s\S]*?gap: 0\.1875rem;[\s\S]*?margin-left: 0\.25rem/, +); assert.match( style.textContent, /\.bb-thread-hover-card__permission-icon \{[\s\S]*?width: 0\.75rem;[\s\S]*?height: 0\.75rem/, From 151241a483336910fd6a5ea828be1e7882d0a49d Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 05:15:20 -0700 Subject: [PATCH 7/8] Mute safe permission labels in hover cards --- plugins/thread-hover-cards/dist/app.js | 6 ++++++ plugins/thread-hover-cards/styles.ts | 6 ++++++ plugins/thread-hover-cards/test/app.test.mjs | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index 87e53fd..8f410ec 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -865,6 +865,12 @@ var HOVER_CARD_CSS = String.raw` color: currentColor; } +.bb-thread-hover-card__access[data-permission-mode="accept-edits"], +.bb-thread-hover-card__access[data-permission-mode="workspace-write"], +.bb-thread-hover-card__access[data-permission-mode="auto"] { + color: color-mix(in srgb, var(--muted-foreground) 72%, transparent); +} + .bb-thread-hover-card__access[data-permission-mode="full"] { color: color-mix( in srgb, diff --git a/plugins/thread-hover-cards/styles.ts b/plugins/thread-hover-cards/styles.ts index e362252..b2cdd97 100644 --- a/plugins/thread-hover-cards/styles.ts +++ b/plugins/thread-hover-cards/styles.ts @@ -305,6 +305,12 @@ export const HOVER_CARD_CSS = String.raw` color: currentColor; } +.bb-thread-hover-card__access[data-permission-mode="accept-edits"], +.bb-thread-hover-card__access[data-permission-mode="workspace-write"], +.bb-thread-hover-card__access[data-permission-mode="auto"] { + color: color-mix(in srgb, var(--muted-foreground) 72%, transparent); +} + .bb-thread-hover-card__access[data-permission-mode="full"] { color: color-mix( in srgb, diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index 6a7ea8a..a3beb93 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -564,6 +564,10 @@ assert.match( style.textContent, /\.bb-thread-hover-card__permission-icon \{[\s\S]*?width: 0\.75rem;[\s\S]*?height: 0\.75rem/, ); +assert.match( + style.textContent, + /\.bb-thread-hover-card__access\[data-permission-mode="accept-edits"\],[\s\S]*?\.bb-thread-hover-card__access\[data-permission-mode="auto"\] \{[\s\S]*?var\(--muted-foreground\) 72%, transparent/, +); assert.match( style.textContent, /\.bb-thread-hover-card__access\[data-permission-mode="full"\][\s\S]*?var\(--warning-text, var\(--warning\)\)/, From 2424a0ab5d03191102c6dfb435af2015b98a25d5 Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 05:39:51 -0700 Subject: [PATCH 8/8] Fix Claude model version labels --- plugins/thread-hover-cards/app.tsx | 14 ++++++--- plugins/thread-hover-cards/dist/app.js | 11 ++++--- plugins/thread-hover-cards/test/app.test.mjs | 32 +++++++++++++++++--- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/plugins/thread-hover-cards/app.tsx b/plugins/thread-hover-cards/app.tsx index 5df29bb..4327513 100644 --- a/plugins/thread-hover-cards/app.tsx +++ b/plugins/thread-hover-cards/app.tsx @@ -485,7 +485,10 @@ function refreshRunTime(card: HTMLElement): void { function formatModelLabel(value: string, providerId: string): string { if (providerId === "claude-code") { - return value + const contextMatch = value.match(/^(.*)\[(\d+(?:\.\d+)?[km])\]$/i); + const modelId = contextMatch?.[1] ?? value; + const context = contextMatch?.[2]?.toUpperCase(); + const formatted = modelId .replace(/^claude[-_\s]+/i, "") .split(/[-_]/) .map((part) => { @@ -495,10 +498,11 @@ function formatModelLabel(value: string, providerId: string): string { } return part; }) - .join(" ") - .replace(/(\d+)\[(\d+)([km])\]$/i, (_match, model, size, unit) => - `${model} (${size}${String(unit).toUpperCase()})`, - ); + .join("-") + .replace(/-(\d+)-(\d+)(?=-|$)/, "-$1.$2") + .split("-") + .join(" "); + return context ? `${formatted} (${context})` : formatted; } const formatted = value diff --git a/plugins/thread-hover-cards/dist/app.js b/plugins/thread-hover-cards/dist/app.js index 8f410ec..ff0c961 100644 --- a/plugins/thread-hover-cards/dist/app.js +++ b/plugins/thread-hover-cards/dist/app.js @@ -1530,16 +1530,17 @@ function refreshRunTime(card) { } function formatModelLabel(value, providerId) { if (providerId === "claude-code") { - return value.replace(/^claude[-_\s]+/i, "").split(/[-_]/).map((part) => { + const contextMatch = value.match(/^(.*)\[(\d+(?:\.\d+)?[km])\]$/i); + const modelId = contextMatch?.[1] ?? value; + const context = contextMatch?.[2]?.toUpperCase(); + const formatted2 = modelId.replace(/^claude[-_\s]+/i, "").split(/[-_]/).map((part) => { if (/^\d+(\.\d+)*$/.test(part)) return part; if (/^[a-z]+$/i.test(part)) { return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); } return part; - }).join(" ").replace( - /(\d+)\[(\d+)([km])\]$/i, - (_match, model, size, unit) => `${model} (${size}${String(unit).toUpperCase()})` - ); + }).join("-").replace(/-(\d+)-(\d+)(?=-|$)/, "-$1.$2").split("-").join(" "); + return context ? `${formatted2} (${context})` : formatted2; } const formatted = value.split("-").map((part) => { if (part.toLowerCase() === "gpt") return "GPT"; diff --git a/plugins/thread-hover-cards/test/app.test.mjs b/plugins/thread-hover-cards/test/app.test.mjs index a3beb93..256bdfc 100644 --- a/plugins/thread-hover-cards/test/app.test.mjs +++ b/plugins/thread-hover-cards/test/app.test.mjs @@ -212,6 +212,7 @@ globalThis.fetch = async (url, init) => { } const isLocal = request.threadId === "thr_local"; const isClaude = request.threadId === "thr_claude"; + const isClaudeVersion = request.threadId === "thr_claude_version"; const hasNoPullRequest = request.threadId === "thr_no_pr"; const pullRequestUnavailable = request.threadId === "thr_pr_unavailable"; const isDraftPullRequest = request.threadId === "thr_draft_pr"; @@ -394,7 +395,7 @@ globalThis.fetch = async (url, init) => { : hasNoPullRequest ? null : "**Agent update**—implementing concise hover cards for foo_bar_baz and \\_literal\\_", - permissionMode: isLocal || isClaude + permissionMode: isLocal || isClaude || isClaudeVersion ? "auto" : isDraftPullRequest ? "accept-edits" @@ -404,11 +405,15 @@ globalThis.fetch = async (url, init) => { ? { kind: "absent" } : { kind: "pending" }, provider: { - displayName: isClaude ? "Claude" : "Codex", - id: isClaude ? "claude-code" : "codex", + displayName: isClaude || isClaudeVersion ? "Claude" : "Codex", + id: isClaude || isClaudeVersion ? "claude-code" : "codex", logoUrl: null, - model: isClaude ? "claude-sonnet-5" : "gpt-5.6-sol", - reasoningLevel: isClaude ? "medium" : "xhigh", + model: isClaudeVersion + ? "claude-opus-4-8[1m]" + : isClaude + ? "claude-sonnet-5" + : "gpt-5.6-sol", + reasoningLevel: isClaude || isClaudeVersion ? "medium" : "xhigh", }, repository: isLocal ? { @@ -1996,6 +2001,23 @@ assert.equal( "SecurityCheckIcon", ); +nestedThread.dispatchEvent( + new window.PointerEvent("pointerout", { + bubbles: true, + pointerType: "mouse", + relatedTarget: window.document.body, + }), +); +await new Promise((resolve) => setTimeout(resolve, 140)); +nestedThread.dataset.sidebarThreadId = "thr_claude_version"; +hoverOver(nestedThread); +await new Promise((resolve) => setTimeout(resolve, 20)); +assert.equal( + window.document.querySelector(".bb-thread-hover-card__provider-model") + ?.textContent, + "Opus 4.8 (1M)", +); + globalThis.__bbThreadHoverCards?.dispose(); assert.equal(window.document.getElementById("bb-thread-hover-card-styles"), null); assert.equal(window.document.getElementById("bb-section-hover-card-styles"), null);