Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 13 additions & 3 deletions apps/web/src/components/BranchToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { ArrowDownIcon, FolderIcon, GitForkIcon, LoaderIcon } from "lucide-react
import { useCallback, useEffect } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

import { gitPullMutationOptions, gitQueryKeys, gitStatusQueryOptions, invalidateGitQueries } from "../lib/gitReactQuery";
import {
gitPullMutationOptions,
gitQueryKeys,
gitStatusQueryOptions,
invalidateGitQueries,
} from "../lib/gitReactQuery";
import { newCommandId } from "../lib/utils";
import { readNativeApi } from "../nativeApi";
import { useComposerDraftStore } from "../composerDraftStore";
Expand Down Expand Up @@ -222,14 +227,19 @@ export default function BranchToolbar({
<ArrowDownIcon className="size-3" />
)}
Pull
<Badge variant="outline" size="sm" className="ml-0.5 px-1 py-0 text-[10px] text-warning border-warning/30">
<Badge
variant="outline"
size="sm"
className="ml-0.5 px-1 py-0 text-[10px] text-warning border-warning/30"
>
{behindCount}
</Badge>
</Button>
}
/>
<TooltipPopup side="bottom" align="end">
Local branch is {behindCount} commit{behindCount !== 1 ? "s" : ""} behind upstream. Pull to update before starting a new thread.
Local branch is {behindCount} commit{behindCount !== 1 ? "s" : ""} behind upstream.
Pull to update before starting a new thread.
</TooltipPopup>
</Tooltip>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1486,7 +1486,6 @@ export default function ChatView({ threadId }: ChatViewProps) {
});
}, [diffOpen, navigate, threadId]);

const toggleCodeViewer = useCodeViewerStore((state) => state.toggle);
const pendingContext = useCodeViewerStore((state) => state.pendingContext);
const clearPendingContext = useCodeViewerStore((state) => state.clearPendingContext);

