Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
a409242
πŸ€– refactor: dedupe memory sweep recordUsage callbacks
mux-bot[bot] Jul 8, 2026
14776de
πŸ€– refactor: dedupe memory scope-full cap check into helper
mux-bot[bot] Jul 9, 2026
152560d
refactor: dedupe blockquote line formatting in bash monitor wake prompt
mux-bot[bot] Jul 9, 2026
a300ba5
refactor: dedupe tool_search removal in prepareToolSearch
mux-bot[bot] Jul 9, 2026
4210aae
refactor: dedupe anthropic cache-create token extraction in usageHelpers
mux-bot[bot] Jul 10, 2026
c2c890e
refactor: dedupe capability-model thinking policy resolution
mux-bot[bot] Jul 10, 2026
2e94627
refactor: dedupe queue entry clear-callback projection in MessageQueue
mux-bot[bot] Jul 11, 2026
0cdab94
refactor: dedupe OpenAI-origin model check in cacheStrategy
mux-bot[bot] Jul 12, 2026
e82180a
refactor: dedupe tool-call-execution-start emit in StreamManager
mux-bot[bot] Jul 13, 2026
6625b86
refactor: unify legacy tool_search part rename helper in toolCatalog
mux-bot[bot] Jul 14, 2026
03435e9
refactor: drop duplicated context-cap rationale comment in codexOAuth
mux-bot[bot] Jul 14, 2026
3f722c7
refactor: dedupe flat-section pinned block resolution in pinnedReorder
mux-bot[bot] Jul 15, 2026
ef65608
refactor: dedupe JSON-wrapped tool-output unwrap in workflowRunMessages
mux-bot[bot] Jul 15, 2026
f1950e3
refactor: hoist errorType local in finalizeWorkspaceTurnFromStreamError
mux-bot[bot] Jul 15, 2026
0538fee
refactor: extract buildSkillDescriptor helper for skill discovery
mux-bot[bot] Jul 15, 2026
3620c76
refactor: extract awaitPendingLoad helper in DevToolsService
mux-bot[bot] Jul 16, 2026
8731952
refactor: extract getTotalTokens helper for total-token sums
mux-bot[bot] Jul 20, 2026
7b7d08f
refactor: hoist duplicated dedupeKeys snapshot in removeByDedupeKeyPr…
mux-bot[bot] Jul 20, 2026
cfc8606
refactor: dedupe settled workspace-turn reconciliation guard
mux-bot[bot] Jul 21, 2026
a302268
refactor: drop duplicated Kimi K3 max-effort rationale in providerOpt…
mux-bot[bot] Jul 22, 2026
8012da7
refactor: drop redundant structuredOutput guard at subagent report ca…
mux-bot[bot] Jul 22, 2026
68191d8
refactor: extract isZipMediaType helper for staged attachment media-t…
mux-bot[bot] Jul 23, 2026
4c84f77
refactor: hoist duplicated goal-bypass attachment check in ChatInput
mux-bot[bot] Jul 24, 2026
943b114
refactor: dedupe anchored Anthropic model-id regex construction
mux-bot[bot] Jul 24, 2026
fd77fb6
refactor: extract _child_dirs helper for job folder discovery
mux-bot[bot] Jul 25, 2026
60f5934
refactor: drop redundant "exec" fallback duplication for normalizeAge…
mux-bot[bot] Jul 27, 2026
4fcea3d
refactor: dedupe defensive unknown-field reads in the timeline mapper
mux-bot[bot] Jul 29, 2026
aa1e491
refactor: share the mobile-touch media query constant
mux-bot[bot] Jul 29, 2026
877146d
refactor: share the docked toast overlay placement class
mux-bot[bot] Jul 29, 2026
6e18079
refactor: share the primary mouse button guard
mux-bot[bot] Jul 29, 2026
33e1597
refactor: name ModelSelector row selection/highlight state
mux-bot[bot] Jul 30, 2026
b401c6c
refactor: share the workspace footer pill class
mux-bot[bot] Jul 30, 2026
542a6d9
refactor: share the safe inactive-animation pause install
mux-bot[bot] Aug 1, 2026
d7ca328
refactor: share the bash monitor wake message predicate
mux-bot[bot] Aug 1, 2026
93456ff
refactor: extract parseSubagentReportFromMessage helper
mux-bot[bot] Aug 2, 2026
28dddd8
refactor: drop stale userOverridable references from experiments UI
mux-bot[bot] Aug 3, 2026
ddb3335
refactor: reuse isTaskAwaitMessage predicate in transcript projection
mux-bot[bot] Aug 3, 2026
6ba8b63
refactor: dedupe non-blank string coercion in taskReportLinking
mux-bot[bot] Aug 4, 2026
927f4be
refactor: drop redundant agentType re-check in task_await rows
mux-bot[bot] Aug 5, 2026
22b930f
refactor: hoist duplicated isGrok45Model check in xAI model creation
mux-bot[bot] Aug 6, 2026
9bd10d3
refactor: share the bash-monitor-wake metadata type guard
mux-bot[bot] Aug 6, 2026
b372b94
refactor: share the persisted tool-error message extraction
mux-bot[bot] Aug 7, 2026
2ee985c
refactor: share the terminal-attention message text join
mux-bot[bot] Aug 7, 2026
20d9838
refactor: share the agent-plugin error diagnostic push
mux-bot[bot] Aug 7, 2026
97fe8d4
refactor: extract projectSelectOptions helper for creation project pi…
mux-bot[bot] Aug 8, 2026
fc2c1bb
refactor: extract shared raw-tool wrapper for MCP startup paths
mux-bot[bot] Aug 9, 2026
82cf79a
refactor: extract createExecError for the three exec failure paths
mux-bot[bot] Aug 10, 2026
b3104dd
refactor: dedupe popover error anchor math into resolvePopoverErrorAn…
mux-bot[bot] Aug 12, 2026
4600083
refactor: dedupe the notification settings panel in WorkspaceMenuBar
mux-bot[bot] Aug 12, 2026
e2ba405
refactor: dedupe persisted-choice normalization in AnalyticsDashboard
mux-bot[bot] Aug 12, 2026
1b12ea4
refactor: extract mergeFetchHeaders for provider fetch wrappers
mux-bot[bot] Aug 12, 2026
1938694
refactor: dedupe unknown-to-record narrowing in PRStatusStore
mux-bot[bot] Aug 12, 2026
d50223a
refactor: dedupe native-xhigh label branches in getThinkingDisplayLabel
mux-bot[bot] Aug 12, 2026
c86507e
refactor: extract rebuild_native_module helper in postinstall.sh
mux-bot[bot] Aug 13, 2026
40f3441
refactor: dedupe Gemini Flash thinking-level tier prefixes
mux-bot[bot] Aug 13, 2026
1b1247f
refactor: dedupe the task-group running-work predicate
mux-bot[bot] Aug 14, 2026
0489337
refactor: dedupe plural-suffix logic in sidebar activity labels
mux-bot[bot] Aug 14, 2026
bcfcc57
refactor: extract splitCoderGatewayModelId helper
mux-bot[bot] Aug 15, 2026
ecfb1e1
refactor: dedupe timeline rule-vs-event row dispatch
mux-bot[bot] Aug 16, 2026
a88d7fe
refactor: extract buildCommandReplacement for slash suggestion traili…
mux-bot[bot] Aug 16, 2026
64fa271
refactor: dedupe the pending patch-artifact seed in generate()
Aug 16, 2026
4fb9d49
refactor: dedupe blob content-address computation in BlobStore
mux-bot[bot] Aug 17, 2026
e0817aa
refactor: dedupe sub-agent status presentations into one table
mux-bot[bot] Aug 19, 2026
f04cb1d
refactor: dedupe the replay/lookup/watermark prelude in SessionTiming…
mux-bot[bot] Aug 19, 2026
85b34a7
refactor: dedupe analytics date-filter epoch round-trip
mux-bot[bot] Aug 20, 2026
847e18d
refactor: dedupe budget-exceeded reporting in the run CLI
mux-bot[bot] Aug 20, 2026
8d802e7
refactor: dedupe the oversized pass-through gate in transformMCPResult
mux-bot[bot] Aug 21, 2026
33feab5
refactor: use centralized isErrnoWithCode in agentSession
mux-bot[bot] Aug 21, 2026
6a96d31
refactor: dedupe canonical/legacy plan path resolution
mux-bot[bot] Aug 21, 2026
cce0241
refactor: name the instruction-file read flags
mux-bot[bot] Aug 22, 2026
69969ce
refactor: extract configContentSignature helper for corrupt-config ba…
mux-bot[bot] Aug 22, 2026
a342741
refactor: extract rollBackInitializedGitDir helper for git-init rollback
mux-bot[bot] Aug 22, 2026
5c9bde9
refactor: dedupe the magnitude-shift bound check in update-models val…
mux-bot[bot] Aug 23, 2026
2303374
refactor: dedupe app-bundle discovery in mac attach-file smoke check
mux-bot[bot] Aug 23, 2026
dbab3c7
refactor: dedupe terminal badge config patching in GeneralSection
mux-bot[bot] Aug 24, 2026
b23485e
refactor: drop vestigial handleJumpToBottom alias in ChatPane
mux-bot[bot] Aug 24, 2026
7f9927a
refactor: share the narrow-viewport media query constant
mux-bot[bot] Aug 25, 2026
7430270
refactor: share the agent-envelope lookalike pre-check
mux-bot[bot] Aug 25, 2026
f69eec6
refactor: dedupe the git-query boilerplate in taskUtils
mux-bot[bot] Aug 25, 2026
8678e1c
refactor: share one newest-first history tail in startup auto-retry
mux-bot[bot] Aug 26, 2026
5829935
refactor: share the stop-veto restore in persistGoalMutationLocked
mux-bot[bot] Aug 26, 2026
88aef9c
refactor: route the new project-removal preflight anchor through reso…
mux-bot[bot] Aug 26, 2026
a6fcd14
refactor: share the diff size bound across combineDiffs exits
mux-bot[bot] Aug 26, 2026
3023196
refactor: extract appendGitConfigOverride for numbered git config ove…
mux-bot[bot] Aug 27, 2026
848f943
refactor: use isEnoentError helper for remaining inline ENOENT check
mux-bot[bot] Aug 28, 2026
971a6e6
refactor: dedupe the queue-cut takeover-stream check
mux-bot[bot] Aug 28, 2026
c6858f5
refactor: dedupe the abort-forwarding wiring in execFileIO
mux-bot[bot] Aug 29, 2026
5b84376
refactor: drop the orphaned BridgeableToolName type
mux-bot[bot] Aug 29, 2026
0712255
refactor: route quickjsRuntime marker tests through isKernelBoundedMa…
mux-bot[bot] Aug 29, 2026
94841ea
refactor: extract reuseCachedMethodHandle for registerObject handle c…
mux-bot[bot] Aug 30, 2026
8d4aa23
refactor: dedupe failed project-result construction in applyProjectPatch
mux-bot[bot] Aug 31, 2026
d4d962a
refactor: extract prepareLockDir for the shared lock-install prelude
mux-bot[bot] Aug 31, 2026
83fc6fc
refactor: extract workflowRunStoreFor for per-workspace run stores
mux-bot[bot] Aug 31, 2026
79a0208
refactor: extract throwIfAborted for the repeated SSH abort guard
mux-bot[bot] Sep 1, 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
17 changes: 8 additions & 9 deletions benchmarks/terminal_bench/prepare_leaderboard_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ def get_model_from_config(config_path: Path) -> str | None:
return None


