Skip to content

Commit 6ea6964

Browse files
mux-bot[bot]Muxmux-botammar-agent
authored
🤖 refactor: auto-cleanup (#3169)
## Summary Long-lived auto-cleanup PR that accumulates low-risk, behavior-preserving refactors picked from recent `main` commits. ## Changes ### Use shared `isAbortError` utility in AuthTokenModal Replace two inline `error instanceof DOMException && error.name === "AbortError"` checks in `AuthTokenModal.tsx` with the existing shared `isAbortError()` utility from `@/browser/utils/isAbortError`, deduplicating the abort detection logic. ### Extract `extractChunkDeltaText` helper to deduplicate advisor chunk parsing Pull the repeated `switch` over `chunk.type` (extracting `chunk.textDelta` or `chunk.argsTextDelta`) into a single `extractChunkDeltaText()` helper in `advisorService.ts`, then call it from both `executeAdvisorStream` and `executeAdvisorStreamWithRetry`. ### Remove unnecessary exports from `skillFileUtils` Un-export `parseSkillFile`, `serializeSkillFile`, and `SKILL_FILENAME` from `src/node/services/agentSkills/skillFileUtils.ts` — all three are only used within the same file, so the `export` keyword was unnecessary. ### Remove dead `getCancelledCompactionKey` storage helper Remove the `getCancelledCompactionKey` function and its entry in the `EPHEMERAL_WORKSPACE_KEY_FUNCTIONS` array from `storage.ts` — the only consumer (`useResumeManager.ts`) was deleted, leaving this as dead code. ### Remove dead `quickReviewNotes` module Remove `src/browser/utils/review/quickReviewNotes.ts` and its test file (482 lines). The `buildQuickLineReviewNote` and `buildQuickHunkReviewNote` functions were never imported by any production code since their introduction in PR #2448. ### Un-export `isBashOutputTool` in messageUtils Remove the `export` keyword from `isBashOutputTool` in `src/browser/utils/messages/messageUtils.ts` — the type guard is only used within the same file by `computeBashOutputGroupInfos`, so the export was unnecessary. ### Deduplicate `hasErrorCode` in submoduleSync Replace inline `NodeJS.ErrnoException`-like error-code checks in `submoduleSync.ts` with calls to the existing `hasErrorCode` helper, keeping a single canonical place where error-code narrowing lives. ### Simplify `hasCompletedDescendants` to reuse `listCompletedDescendantAgentTaskIds` Rewrite `hasCompletedDescendants` to delegate to the existing `listCompletedDescendantAgentTaskIds` helper instead of re-implementing the traversal, collapsing the two code paths into one. ### Reuse `anthropicSupportsNativeXhigh` in Anthropic fetch wrapper Replace the duplicated Opus 4.7+ regex inside `wrapFetchWithAnthropicCacheControl` (src/node/services/providerModelFactory.ts) with a call to the existing `anthropicSupportsNativeXhigh` helper from `src/common/types/thinking.ts`. The helper already performs the same regex check plus provider-prefix normalization (e.g., `anthropic/claude-opus-4-7` via the `ai-model-id` gateway header), keeping the wire-level detection and the policy-level detection in one place. ### Extract `getFetchInputUrl` helper to deduplicate URL extraction The OpenAI/Codex and Copilot fetch wrappers in `providerModelFactory.ts` each contained an identical 15-line IIFE that extracted a URL string from the `fetch` `input` argument (handling string, `URL`, and `Request`-like shapes). Extract the logic into a single `getFetchInputUrl` helper so both wrappers share one implementation. Behavior-preserving: the helper returns the same empty-string fallback on unrecognized inputs, so callers continue to fall through to normal fetch behavior without throwing. ### Extract `clonePersistedToolModelUsage` helper in streamManager The deep-clone pattern for `PersistedToolModelUsage` (spread event, fresh `usage` object, conditional `providerMetadata`) was duplicated between `recordToolModelUsage` and the stream-end tool-usage snapshot in `streamManager.ts`. Extract a single file-local helper so both sites share the same implementation. Behavior-preserving: both callsites continue to produce structurally identical clones. ### Reuse `getClosestTranscriptAncestor` in `getTranscriptContextMenuLink` The new `getTranscriptContextMenuLink` helper (added in #3188) inlined the same "resolve event target → `element.closest(selector)` → require both to stay within the transcript root" pattern that `getClosestTranscriptAncestor` — defined a few lines above in the same file — already implements. Delegate to the shared helper so the null/contains guards live in one place. Behavior-preserving: the helper returns null for a null/outside-root target, then `element.closest("a[href]")`, then null again if the anchor is outside the transcript root — identical to the previous inline checks. All 22 `transcriptContextMenu` tests continue to pass. ### Remove duplicate `gpt-5.5-pro` thinking-policy test When #3192 renamed `gpt-5.4-pro` → `gpt-5.5-pro` across `src/common/utils/thinking/policy.test.ts`, it accidentally introduced a third `returns medium/high/xhigh for gpt-5.5-pro` test that is byte-identical to the renamed first occurrence (the two remaining tests are the bare-prefix and `with version suffix` variants; the deleted block had no version suffix and no gateway prefix). Drop the duplicate so the suite has one canonical no-suffix test, one mux-gateway test, and one version-suffix test. Behavior-preserving — `getThinkingPolicyForModel` coverage for `gpt-5.5-pro` is unchanged; 63 / 63 tests in `policy.test.ts` continue to pass. ### Extract `getAppProxyBasePathFromRequestValue` helper in orpc server The orpc server's public-base-path detection in `src/node/orpc/server.ts` repeated the pattern `parsePathnameFromRequestValue(value) → getAppProxyBasePathFromPathname(...)` across four callsites (forwarded headers, the `originalUrl` / `url` loop, the referer header, and the direct app-proxy handler-prefix calculator). Extract a single `getAppProxyBasePathFromRequestValue` helper that performs the two-step normalize-then-classify operation, then call it from every site. Behavior-preserving: each callsite still produces `null` when the value is absent or yields an invalid pathname, and otherwise returns the same parsed app-proxy base path. All 52 tests in `src/node/orpc/server.test.ts` continue to pass. ### Inline `getRoutePathnameForBaseHref` wrapper in orpc server The new helper added in #3195 was a one-line shim that simply renamed `getPathnameFromRequestUrl(req.url)` to fit the surrounding "for base href" naming theme. It was used in only two adjacent functions (`shouldInjectSlashlessRootRedirect` and `getPublicBaseHref`), and the existing `getPathnameFromRequestUrl` already conveys the intent at the callsite. Inline both calls so the request-URL → pathname conversion lives at the points of use, removing one layer of indirection without changing behavior. All 52 tests in `src/node/orpc/server.test.ts` continue to pass. ### Remove dead `AdvisorToolResultSchema` definitions `AdvisorToolResultSchema` and its three constituent schemas (`AdvisorToolAdviceResultSchema`, `AdvisorToolLimitResultSchema`, `AdvisorToolErrorResultSchema`) in `src/common/utils/tools/toolDefinitions.ts` were introduced alongside the experimental advisor tool in #3157 but were never imported anywhere — neither by `src/common/types/tools.ts` (which derives the public advisor result shape from a different type local to `AdvisorToolCall.tsx`), nor by the advisor tool implementation itself, nor by any test. Unlike the analogous `TaskToolResultSchema` / `TaskAwaitToolResultSchema` / `TaskApplyGitPatchToolResultSchema` / `TaskTerminateToolResultSchema` (all of which are imported via `z.infer` in `src/common/types/tools.ts`), the advisor variant had no consumer. Drop the four dead schemas; the file shrinks by ~32 lines and keeps `AdvisorToolInputSchema` (which is imported by `advisor.ts`) intact. Behavior-preserving. ### Reuse `getProviderPolicy()` in custom-provider `getConfig()` loop `ProviderService.getConfig()`'s custom-provider branch inlined the same "if enforced, look up `providerAccess` entry → narrow to `{ forcedBaseUrl, allowedModels }`" lookup that the existing private `getProviderPolicy()` helper already implements (and that other callsites such as `addCustomOpenAICompatibleProvider` use). Replace the inline lookup with a call to `getProviderPolicy(providerId)` so the small policy-shape projection lives in one place. Behavior-preserving: the only structural difference is that, when policy is not enforced, `getProviderPolicy()` returns `{}` while the inline form passed `{ forcedBaseUrl: undefined, allowedModels: null }`, but `buildCustomProviderConfigInfo` normalizes both via `policy?.forcedBaseUrl ?? resolveConfigBaseUrl(...)` and `policy?.allowedModels ?? null`, so the resulting `ProviderConfigInfo` is byte-identical. All 74 tests in `providerService.test.ts` continue to pass. ### Collapse task-group parent rail offset into shared helper After #3199 introduced `getTaskGroupMemberDepth` and set `TASK_GROUP_MEMBER_PARENT_RAIL_OFFSET_PX = SIDEBAR_LEADING_SLOT_CENTER_OFFSET_PX`, the `task-group-member` branch of `getSubAgentParentRailX` in `src/browser/components/sidebarItemLayout.ts` reduced to `getSidebarLeadingSlotCenterX(depth)`. Replace the inline `getSidebarItemPaddingLeft(depth) + TASK_GROUP_MEMBER_PARENT_RAIL_OFFSET_PX` arithmetic with a call to the existing helper and drop the now-redundant constant, leaving the leading-slot center offset defined exactly once. Behavior-preserving: `getSubAgentParentRailX` still returns `38` at `memberDepth = 2.5`, matching the pinned values in `sidebarItemLayout.test.ts` (and the equivalent `getSubAgentChildStatusCenterX` result). All 40 tests in `sidebarItemLayout.test.ts`, `AgentListItem.test.tsx`, and `ProjectSidebar.test.tsx` continue to pass. ### Remove unnecessary exports from inline-skill utilities Un-export four interfaces in the new inline-skill helper files added in #3204 — `InlineSkillSuggestionContext` and `InlineSkillSuggestionRefreshContext` in `src/browser/utils/agentSkills/inlineSkillSuggestions.ts`, plus `InlineSkillCursorMatch` and `InlineSkillResolveOptions` in `src/browser/utils/agentSkills/inlineSkillReferences.ts`. All four are only used as parameter types within their defining files: the test files import the value functions and pass object-literal arguments, and the consumer call-sites in `ChatInput/index.tsx` only import the exported functions, never the parameter type names. So the `export` keyword was unnecessary. Behavior-preserving and type-only — TypeScript compile passes for both browser and main configs, and the 49 tests in `inlineSkillSuggestions.test.ts` and `inlineSkillReferences.test.ts` continue to pass. > The earlier "sync thinking-policy doc comments with gpt-5.5 regex" > cleanup was dropped during rebase: #3192 superseded it by retiring > `gpt-5.4` from those comments entirely, so the comment-only diff > became redundant. > The earlier "reuse `hasNonEmptyString` helper for apiKey checks" > cleanup was dropped during rebase: #3202 restructured > `resolveProviderCredentials` to delegate to a new > `resolveApiKeyCandidate` helper (subsuming the inline check) and > already updated `hasAnyConfiguredProvider` to use `hasNonEmptyString` > directly, so the cleanup diff no longer applied cleanly and was no > longer needed. ### Replace stale `system-1` reference in telemetry comment The `ExperimentOverriddenPayload.experimentId` JSDoc in `src/common/telemetry/payload.ts` used `'system-1'` as an example experiment ID, but the System 1 feature was removed wholesale in #3207 and that experiment ID no longer exists. Swap the example for a current entry from `EXPERIMENT_IDS` (`'agent-browser'`) so the JSDoc points readers at a real experiment. Behavior-preserving — comment-only change. ### Extract `isLightThemeMode` helper for Shiki theme detection Three callsites independently encoded `themeMode === "light" || themeMode.endsWith("-light")` to map a theme-mode string (including namespaced variants like `flexoki-light`) to the light Shiki theme: - `highlightDiffChunk.ts` had a private `isLightTheme(theme: ThemeMode)` helper. - `HighlightedCode.tsx` and `MarkdownComponents.tsx` had it inline (the latter with an intermediate `isLight` local). Promote the predicate to `isLightThemeMode` in `src/browser/utils/highlighting/shiki-shared.ts` (next to `SHIKI_DARK_THEME` / `SHIKI_LIGHT_THEME` and `mapToShikiLang`) and route all three callsites through it. The suffix convention now has a single source of truth for the light/dark mapping. Behavior-preserving. ### Remove unnecessary exports from `fileRead` After #3208 removed the file explorer / file viewer flow, the only external consumers of `src/browser/utils/fileRead.ts` are `ImmersiveReviewView` (`buildReadFileScript`, `processFileContents`) and the colocated test (`buildReadFileScript`, `EXIT_CODE_TOO_LARGE`, `processFileContents`). Un-export the helpers that are now only used inside the module itself (`MAX_FILE_SIZE`, `shellEscape`, `base64ToUint8Array`, `detectImageType`, `detectSvg`, `detectBinary`, `parseReadFileOutput`) so the module surface accurately reflects its public API. Behavior-preserving. Auto-cleanup checkpoint: d1c0109 --- _Generated with `mux` • Model: `anthropic:claude-opus-4-7` • Thinking: `xhigh`_ <!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=xhigh --> --------- Co-authored-by: mux-bot[bot] <264182336+mux-bot[bot]@users.noreply.github.com> Co-authored-by: Mux <noreply@coder.com> Co-authored-by: mux-bot <mux-bot@coder.com> Co-authored-by: ammar-agent <ammar+ai@ammar.io>
1 parent d1c0109 commit 6ea6964

25 files changed

Lines changed: 152 additions & 685 deletions

src/browser/components/AuthTokenModal/AuthTokenModal.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { Button } from "@/browser/components/Button/Button";
1212
import { getBrowserBackendBaseUrl } from "@/browser/utils/backendBaseUrl";
1313
import { getErrorMessage } from "@/common/utils/errors";
14+
import { isAbortError } from "@/browser/utils/isAbortError";
1415

1516
interface AuthTokenModalProps {
1617
isOpen: boolean;
@@ -136,8 +137,7 @@ export function AuthTokenModal(props: AuthTokenModalProps) {
136137
return;
137138
}
138139

139-
const isAbortError = error instanceof DOMException && error.name === "AbortError";
140-
if (!isAbortError) {
140+
if (!isAbortError(error)) {
141141
setGithubDeviceFlowEnabled(false);
142142
setGithubOptionsLoading(false);
143143
}
@@ -218,8 +218,7 @@ export function AuthTokenModal(props: AuthTokenModalProps) {
218218
clearStoredAuthToken();
219219
props.onSessionAuthenticated?.();
220220
} catch (error) {
221-
const isAbortError = error instanceof DOMException && error.name === "AbortError";
222-
if (isAbortError) {
221+
if (isAbortError(error)) {
223222
return;
224223
}
225224

src/browser/components/sidebarItemLayout.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const SIDEBAR_DEPTH_INDENT_PX = 8;
55
export const SIDEBAR_LEADING_SLOT_SIZE_PX = 16;
66
const SIDEBAR_LEADING_SLOT_CENTER_OFFSET_PX = SIDEBAR_LEADING_SLOT_SIZE_PX / 2;
77
const TASK_GROUP_MEMBER_DEPTH_OFFSET = 1.5;
8-
const TASK_GROUP_MEMBER_PARENT_RAIL_OFFSET_PX = SIDEBAR_LEADING_SLOT_CENTER_OFFSET_PX;
98
const TASK_GROUP_MEMBER_ANCESTOR_RAIL_OFFSET_PX = 6;
109

1110
export function getSidebarItemPaddingLeft(depth?: number): number {
@@ -28,8 +27,10 @@ export function getSidebarLeadingSlotCenterX(depth: number): number {
2827
export function getSubAgentParentRailX(depth: number, layout: SubAgentConnectorLayout): number {
2928
if (layout === "task-group-member") {
3029
// Group members keep their shared rail in the task-group column instead of
31-
// snapping to the nested workspace slot center.
32-
return getSidebarItemPaddingLeft(depth) + TASK_GROUP_MEMBER_PARENT_RAIL_OFFSET_PX;
30+
// snapping to the nested workspace slot center. Their half-step depth
31+
// offset already places this column under the task-group icon, so the
32+
// rail x reduces to the leading-slot center at the member's own depth.
33+
return getSidebarLeadingSlotCenterX(depth);
3334
}
3435

3536
// Regular sub-agents branch from the parent row's leading status slot center,

src/browser/features/Messages/MarkdownComponents.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Play } from "lucide-react";
44
import { Mermaid } from "./Mermaid";
55
import { useOptionalMessageListContext } from "./MessageListContext";
66
import { highlightCode } from "@/browser/utils/highlighting/highlightWorkerClient";
7-
import { extractShikiLines } from "@/browser/utils/highlighting/shiki-shared";
7+
import { extractShikiLines, isLightThemeMode } from "@/browser/utils/highlighting/shiki-shared";
88
import { useTheme } from "@/browser/contexts/ThemeContext";
99
import { CopyButton } from "@/browser/components/CopyButton/CopyButton";
1010
import { resolveBrowserLocalhostProxyTemplate } from "@/browser/utils/browserLocalhostProxyTemplate";
@@ -163,8 +163,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ code, language, highlightLanguage
163163

164164
const shikiLanguage = highlightLanguage ?? language;
165165
const { theme: themeMode } = useTheme();
166-
const isLight = themeMode === "light" || themeMode.endsWith("-light");
167-
const theme = isLight ? "light" : "dark";
166+
const theme = isLightThemeMode(themeMode) ? "light" : "dark";
168167

169168
// Split code into lines, removing trailing empty line
170169
const plainLines = code

src/browser/features/Tools/Shared/HighlightedCode.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState, useEffect } from "react";
22
import { highlightCode } from "@/browser/utils/highlighting/highlightWorkerClient";
3-
import { extractShikiLines } from "@/browser/utils/highlighting/shiki-shared";
3+
import { extractShikiLines, isLightThemeMode } from "@/browser/utils/highlighting/shiki-shared";
44
import { useTheme } from "@/browser/contexts/ThemeContext";
55
import { cn } from "@/common/lib/utils";
66

@@ -33,7 +33,7 @@ export const HighlightedCode: React.FC<HighlightedCodeProps> = ({
3333
}) => {
3434
const [highlighted, setHighlighted] = useState<HighlightedLines | null>(null);
3535
const { theme: themeMode } = useTheme();
36-
const theme = themeMode === "light" || themeMode.endsWith("-light") ? "light" : "dark";
36+
const theme = isLightThemeMode(themeMode) ? "light" : "dark";
3737

3838
const plainLines = code.split("\n").filter((line, i, arr) => i < arr.length - 1 || line !== "");
3939

src/browser/utils/agentSkills/inlineSkillReferences.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface InlineSkillCandidate {
1414
}
1515

1616
/** Active candidate when the cursor is inside a `$partial` token (used by autocomplete). */
17-
export interface InlineSkillCursorMatch {
17+
interface InlineSkillCursorMatch {
1818
partial: string;
1919
startIndex: number;
2020
endIndex: number;
@@ -335,7 +335,7 @@ export function findInlineSkillReferenceAtCursor(
335335
};
336336
}
337337

338-
export interface InlineSkillResolveOptions {
338+
interface InlineSkillResolveOptions {
339339
candidates: InlineSkillCandidate[];
340340
agentSkillDescriptors: AgentSkillDescriptor[];
341341
api: APIClient | null;

src/browser/utils/agentSkills/inlineSkillSuggestions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { SlashSuggestion } from "@/browser/utils/slashCommands/types";
22
import type { AgentSkillDescriptor } from "@/common/types/agentSkill";
33

4-
export interface InlineSkillSuggestionContext {
4+
interface InlineSkillSuggestionContext {
55
/** The token typed after `$`. Empty string is allowed (just typed `$`). */
66
partial: string;
77
/** Already-loaded descriptors for current discovery target. */
88
descriptors: AgentSkillDescriptor[];
99
}
1010

11-
export interface InlineSkillSuggestionRefreshContext {
11+
interface InlineSkillSuggestionRefreshContext {
1212
inputChanged: boolean;
1313
previousPartial: string | null;
1414
partial: string;

src/browser/utils/fileRead.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
/** Maximum file size for reading into the UI (10MB). */
6-
export const MAX_FILE_SIZE = 10 * 1024 * 1024;
6+
const MAX_FILE_SIZE = 10 * 1024 * 1024;
77

88
/** Exit code for "file too large". */
99
export const EXIT_CODE_TOO_LARGE = 42;
@@ -19,12 +19,12 @@ const IMAGE_MAGIC_BYTES: Array<{ bytes: number[]; mime: string }> = [
1919
];
2020

2121
/** Escapes a path for safe use in shell commands. */
22-
export function shellEscape(s: string): string {
22+
function shellEscape(s: string): string {
2323
return "'" + s.replaceAll("'", "'\"'\"'") + "'";
2424
}
2525

2626
/** Decode a base64 string to bytes. */
27-
export function base64ToUint8Array(base64: string): Uint8Array {
27+
function base64ToUint8Array(base64: string): Uint8Array {
2828
const binaryString = atob(base64);
2929
const bytes = new Uint8Array(binaryString.length);
3030
for (let i = 0; i < binaryString.length; i++) {
@@ -34,7 +34,7 @@ export function base64ToUint8Array(base64: string): Uint8Array {
3434
}
3535

3636
/** Detect image type from magic bytes. */
37-
export function detectImageType(buffer: Uint8Array): string | undefined {
37+
function detectImageType(buffer: Uint8Array): string | undefined {
3838
for (const { bytes, mime } of IMAGE_MAGIC_BYTES) {
3939
if (buffer.length < bytes.length) continue;
4040

@@ -68,7 +68,7 @@ export function detectImageType(buffer: Uint8Array): string | undefined {
6868
}
6969

7070
/** Check if file is an SVG by looking for XML/SVG markers in content. */
71-
export function detectSvg(buffer: Uint8Array): boolean {
71+
function detectSvg(buffer: Uint8Array): boolean {
7272
const sampleSize = Math.min(buffer.length, 1024);
7373
const decoder = new TextDecoder("utf-8", { fatal: true });
7474
try {
@@ -80,7 +80,7 @@ export function detectSvg(buffer: Uint8Array): boolean {
8080
}
8181

8282
/** Check if buffer contains binary content. */
83-
export function detectBinary(buffer: Uint8Array): boolean {
83+
function detectBinary(buffer: Uint8Array): boolean {
8484
const sampleSize = Math.min(buffer.length, 8192);
8585

8686
for (let i = 0; i < sampleSize; i++) {
@@ -106,7 +106,7 @@ base64 < ${file}`;
106106
}
107107

108108
/** Parse the read file script output (size on first line, base64 on remaining lines). */
109-
export function parseReadFileOutput(output: string): { size: number; base64: string } {
109+
function parseReadFileOutput(output: string): { size: number; base64: string } {
110110
const firstNewline = output.indexOf("\n");
111111

112112
if (firstNewline === -1) {

src/browser/utils/highlighting/highlightDiffChunk.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { highlightCode } from "./highlightWorkerClient";
2+
import { isLightThemeMode } from "./shiki-shared";
23
import type { DiffChunk } from "./diffChunking";
34

45
/**
@@ -29,11 +30,6 @@ export interface HighlightedLine {
2930

3031
import type { ThemeMode } from "@/browser/contexts/ThemeContext";
3132

32-
/** Map theme mode to Shiki theme (light/dark only) */
33-
function isLightTheme(theme: ThemeMode): boolean {
34-
return theme === "light" || theme.endsWith("-light");
35-
}
36-
3733
export interface HighlightedChunk {
3834
type: DiffChunk["type"];
3935
lines: HighlightedLine[];
@@ -72,7 +68,7 @@ export async function highlightDiffChunk(
7268
}
7369

7470
const code = chunk.lines.join("\n");
75-
const workerTheme = isLightTheme(themeMode) ? "light" : "dark";
71+
const workerTheme = isLightThemeMode(themeMode) ? "light" : "dark";
7672

7773
try {
7874
// Highlight via worker (cached, off main thread)

src/browser/utils/highlighting/shiki-shared.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@
77
export const SHIKI_DARK_THEME = "min-dark";
88
export const SHIKI_LIGHT_THEME = "min-light";
99

10+
/**
11+
* Whether a theme-mode string maps to the light Shiki theme.
12+
*
13+
* Accepts both base modes (`"light"`/`"dark"`) and namespaced variants
14+
* (e.g. `"flexoki-light"`), keeping the variant suffix convention as the
15+
* single source of truth for the light/dark mapping.
16+
*/
17+
export function isLightThemeMode(themeMode: string): boolean {
18+
return themeMode === "light" || themeMode.endsWith("-light");
19+
}
20+
1021
/**
1122
* Map language names to Shiki-compatible language IDs
1223
* Handles special cases where detected language differs from Shiki's name

src/browser/utils/messages/messageUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function getEditableUserMessageText(
3232
/**
3333
* Type guard to check if a message is a bash_output tool call with valid args
3434
*/
35-
export function isBashOutputTool(
35+
function isBashOutputTool(
3636
msg: DisplayedMessage
3737
): msg is DisplayedMessage & { type: "tool"; toolName: "bash_output"; args: BashOutputToolArgs } {
3838
if (msg.type !== "tool" || msg.toolName !== "bash_output") {

0 commit comments

Comments
 (0)