Expand Down Expand Up @@ -5547,6 +5546,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
closeShortcutLabel={closeTerminalShortcutLabel ?? undefined}
onActiveTerminalChange={activateTerminal}
onCloseTerminal={closeTerminal}
onCollapseTerminal={toggleTerminalVisibility}
onHeightChange={setTerminalHeight}
onAddTerminalContext={addTerminalContextToDraft}
onSendTerminalContext={sendSelectedTerminalContext}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ export default function Sidebar() {
},
});
const { copyToClipboard: copyPathToClipboard } = useCopyToClipboard<{ path: string }>({
onCopy: (ctx) => {
onCopy: () => {
toastManager.add({
type: "success",
title: "Path copied",
Expand Down
117 changes: 55 additions & 62 deletions apps/web/src/components/ThreadTerminalDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ import {
useRef,
useState,
} from "react";
import {
Menu,
MenuItem,
MenuPopup,
MenuSeparator,
MenuShortcut,
MenuTrigger,
} from "~/components/ui/menu";
import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover";
import { type TerminalContextSelection } from "~/lib/terminalContext";
import { openInPreferredEditor } from "../editorPreferences";
Expand Down Expand Up @@ -817,6 +809,7 @@ interface ThreadTerminalDrawerProps {
closeShortcutLabel?: string | undefined;
onActiveTerminalChange: (terminalId: string) => void;
onCloseTerminal: (terminalId: string) => void;
onCollapseTerminal: () => void;
onHeightChange: (height: number) => void;
onAddTerminalContext: (selection: TerminalContextSelection) => void;
onSendTerminalContext?: ((selection: TerminalContextSelection) => void) | undefined;
Expand Down Expand Up @@ -852,62 +845,60 @@ function TerminalActionButton({ label, className, onClick, children }: TerminalA
);
}

interface TerminalDropdownMenuProps {
interface TerminalActionRailProps {
onSplitTerminal: () => void;
onNewTerminal: () => void;
onCloseTerminal: () => void;
onCollapseTerminal: () => void;
splitDisabled: boolean;
splitLabel: string;
newLabel: string;
closeLabel: string;
splitShortcutLabel?: string | undefined;
newShortcutLabel?: string | undefined;
closeShortcutLabel?: string | undefined;
triggerClassName: string;
collapseLabel: string;
actionClassName: string;
destructiveActionClassName?: string | undefined;
}

function TerminalDropdownMenu({
function TerminalActionRail({
onSplitTerminal,
onNewTerminal,
onCloseTerminal,
onCollapseTerminal,
splitDisabled,
splitLabel,
newLabel,
closeLabel,
splitShortcutLabel,
newShortcutLabel,
closeShortcutLabel,
triggerClassName,
}: TerminalDropdownMenuProps) {
collapseLabel,
actionClassName,
destructiveActionClassName,
}: TerminalActionRailProps) {
return (
<Menu>
<MenuTrigger
render={<button type="button" className={triggerClassName} aria-label="Terminal actions" />}
<div className="inline-flex items-center">
<TerminalActionButton
label={splitLabel}
className={`${actionClassName} ${splitDisabled ? "pointer-events-none opacity-45" : ""}`}
onClick={onSplitTerminal}
>
<ChevronDown className="size-3.25" />
</MenuTrigger>
<MenuPopup align="end" side="bottom" sideOffset={6}>
<MenuItem
className={splitDisabled ? "pointer-events-none opacity-45" : ""}
onClick={onSplitTerminal}
>
<SquareSplitHorizontal className="size-4" />
{splitLabel}
{splitShortcutLabel ? <MenuShortcut>{splitShortcutLabel}</MenuShortcut> : null}
</MenuItem>
<MenuItem onClick={onNewTerminal}>
<Plus className="size-4" />
{newLabel}
{newShortcutLabel ? <MenuShortcut>{newShortcutLabel}</MenuShortcut> : null}
</MenuItem>
<MenuSeparator />
<MenuItem variant="destructive" onClick={onCloseTerminal}>
<Trash2 className="size-4" />
{closeLabel}
{closeShortcutLabel ? <MenuShortcut>{closeShortcutLabel}</MenuShortcut> : null}
</MenuItem>
</MenuPopup>
</Menu>
<SquareSplitHorizontal className="size-3.5" />
</TerminalActionButton>
<TerminalActionButton label={newLabel} className={actionClassName} onClick={onNewTerminal}>
<Plus className="size-3.5" />
</TerminalActionButton>
<TerminalActionButton
label={closeLabel}
className={destructiveActionClassName ?? actionClassName}
onClick={onCloseTerminal}
>
<Trash2 className="size-3.5" />
</TerminalActionButton>
<TerminalActionButton
label={collapseLabel}
className={actionClassName}
onClick={onCollapseTerminal}
>
<ChevronDown className="size-3.5" />
</TerminalActionButton>
</div>
);
}

Expand All @@ -928,6 +919,7 @@ export default function ThreadTerminalDrawer({
closeShortcutLabel,
onActiveTerminalChange,
onCloseTerminal,
onCollapseTerminal,
onHeightChange,
onAddTerminalContext,
onSendTerminalContext,
Expand Down Expand Up @@ -1056,6 +1048,7 @@ export default function ThreadTerminalDrawer({
const closeTerminalActionLabel = closeShortcutLabel
? `Close Terminal (${closeShortcutLabel})`
: "Close Terminal";
const collapseTerminalActionLabel = "Collapse Terminal";
const onSplitTerminalAction = useCallback(() => {
if (hasReachedSplitLimit) return;
onSplitTerminal();
Expand Down Expand Up @@ -1171,18 +1164,18 @@ export default function ThreadTerminalDrawer({
{!hasTerminalSidebar && (
<div className="pointer-events-none absolute right-2 top-2 z-20">
<div className="pointer-events-auto inline-flex items-center overflow-hidden rounded-md border border-border/80 bg-background/70">
<TerminalDropdownMenu
<TerminalActionRail
onSplitTerminal={onSplitTerminalAction}
onNewTerminal={onNewTerminalAction}
onCloseTerminal={() => onCloseTerminal(resolvedActiveTerminalId)}
onCollapseTerminal={onCollapseTerminal}
splitDisabled={hasReachedSplitLimit}
splitLabel="Split Terminal"
newLabel="New Terminal"
closeLabel="Close Terminal"
splitShortcutLabel={splitShortcutLabel}
newShortcutLabel={newShortcutLabel}
closeShortcutLabel={closeShortcutLabel}
triggerClassName="p-1 text-foreground/90 transition-colors hover:bg-accent"
splitLabel={splitTerminalActionLabel}
newLabel={newTerminalActionLabel}
closeLabel={closeTerminalActionLabel}
collapseLabel={collapseTerminalActionLabel}
actionClassName="inline-flex size-7 items-center justify-center text-foreground/90 transition-colors hover:bg-accent"
destructiveActionClassName="inline-flex size-7 items-center justify-center text-destructive transition-colors hover:bg-destructive/10"
/>
</div>
</div>
Expand Down Expand Up @@ -1255,18 +1248,18 @@ export default function ThreadTerminalDrawer({
<aside className="flex w-36 min-w-36 flex-col border border-border/70 bg-muted/10">
<div className="flex h-[22px] items-stretch justify-end border-b border-border/70">
<div className="inline-flex h-full items-stretch">
<TerminalDropdownMenu
<TerminalActionRail
onSplitTerminal={onSplitTerminalAction}
onNewTerminal={onNewTerminalAction}
onCloseTerminal={() => onCloseTerminal(resolvedActiveTerminalId)}
onCollapseTerminal={onCollapseTerminal}
splitDisabled={hasReachedSplitLimit}
splitLabel="Split Terminal"
newLabel="New Terminal"
closeLabel="Close Terminal"
splitShortcutLabel={splitShortcutLabel}
newShortcutLabel={newShortcutLabel}
closeShortcutLabel={closeShortcutLabel}
triggerClassName="inline-flex h-full items-center px-1 text-foreground/90 transition-colors hover:bg-accent/70"
splitLabel={splitTerminalActionLabel}
newLabel={newTerminalActionLabel}
closeLabel={closeTerminalActionLabel}
collapseLabel={collapseTerminalActionLabel}
actionClassName="inline-flex h-full w-5 items-center justify-center text-foreground/90 transition-colors hover:bg-accent/70"
destructiveActionClassName="inline-flex h-full w-5 items-center justify-center text-destructive transition-colors hover:bg-destructive/10"
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/WorkspaceFileTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: {
? "Finder"
: "File Manager";
const { copyToClipboard: copyPathToClipboard } = useCopyToClipboard<{ path: string }>({
onCopy: (ctx) => {
onCopy: () => {
toastManager.add({
type: "success",
title: "Path copied",
Expand Down
20 changes: 16 additions & 4 deletions apps/web/src/components/chat/InlineDiffBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function computeLineDiff(oldStr: string, newStr: string): DiffLine[] {
}
}

return result.reverse();
return result.toReversed();
}

/**
Expand Down Expand Up @@ -174,12 +174,24 @@ export const InlineDiffBlock = memo(function InlineDiffBlock(props: {

const allLines = useMemo(() => buildDiffLines(diffData), [diffData]);
const stats = useMemo(() => countStats(allLines), [allLines]);
const keyedLines = useMemo(() => {
const occurrenceBySignature = new Map<string, number>();
return allLines.map((line) => {
const signature = `${line.kind}:${line.text}`;
const occurrence = (occurrenceBySignature.get(signature) ?? 0) + 1;
occurrenceBySignature.set(signature, occurrence);
return {
key: `${signature}:${occurrence}`,
line,
};
});
}, [allLines]);

if (allLines.length === 0) return null;

const needsTruncation = allLines.length > MAX_VISIBLE_LINES;
const visibleLines =
needsTruncation && !isExpanded ? allLines.slice(0, MAX_VISIBLE_LINES) : allLines;
needsTruncation && !isExpanded ? keyedLines.slice(0, MAX_VISIBLE_LINES) : keyedLines;
const hiddenCount = allLines.length - visibleLines.length;
const fileName = basename(diffData.filePath);

Expand Down Expand Up @@ -208,9 +220,9 @@ export const InlineDiffBlock = memo(function InlineDiffBlock(props: {

{/* Diff lines */}
<div className="border-t border-border/40">
{visibleLines.map((line, idx) => (
{visibleLines.map(({ key, line }) => (
<div
key={idx}
key={key}
className={cn(
"flex border-l-2 font-mono text-[11px] leading-5",
lineKindStyle(line.kind),
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,6 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
const EntryIcon = workEntryIcon(workEntry);
const heading = toolWorkEntryHeading(workEntry);
const preview = workEntryPreview(workEntry);
const displayText = preview ? `${heading} - ${preview}` : heading;
const hasChangedFiles = (workEntry.changedFiles?.length ?? 0) > 0;
const previewIsChangedFiles = hasChangedFiles && !workEntry.command && !workEntry.detail;
const hasDiffData = workEntry.diffData != null && workEntry.itemType === "file_change";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ function MergeConflictGuidanceRail({

const navigateToLatestThread = useCallback(() => {
openCodeViewer();
const sorted = [...threads].sort((a, b) =>
const sorted = threads.toSorted((a, b) =>
(b.updatedAt ?? b.createdAt).localeCompare(a.updatedAt ?? a.createdAt),
);
const latest = sorted[0];
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/hooks/useAutoDeleteMergedThreads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ export function useAutoDeleteMergedThreads(settings: AppSettings) {

// Cleanup all timers on unmount.
useEffect(() => {
const timers = timersRef.current;
return () => {
for (const [, timer] of timersRef.current) {
for (const [, timer] of timers) {
clearTimeout(timer.timeoutId);
}
timersRef.current.clear();
timers.clear();
};
}, []);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/hooks/useFileViewNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function useFileViewNavigation() {
// If not already on a thread page, navigate to the most recent thread
// so the code-viewer inline sidebar is visible.
if (!threadId) {
const sorted = [...threads].sort((a, b) =>
const sorted = threads.toSorted((a, b) =>
(b.updatedAt ?? b.createdAt).localeCompare(a.updatedAt ?? a.createdAt),
);
const latest = sorted[0];
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/routes/_chat.file-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function FileViewRouteRedirect() {
}

// Navigate to the most recent thread (or home)
const sorted = [...threads].sort((a, b) =>
const sorted = threads.toSorted((a, b) =>
(b.updatedAt ?? b.createdAt).localeCompare(a.updatedAt ?? a.createdAt),
);
const latest = sorted[0];
Expand Down
14 changes: 7 additions & 7 deletions docs/releases/v0.14.0/assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ After the workflow completes, expect **installer and updater** artifacts similar

## Desktop installers and payloads

| Platform | Kind | Typical pattern |
| ------------------- | -------------- | ----------------- |
| macOS Apple Silicon | DMG (signed) | `*.dmg` (arm64) |
| macOS Intel | DMG (signed) | `*.dmg` (x64) |
| macOS | ZIP (updater) | `*.zip` |
| Linux x64 | AppImage | `*.AppImage` |
| Windows x64 | NSIS installer | `*.exe` |
| Platform | Kind | Typical pattern |
| ------------------- | -------------- | --------------- |
| macOS Apple Silicon | DMG (signed) | `*.dmg` (arm64) |
| macOS Intel | DMG (signed) | `*.dmg` (x64) |
| macOS | ZIP (updater) | `*.zip` |
| Linux x64 | AppImage | `*.AppImage` |
| Windows x64 | NSIS installer | `*.exe` |

### macOS code signing and notarization

Expand Down
7 changes: 1 addition & 6 deletions packages/contracts/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,7 @@ import {
} from "./project";
import { ProjectFileTreeChangedPayload } from "./project";
import { OpenInEditorInput, OpenPathInput } from "./editor";
import {
GeneratePairingLinkInput,
ListTokensResult,
RevokeTokenInput,
ServerConfigUpdatedPayload,
} from "./server";
import { GeneratePairingLinkInput, RevokeTokenInput, ServerConfigUpdatedPayload } from "./server";
import {
SkillListInput,
SkillCatalogInput,
Expand Down
Loading