def _child_dirs(path: Path) -> list[Path]:
"""List the immediate subdirectories of a directory, skipping plain files."""
return [child for child in path.iterdir() if child.is_dir()]


def _is_job_folder(path: Path) -> bool:
"""Check if a directory looks like a job folder (contains trial dirs with config.json)."""
if not path.is_dir():
Expand Down Expand Up @@ -249,20 +254,14 @@ def find_job_folders(artifacts_dir: Path) -> list[Path]:
# Check for direct jobs/ folder
direct_jobs = artifacts_dir / "jobs"
if direct_jobs.exists():
for item in direct_jobs.iterdir():
if item.is_dir():
job_folders.append(item)
job_folders.extend(_child_dirs(direct_jobs))
return job_folders

# Check for per-artifact structure
for artifact_dir in artifacts_dir.iterdir():
if not artifact_dir.is_dir():
continue
for artifact_dir in _child_dirs(artifacts_dir):
jobs_dir = artifact_dir / "jobs"
if jobs_dir.exists():
for item in jobs_dir.iterdir():
if item.is_dir():
job_folders.append(item)
job_folders.extend(_child_dirs(jobs_dir))

return job_folders

Expand Down
16 changes: 5 additions & 11 deletions scripts/checkMacAttachFileRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,9 @@ async function findAppBundles(rootDir: string): Promise<{ matches: string[]; see
return { matches, seen };
}

