Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c90daa1
πŸ€– refactor: extract IMAGE_TOOL_EXCESS_COUNT_SETUP_HINT constant
mux-bot[bot] May 15, 2026
266c6c4
πŸ€– refactor: hoist mermaid sanitizer URL tables to module scope
ammar-agent May 16, 2026
4076c37
refactor: extract tool-part phase lookup to module-level constant
ammar-agent May 16, 2026
e28b671
refactor: extract createSlashCommandExperimentResolver helper
mux-bot[bot] May 17, 2026
02d80c0
refactor: dedupe Runtime type import in cli/run.ts
mux-bot[bot] May 17, 2026
609a559
refactor: dedupe built-in skill name list in agentSkills test
ammar-agent May 18, 2026
05a939a
refactor: extract AdvisorSwitch component in TasksSection
mux-bot[bot] May 18, 2026
350f919
refactor: share TEXT_OUTPUT_DELTA_FIELDS constant
mux-bot[bot] May 18, 2026
f3e8216
refactor: extract StatTileHeader from goal-tab budget/turns tiles
May 18, 2026
07a066e
refactor: hoist GoalInterventionPolicy to common/orpc/types
mux-bot[bot] May 19, 2026
bc190cd
refactor: convert synthesizeSilentContinuationSummary to free function
ammar-agent May 19, 2026
8403a11
refactor: use loop index for review_pane dedup seed map
mux-bot[bot] May 19, 2026
f868624
refactor: drop unused __resetAdditionalSystemContextFocusForTests helper
mux-bot[bot] May 20, 2026
7fb8b0b
refactor: extract CLI_DISABLED_TOOL_NAMES to dedupe mux run toolPolicy
mux-bot[bot] May 20, 2026
d22bcf7
refactor: extract section-pill className helper in AskUserQuestionToo…
May 20, 2026
3de402a
refactor: extract restoreEnvVar helper in signingService.test.ts
ammar-agent May 21, 2026
1ef426d
refactor: relocate test-only buildReviewDiffPathFilter wrapper
ammar-agent May 21, 2026
6058110
refactor: use template literals in goal flag parser errors
mux-bot[bot] May 21, 2026
e1db195
refactor: extract neutral lifecycle button className in GoalTab
mux-bot[bot] May 21, 2026
965b4ee
refactor: dedupe UNRESOLVED_DELTA_PUSH_PATTERNS into PROJECT_SYNC_RET…
mux-bot[bot] May 21, 2026
ee41d1c
refactor: extract matchesCommand helper in bashCollapsedSummary
mux-bot[bot] May 22, 2026
f202d4a
refactor: extract filterDismissedAssistedHunks helper
mux-bot[bot] May 22, 2026
d1dde90
refactor: extract persistChatTranscriptFullWidth helper
May 22, 2026
e478c8a
refactor: dedupe plain-text HighlightedChunk construction
mux-bot[bot] May 23, 2026
6fb7a34
refactor: dedupe listener-set lazy-init in additionalSystemContextStore
ammar-agent May 24, 2026
bf00b71
refactor: extract parseOptionalIsoTimestamp helper in recency
mux-bot[bot] May 26, 2026
f02ab23
refactor: hoist OPENROUTER_ROUTING_OPTIONS to module scope
May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/browser/components/CommandPalette/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
matchesKeybind,
} from "@/browser/utils/ui/keybinds";
import { stopKeyboardPropagation } from "@/browser/utils/events";
import { resolveSlashCommandExperimentValue } from "@/browser/utils/slashCommands/experimentVisibility";
import { createSlashCommandExperimentResolver } from "@/browser/utils/slashCommands/experimentVisibility";
import { getSlashCommandSuggestions } from "@/browser/utils/slashCommands/suggestions";
import { CUSTOM_EVENTS, createCustomEvent } from "@/common/constants/events";
import { EXPERIMENT_IDS } from "@/common/constants/experiments";
Expand Down Expand Up @@ -290,10 +290,9 @@ export const CommandPalette: React.FC<CommandPaletteProps> = ({ getSlashContext
const suggestions = getSlashCommandSuggestions(q, {
agentSkills,
variant: ctx.workspaceId ? "workspace" : "creation",
isExperimentEnabled: (experimentId) =>
resolveSlashCommandExperimentValue(experimentId, {
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
isExperimentEnabled: createSlashCommandExperimentResolver({
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
});
const section = "Slash Commands";
const groups: PaletteGroup[] = [
Expand Down
16 changes: 7 additions & 9 deletions src/browser/features/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ import {
getSlashCommandSuggestions,
type SlashSuggestion,
} from "@/browser/utils/slashCommands/suggestions";
import { resolveSlashCommandExperimentValue } from "@/browser/utils/slashCommands/experimentVisibility";
import { createSlashCommandExperimentResolver } from "@/browser/utils/slashCommands/experimentVisibility";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip";
import { AgentModePicker } from "@/browser/components/AgentModePicker/AgentModePicker";
import { ContextUsageIndicatorButton } from "@/browser/components/ContextUsageIndicatorButton/ContextUsageIndicatorButton";
Expand Down Expand Up @@ -1427,10 +1427,9 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
const suggestions = getSlashCommandSuggestions(input, {
agentSkills: agentSkillDescriptors,
variant,
isExperimentEnabled: (experimentId) =>
resolveSlashCommandExperimentValue(experimentId, {
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
isExperimentEnabled: createSlashCommandExperimentResolver({
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
});
setCommandSuggestions((prev) => replaceSuggestions(prev, suggestions));
setShowCommandSuggestions(suggestions.length > 0);
Expand All @@ -1440,10 +1439,9 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
// Show only when suggestions are hidden and the input is exactly "/command " with no args yet.
const commandGhostHint = getCommandGhostHint(input, showCommandSuggestions, {
variant,
isExperimentEnabled: (experimentId) =>
resolveSlashCommandExperimentValue(experimentId, {
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
isExperimentEnabled: createSlashCommandExperimentResolver({
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
});

// Load agent skills for suggestions
Expand Down
5 changes: 4 additions & 1 deletion src/browser/features/ChatInput/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import type { Review } from "@/common/types/review";
import type { EditingMessageState, PendingUserMessage } from "@/browser/utils/chatEditing";
import type { SendMessageOptions } from "@/common/orpc/types";

export type GoalInterventionPolicy = NonNullable<SendMessageOptions["goalInterventionPolicy"]>;
// Re-export so `ChatInput/types` (the existing barrel for ChatInput-local
// types) stays the single import surface for this feature, while the
// canonical declaration lives next to `SendMessageOptions` itself.
export type { GoalInterventionPolicy } from "@/common/orpc/types";
export type QueueDispatchMode = NonNullable<SendMessageOptions["queueDispatchMode"]>;

export interface ChatInputAPI {
Expand Down
12 changes: 7 additions & 5 deletions src/browser/features/Messages/Mermaid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { usePersistedState } from "@/browser/hooks/usePersistedState";
const MIN_HEIGHT = 300;
const MAX_HEIGHT = 1200;

// Sanitizer lookup tables. Hoisted to module scope so they aren't reallocated on
// every sanitizeMermaidSvg() call (which can run per render for large SVGs).
const SANITIZER_URL_ATTRIBUTES = new Set(["href", "xlink:href", "src", "action", "formaction"]);
const SANITIZER_BLOCKED_URL_SCHEMES = ["javascript:", "vbscript:", "data:text/html"];

// Initialize mermaid
mermaid.initialize({
startOnLoad: false,
Expand Down Expand Up @@ -162,9 +167,6 @@ export function sanitizeMermaidSvg(svg: string): string | null {
node.remove();
});

const urlAttributes = new Set(["href", "xlink:href", "src", "action", "formaction"]);
const blockedSchemes = ["javascript:", "vbscript:", "data:text/html"];

const elementsToScan: Element[] = [svgRoot, ...Array.from(svgRoot.querySelectorAll("*"))];
elementsToScan.forEach((element) => {
for (const attribute of Array.from(element.attributes)) {
Expand All @@ -177,8 +179,8 @@ export function sanitizeMermaidSvg(svg: string): string | null {
}

if (
urlAttributes.has(attributeName) &&
blockedSchemes.some((scheme) => canonicalUrlValue.startsWith(scheme))
SANITIZER_URL_ATTRIBUTES.has(attributeName) &&
SANITIZER_BLOCKED_URL_SCHEMES.some((scheme) => canonicalUrlValue.startsWith(scheme))
) {
element.removeAttribute(attribute.name);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
import { describe, expect, test } from "bun:test";

import type { AssistedReviewHunk, DiffHunk } from "@/common/types/review";
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
import {
buildReviewDiffPathFilter,
buildReviewDiffPathFilterSpecs,
countUnreadAssistedHunks,
getEffectiveReviewFrontendFilters,
getEffectiveReviewIncludeUncommitted,
getNextDismissedAssistedKeys,
} from "./ReviewPanel";

// Test-only single-result wrapper around `buildReviewDiffPathFilterSpecs`.
// Production callers go through the multi-spec form directly to handle the
// multi-project case; these tests only exercise the first spec's pathFilter,
// so the wrapper lives here instead of polluting ReviewPanel's exported API.
function buildReviewDiffPathFilter(params: {
isImmersive: boolean;
assistedOnly: boolean;
assistedHunks: readonly AssistedReviewHunk[];
selectedFilePath: string | null;
selectedDiffPath: string;
workspaceMetadata: Pick<FrontendWorkspaceMetadata, "projects"> | null | undefined;
repoRootProjectPath: string | null | undefined;
}): string {
return (
buildReviewDiffPathFilterSpecs({
...params,
projectPath: params.repoRootProjectPath ?? "",
})[0]?.pathFilter ?? ""
);
}

function hunk(overrides: Partial<DiffHunk>): DiffHunk {
return {
id: overrides.id ?? "h1",
Expand Down
47 changes: 13 additions & 34 deletions src/browser/features/RightSidebar/CodeReview/ReviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ import React, {
useRef,
useSyncExternalStore,
} from "react";
import { findAssistedMatch, formatAssistedFilter } from "@/common/utils/review/assistedReview";
import {
filterDismissedAssistedHunks,
findAssistedMatch,
formatAssistedFilter,
} from "@/common/utils/review/assistedReview";
import { createPortal } from "react-dom";
import { HunkViewer } from "./HunkViewer";
import { InlineReviewNote, type ReviewActionCallbacks } from "../../Shared/InlineReviewNote";
Expand Down Expand Up @@ -529,23 +533,6 @@ export function buildReviewDiffPathFilterSpecs(params: {
}));
}

export function buildReviewDiffPathFilter(params: {
isImmersive: boolean;
assistedOnly: boolean;
assistedHunks: readonly AssistedReviewHunk[];
selectedFilePath: string | null;
selectedDiffPath: string;
workspaceMetadata: Pick<FrontendWorkspaceMetadata, "projects"> | null | undefined;
repoRootProjectPath: string | null | undefined;
}): string {
return (
buildReviewDiffPathFilterSpecs({
...params,
projectPath: params.repoRootProjectPath ?? "",
})[0]?.pathFilter ?? ""
);
}

export function getEffectiveReviewIncludeUncommitted(params: {
assistedOnly: boolean;
includeUncommitted: boolean;
Expand Down Expand Up @@ -639,11 +626,10 @@ export const ReviewAssistedStatsReporter: React.FC<ReviewAssistedStatsReporterPr
[],
{ listener: true }
);
const assistedHunks = useMemo(() => {
if (dismissedAssistedKeys.length === 0) return rawAssistedHunks;
const dismissed = new Set(dismissedAssistedKeys);
return rawAssistedHunks.filter((entry) => !dismissed.has(formatAssistedFilter(entry)));
}, [rawAssistedHunks, dismissedAssistedKeys]);
const assistedHunks = useMemo(
() => filterDismissedAssistedHunks(rawAssistedHunks, dismissedAssistedKeys),
[rawAssistedHunks, dismissedAssistedKeys]
);

// Self-heal the dismissed-pin list whenever the agent's set changes:
// drop any dismissed key that is no longer present in the agent's pins
Expand Down Expand Up @@ -1091,19 +1077,12 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
[],
{ listener: true }
);
const dismissedAssistedKeySet = useMemo(
() => new Set(dismissedAssistedKeys),
[dismissedAssistedKeys]
);

// Effective assisted set after applying user dismissals. Memoized so all
// downstream maps depend on a stable reference when nothing changes.
const assistedHunks = useMemo(() => {
if (dismissedAssistedKeySet.size === 0) return rawAssistedHunks;
return rawAssistedHunks.filter(
(entry) => !dismissedAssistedKeySet.has(formatAssistedFilter(entry))
);
}, [rawAssistedHunks, dismissedAssistedKeySet]);
const assistedHunks = useMemo(
() => filterDismissedAssistedHunks(rawAssistedHunks, dismissedAssistedKeys),
[rawAssistedHunks, dismissedAssistedKeys]
);

// The self-healing prune of stale dismissed keys lives in
// `ReviewAssistedStatsReporter` (always mounted) so it runs even when the
Expand Down
87 changes: 58 additions & 29 deletions src/browser/features/RightSidebar/GoalTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ interface GoalTabProps {
const parseBudgetInput = parseGoalBudgetInputCents;
const parseTurnCapInput = parseGoalTurnCapInput;

// Shared className for the neutral lifecycle action buttons in the active-goal
// header (Pause / Mark complete / Reopen). All three render with the same
// bordered surface-secondary chip styling so they read as visual peers; the
// accent-colored Archive and success-tinted Resume buttons keep their own
// inline class strings because they are the only primary / positive variants.
// Centralizing the neutral string here keeps the next tone tweak to those
// three buttons a one-line edit instead of three duplicated copies.
const GOAL_LIFECYCLE_NEUTRAL_BUTTON_CLASS =
"border-border-light bg-surface-secondary text-foreground hover:bg-surface-tertiary inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm";

type EditingField = "objective" | "budget" | "turnCap";

export function GoalTab(props: GoalTabProps) {
Expand Down Expand Up @@ -613,7 +623,7 @@ export function GoalTab(props: GoalTabProps) {
{canPause && (
<button
type="button"
className="border-border-light bg-surface-secondary text-foreground hover:bg-surface-tertiary inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm"
className={GOAL_LIFECYCLE_NEUTRAL_BUTTON_CLASS}
aria-label="Pause goal"
onClick={() => void setStatus("paused")}
>
Expand Down Expand Up @@ -641,7 +651,7 @@ export function GoalTab(props: GoalTabProps) {
{canComplete && (
<button
type="button"
className="border-border-light bg-surface-secondary text-foreground hover:bg-surface-tertiary inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm"
className={GOAL_LIFECYCLE_NEUTRAL_BUTTON_CLASS}
aria-label="Mark goal complete"
onClick={(event) => openSummaryInput(event.currentTarget)}
>
Expand All @@ -660,7 +670,7 @@ export function GoalTab(props: GoalTabProps) {
<>
<button
type="button"
className="border-border-light bg-surface-secondary text-foreground hover:bg-surface-tertiary inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm"
className={GOAL_LIFECYCLE_NEUTRAL_BUTTON_CLASS}
aria-label="Reopen goal"
onClick={() => void setStatus("active")}
>
Expand Down Expand Up @@ -792,6 +802,39 @@ export function GoalTab(props: GoalTabProps) {
);
}

interface StatTileHeaderProps {
/** Tile label rendered as a `<dt>` (the parent `<dl>` lives in the active-goal section). */
label: string;
canEdit: boolean;
/** Aria label for the inline "Edit" button β€” distinguishes the budget vs turn-cap openers in tests + a11y. */
editAriaLabel: string;
onEdit: (event: React.MouseEvent<HTMLButtonElement>) => void;
}

/**
* Shared header row for the active-goal stat tiles (Budget, Turns):
* a `<dt>` label on the left and an optional inline "Edit" button on
* the right. Kept scoped to this file because the styling is intentionally
* local to the dashboard tile look and not reused elsewhere.
*/
function StatTileHeader(props: StatTileHeaderProps) {
return (
<div className="flex items-baseline justify-between gap-2">
<dt className="text-muted text-xs">{props.label}</dt>
{props.canEdit && (
<button
type="button"
className="text-muted hover:text-foreground text-xs underline"
aria-label={props.editAriaLabel}
onClick={props.onEdit}
>
Edit
</button>
)}
</div>
);
}

interface BudgetTileProps {
costCents: number;
budgetCents: number | null;
Expand Down Expand Up @@ -853,19 +896,12 @@ function BudgetTile(props: BudgetTileProps) {

return (
<div className="bg-surface-secondary col-span-2 rounded-md p-3">
<div className="flex items-baseline justify-between gap-2">
<dt className="text-muted text-xs">Budget</dt>
{canEdit && (
<button
type="button"
className="text-muted hover:text-foreground text-xs underline"
aria-label="Edit goal budget"
onClick={onEdit}
>
Edit
</button>
)}
</div>
<StatTileHeader
label="Budget"
canEdit={canEdit}
editAriaLabel="Edit goal budget"
onEdit={onEdit}
/>
<dd className="counter-nums text-foreground mt-1 flex items-baseline justify-between gap-3 leading-tight">
<span className="text-base font-medium">
<span>{formatGoalCents(costCents)}</span>
Expand Down Expand Up @@ -939,19 +975,12 @@ function TurnsTile(props: TurnsTileProps) {

return (
<div className="bg-surface-secondary rounded-md p-3">
<div className="flex items-baseline justify-between gap-2">
<dt className="text-muted text-xs">Turns</dt>
{canEdit && (
<button
type="button"
className="text-muted hover:text-foreground text-xs underline"
aria-label="Edit goal turn cap"
onClick={onEdit}
>
Edit
</button>
)}
</div>
<StatTileHeader
label="Turns"
canEdit={canEdit}
editAriaLabel="Edit goal turn cap"
onEdit={onEdit}
/>
<dd className="counter-nums text-foreground mt-1 leading-tight">
<div className="text-base font-medium">
<span>{hasCap ? `${turnsUsed} / ${turnCap}` : String(turnsUsed)}</span>
Expand Down
14 changes: 4 additions & 10 deletions src/browser/features/Settings/Sections/GeneralSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
} from "@/browser/components/SelectPrimitive/SelectPrimitive";
import { Input } from "@/browser/components/Input/Input";
import { Switch } from "@/browser/components/Switch/Switch";
import { updatePersistedState, usePersistedState } from "@/browser/hooks/usePersistedState";
import { usePersistedState } from "@/browser/hooks/usePersistedState";
import { persistChatTranscriptFullWidth } from "@/browser/hooks/useChatTranscriptFullWidth";
import { useAPI } from "@/browser/contexts/API";
import { CUSTOM_EVENTS, createCustomEvent } from "@/common/constants/events";
import {
Expand All @@ -20,7 +21,6 @@ import {
LAUNCH_BEHAVIOR_KEY,
BASH_COLLAPSED_SUMMARY_MODE_KEY,
BASH_COLLAPSED_SUMMARY_MODES,
CHAT_TRANSCRIPT_FULL_WIDTH_KEY,
DEFAULT_BASH_COLLAPSED_SUMMARY_MODE,
normalizeBashCollapsedSummaryMode,
type BashCollapsedSummaryMode,
Expand Down Expand Up @@ -283,10 +283,7 @@ export function GeneralSection() {
if (chatTranscriptFullWidthNonce === chatTranscriptFullWidthLoadNonceRef.current) {
const enabled = cfg.chatTranscriptFullWidth === true;
setChatTranscriptFullWidth(enabled);
updatePersistedState<boolean | undefined>(
CHAT_TRANSCRIPT_FULL_WIDTH_KEY,
enabled ? true : undefined
);
persistChatTranscriptFullWidth(enabled);
}

if (llmDebugLogsNonce === llmDebugLogsLoadNonceRef.current) {
Expand Down Expand Up @@ -379,10 +376,7 @@ export function GeneralSection() {
// Invalidate any in-flight config load so it does not overwrite the user's selection.
chatTranscriptFullWidthLoadNonceRef.current++;
setChatTranscriptFullWidth(checked);
updatePersistedState<boolean | undefined>(
CHAT_TRANSCRIPT_FULL_WIDTH_KEY,
checked ? true : undefined
);
persistChatTranscriptFullWidth(checked);

if (!api?.config?.updateChatTranscriptFullWidth) {
return;
Expand Down
Loading