async function chooseDefaultAppBundle(): Promise<string> {
const { matches: appBundles, seen } = await findAppBundles(RELEASE_DIR);
assert(
appBundles.length > 0,
`No ${APP_NAME} found under ${RELEASE_DIR}. Run make dist-mac first. Stored .app names: ${
seen.length > 0 ? seen.join(", ") : "(none)"
}`
);

// Takes the already-discovered bundles so callers do not re-walk the release
// tree (and re-run the identical "no bundle found" assert) just to pick one.
function chooseDefaultAppBundle(appBundles: readonly string[]): string {
const preferredSuffixes =
process.arch === "arm64"
? [
Expand All @@ -94,7 +88,7 @@ async function chooseDefaultAppBundle(): Promise<string> {
}
}

return appBundles.sort()[0]!;
return [...appBundles].sort()[0]!;
}

async function findFileMatching(rootDir: string, pattern: RegExp): Promise<string | null> {
Expand Down Expand Up @@ -304,7 +298,7 @@ async function main(): Promise<void> {
}`
);
appBundles = matches;
smokeAppBundle = await chooseDefaultAppBundle();
smokeAppBundle = chooseDefaultAppBundle(matches);
}

const verifiedArchitectures = new Set<MacAppArchitecture>();
Expand Down
55 changes: 27 additions & 28 deletions scripts/postinstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,40 +81,39 @@ else
exit 0
fi

# 6) Rebuild node-pty (once per version/platform)
if [ "$HAS_NODE_PTY" = "1" ]; then
if [ -f "$NODE_PTY_STAMP_FILE" ]; then
echo "βœ… node-pty already rebuilt for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH} – skipping"
else
echo "πŸ”§ Rebuilding node-pty for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH}..."
$REBUILD_CMD @electron/rebuild -f -m node_modules/node-pty || {
echo "⚠️ Failed to rebuild native modules"
echo " Terminal functionality may not work in desktop mode."
echo " Run 'make rebuild-native' manually to fix."
exit 0
}
touch "$NODE_PTY_STAMP_FILE"
echo "βœ… node-pty rebuilt successfully (cached at $NODE_PTY_STAMP_FILE)"
# 6) Rebuild one native module for Electron's ABI, skipping when its stamp exists.
# A rebuild failure is non-fatal (desktop terminal/DB features degrade, install succeeds),
# so the failure path exits the whole script with 0 rather than returning to the caller.
rebuild_native_module() {
label="$1"
module_path="$2"
stamp_file="$3"

if [ -f "$stamp_file" ]; then
echo "βœ… ${label} already rebuilt for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH} – skipping"
return 0
fi

echo "πŸ”§ Rebuilding ${label} for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH}..."
$REBUILD_CMD @electron/rebuild -f -m "$module_path" || {
echo "⚠️ Failed to rebuild native modules"
echo " Terminal functionality may not work in desktop mode."
echo " Run 'make rebuild-native' manually to fix."
exit 0
}
touch "$stamp_file"
echo "βœ… ${label} rebuilt successfully (cached at $stamp_file)"
}

# 7) Rebuild native modules (once per version/platform)
if [ "$HAS_NODE_PTY" = "1" ]; then
rebuild_native_module "node-pty" "node_modules/node-pty" "$NODE_PTY_STAMP_FILE"
else
echo "ℹ️ node-pty package missing – skipping node-pty rebuild"
fi

# 7) Rebuild DuckDB (once per version/platform)
if [ "$HAS_DUCKDB" = "1" ]; then
if [ -f "$DUCKDB_STAMP_FILE" ]; then
echo "βœ… DuckDB already rebuilt for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH} – skipping"
else
echo "πŸ”§ Rebuilding DuckDB for Electron ${ELECTRON_VERSION} on ${PLATFORM}/${ARCH}..."
$REBUILD_CMD @electron/rebuild -f -m node_modules/@duckdb/node-bindings || {
echo "⚠️ Failed to rebuild native modules"
echo " Terminal functionality may not work in desktop mode."
echo " Run 'make rebuild-native' manually to fix."
exit 0
}
touch "$DUCKDB_STAMP_FILE"
echo "βœ… DuckDB rebuilt successfully (cached at $DUCKDB_STAMP_FILE)"
fi
rebuild_native_module "DuckDB" "node_modules/@duckdb/node-bindings" "$DUCKDB_STAMP_FILE"
else
echo "ℹ️ DuckDB packages missing – skipping DuckDB rebuild"
fi
4 changes: 2 additions & 2 deletions src/browser/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
LEFT_SIDEBAR_DEFAULT_WIDTH_PX,
LEFT_SIDEBAR_MAX_WIDTH_PX,
LEFT_SIDEBAR_MIN_WIDTH_PX,
MOBILE_TOUCH_MEDIA_QUERY,
} from "@/constants/layout";
import { XUM_PRODUCT_SLUG } from "@/common/constants/product";
import { buildCoreSources, type BuildSourcesParams } from "./utils/commands/sources";
Expand Down Expand Up @@ -233,8 +234,7 @@ function AppInner() {
// because the sidebar width is controlled by CSS and shouldn't rewrite the user's desktop
// width preference.
const isMobileTouch =
typeof window !== "undefined" &&
window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches;
typeof window !== "undefined" && window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY).matches;
if (isMobileTouch) {
return Number.POSITIVE_INFINITY;
}
Expand Down
10 changes: 7 additions & 3 deletions src/browser/components/AgentListItem/AgentListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,13 @@ function HeartbeatFallbackIcon() {
);
}

/** Plural "s" for the sidebar activity counts, which are all regular nouns. */
function pluralSuffix(count: number): string {
return count === 1 ? "" : "s";
}

function formatSubAgentCount(count: number, label: "active" | "queued"): string {
return `${count} sub-agent${count === 1 ? "" : "s"} ${label}`;
return `${count} sub-agent${pluralSuffix(count)} ${label}`;
}

function formatDelegatedActivityText(activity: WorkspaceDelegatedActivity): string | null {
Expand Down Expand Up @@ -295,8 +300,7 @@ function formatHiddenSubAgentsPresentation(
? summary.runningWorkflowAgentCount
: summary.queuedWorkflowAgentCount;
// Gap-only runs have no countable workers; skip the "(0 agents)" noise.
const agentSuffix =
agentCount > 0 ? ` (${agentCount} agent${agentCount === 1 ? "" : "s"})` : "";
const agentSuffix = agentCount > 0 ? ` (${agentCount} agent${pluralSuffix(agentCount)})` : "";
const queuedSuffix =
hasRunningRun && summary.queuedWorkflowAgentCount > 0
? ` Β· ${summary.queuedWorkflowAgentCount} queued`
Expand Down
19 changes: 7 additions & 12 deletions src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getErrorMessage } from "@/common/utils/errors";
import { useAPI } from "@/browser/contexts/API";
import { useWorkspaceContext } from "@/browser/contexts/WorkspaceContext";
import { usePersistedState } from "@/browser/hooks/usePersistedState";
import { usePopoverError } from "@/browser/hooks/usePopoverError";
import { resolvePopoverErrorAnchor, usePopoverError } from "@/browser/hooks/usePopoverError";
import { ChevronDown, ChevronRight, FolderX, Loader2, Search, Trash2 } from "lucide-react";
import { ArchiveIcon, ArchiveRestoreIcon } from "../icons/ArchiveIcon/ArchiveIcon";
import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip";
Expand Down Expand Up @@ -584,15 +584,11 @@ export const ArchivedWorkspaces: React.FC<ArchivedWorkspacesProps> = ({
return;
}

if (anchorEl) {
const rect = anchorEl.getBoundingClientRect();
unarchiveError.showError(workspaceId, result.error ?? "Failed to restore workspace", {
top: rect.top + window.scrollY,
left: rect.right + 10,
});
} else {
unarchiveError.showError(workspaceId, result.error ?? "Failed to restore workspace");
}
unarchiveError.showError(
workspaceId,
result.error ?? "Failed to restore workspace",
resolvePopoverErrorAnchor(anchorEl)
);
} finally {
setProcessingIds((prev) => {
const next = new Set(prev);
Expand Down Expand Up @@ -675,8 +671,7 @@ export const ArchivedWorkspaces: React.FC<ArchivedWorkspacesProps> = ({
};

const handleDeleteWorktree = async (workspaceId: string, anchorEl?: HTMLElement) => {
const rect = anchorEl?.getBoundingClientRect();
const anchor = rect ? { top: rect.top + window.scrollY, left: rect.right + 10 } : undefined;
const anchor = resolvePopoverErrorAnchor(anchorEl);

if (!api) {
deleteWorktreeError.showError(workspaceId, "Not connected to server", anchor);
Expand Down
29 changes: 14 additions & 15 deletions src/browser/components/ChatPane/ChatPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
mergeConsecutiveStreamErrors,
computeBashOutputGroupInfos,
shouldBypassDeferredMessages,
isBashMonitorWakeMessage,
} from "@/browser/utils/messages/messageUtils";
import { computeTaskReportLinking } from "@/browser/utils/messages/taskReportLinking";
import { BashCollapsedSummaryModeProvider } from "@/browser/features/Tools/BashCollapsedSummaryModeContext";
Expand Down Expand Up @@ -739,8 +740,6 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
[handleScrollContainerKeyDown, isComposerDockEvent]
);

const handleJumpToBottom = jumpToBottom;

// Handler to navigate (scroll) to a specific message by historyId
const handleNavigateToMessage = useCallback(
(historyId: string) => {
Expand All @@ -767,7 +766,7 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
// interrupt navigation between human prompts (payloads themselves are assistant rows).
if (
message.type === "user" &&
message.bashMonitorWake == null &&
!isBashMonitorWakeMessage(message) &&
message.agentPeerMessageTrigger == null
) {
userHistoryIds.push(message.historyId);
Expand Down Expand Up @@ -1012,8 +1011,8 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
// send success can be too late because the backend may not resolve until the
// stream has already produced rows, leaving the first deltas offscreen when the
// user had previously scrolled up.
handleJumpToBottom();
}, [handleJumpToBottom]);
jumpToBottom();
}, [jumpToBottom]);

const handleMessageSent = useCallback(
(dispatchMode: QueueDispatchMode = "tool-end") => {
Expand All @@ -1026,15 +1025,15 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {

// Slash-command send paths still report after backend success; keep this
// harmless duplicate pin so those paths also re-arm auto-scroll.
handleJumpToBottom();
jumpToBottom();
},
[autoBackgroundOnSend, handleJumpToBottom]
[autoBackgroundOnSend, jumpToBottom]
);

const handleClearHistory = useCallback(
async (percentage = 1.0) => {
// Re-arm the tail before clearing so the empty/starting state owns the bottom.
handleJumpToBottom();
jumpToBottom();

// Truncate history in backend
const result = await api?.workspace.truncateHistory({ workspaceId, percentage });
Expand All @@ -1046,18 +1045,18 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
throw new Error(result.error);
}
},
[workspaceId, handleJumpToBottom, api]
[workspaceId, jumpToBottom, api]
);

const handleResetContext = useCallback(async (): Promise<"reset" | "noop"> => {
handleJumpToBottom();
jumpToBottom();

const result = await api?.workspace.resetContext({ workspaceId });
if (!result?.success) {
throw new Error(result?.error ?? "Failed to reset context");
}
return result.data;
}, [workspaceId, handleJumpToBottom, api]);
}, [workspaceId, jumpToBottom, api]);

const openInEditor = useOpenInEditor();
const handleOpenInEditor = useCallback(() => {
Expand All @@ -1076,8 +1075,8 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
// the ref-backed auto-scroll flag and pins any cached rows before paint; if rows are still
// hydrating, the next content resize owns the tail instead of showing the prior workspace's state.
useLayoutEffect(() => {
handleJumpToBottom();
}, [hasLoadedTranscriptRows, handleJumpToBottom, workspaceId]);
jumpToBottom();
}, [hasLoadedTranscriptRows, jumpToBottom, workspaceId]);

// Compute showRetryBarrier once for both keybinds and UI.
// Track if last message was interrupted or errored (for RetryBarrier).
Expand Down Expand Up @@ -1232,7 +1231,7 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
(workspaceState?.canInterrupt ?? false) || (workspaceState?.isStreamStarting ?? false),
showRetryBarrier,
chatInputAPI,
jumpToBottom: handleJumpToBottom,
jumpToBottom,
loadOlderHistory: shouldRenderLoadOlderMessagesButton ? handleLoadOlderHistory : null,
handleOpenTerminal: onOpenTerminal,
handleOpenInEditor,
Expand Down Expand Up @@ -1677,7 +1676,7 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
>
{!autoScroll && (
<button
onClick={handleJumpToBottom}
onClick={jumpToBottom}
type="button"
// Sit just above the composer dock (8px gap), tracking its live
// height through normal layout instead of a measured offset.
Expand Down
10 changes: 8 additions & 2 deletions src/browser/components/ChatPane/WorkspaceFooterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ function WorkspaceBranchControls(props: {
);
}

// Shared by the footer's interactive pills (repository link, "Last prompt") so they keep reading as
// one affordance family: restyling one silently drifting from the other is the failure mode here.
const FOOTER_PILL_CLASS =
"text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 transition-colors focus-visible:ring-1";

function FooterRepositoryLabel(props: { workspaceId: string; projectLabel: string }) {
const workspacePR = useWorkspacePR(props.workspaceId);

Expand All @@ -169,7 +174,7 @@ function FooterRepositoryLabel(props: { workspaceId: string; projectLabel: strin
target="_blank"
rel="noopener noreferrer"
data-testid="workspace-footer-repository"
className="text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 transition-colors focus-visible:ring-1"
className={FOOTER_PILL_CLASS}
>
<Github className="h-3 w-3 shrink-0" aria-hidden="true" />
<span className="font-mono">{slug}</span>
Expand Down Expand Up @@ -320,7 +325,8 @@ function FooterLastPrompt(props: { workspaceId: string }) {
<PopoverTrigger asChild>
<button
type="button"
className="text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 cursor-pointer items-center gap-1 rounded-md border-0 bg-transparent px-1.5 transition-colors focus-visible:ring-1"
// The extra classes are <button> resets; the pill styling itself is shared.
className={cn(FOOTER_PILL_CLASS, "cursor-pointer border-0 bg-transparent")}
data-testid="workspace-footer-last-prompt"
onKeyDown={stopKeyboardPropagation}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from "react";
import { useAPI } from "@/browser/contexts/API";

const wrapperClassName =
"pointer-events-none absolute right-[15px] bottom-full left-[15px] z-[1000] mb-2 [&>*]:pointer-events-auto";
import { CHAT_DOCK_TOAST_OVERLAY_CLASS } from "@/constants/layout";

/**
* Connection status banner that uses the same *overlay placement* as ChatInputToast.
Expand Down Expand Up @@ -55,7 +53,7 @@ export const ConnectionStatusToast: React.FC<ConnectionStatusToastProps> = ({ wr

if (!wrap) return content;

return <div className={wrapperClassName}>{content}</div>;
return <div className={CHAT_DOCK_TOAST_OVERLAY_CLASS}>{content}</div>;
}

if (apiState.status === "error") {
Expand All @@ -75,7 +73,7 @@ export const ConnectionStatusToast: React.FC<ConnectionStatusToastProps> = ({ wr

if (!wrap) return content;

return <div className={wrapperClassName}>{content}</div>;
return <div className={CHAT_DOCK_TOAST_OVERLAY_CLASS}>{content}</div>;
}

return null;
Expand Down
Loading