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
2 changes: 0 additions & 2 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2165,8 +2165,6 @@ export default function ChatView(props: ChatViewProps) {
timelineEntries,
timelineAnchorMessageId,
setTimelineAnchor,
onNavigateHistory: () => composerRef.current?.collapseForTimelineNavigation(),
onReachedEnd: () => composerRef.current?.restoreAfterTimelineReachedEnd(),
});

// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- schedules a frame to focus the composer DOM element when a thread opens with the terminal closed
Expand Down
83 changes: 0 additions & 83 deletions apps/web/src/components/GitActionsControl.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
resolveDefaultBranchActionDialogCopy,
resolveLiveThreadBranchUpdate,
resolveQuickAction,
buildGitActionProgressStages,
resolveThreadBranchUpdate,
} from "./GitActionsControl.logic";

Expand Down Expand Up @@ -475,88 +474,6 @@ describe("resolveDefaultBranchActionDialogCopy", () => {
});
});

describe("buildGitActionProgressStages", () => {
it("shows only push progress for explicit push actions", () => {
const stages = buildGitActionProgressStages({
action: "push",
hasCustomCommitMessage: false,
hasWorkingTreeChanges: false,
pushTarget: "origin/feature/test",
});
assert.deepEqual(stages, ["Pushing to origin/feature/test..."]);
});

it("shows push and PR progress for create-pr actions that still need a push", () => {
const stages = buildGitActionProgressStages({
action: "create_pr",
hasCustomCommitMessage: false,
hasWorkingTreeChanges: false,
pushTarget: "origin/feature/test",
shouldPushBeforePr: true,
});
assert.deepEqual(stages, [
"Pushing to origin/feature/test...",
"Preparing PR...",
"Generating PR content...",
"Creating pull request...",
]);
});

it("shows only PR progress when create-pr can skip the push", () => {
const stages = buildGitActionProgressStages({
action: "create_pr",
hasCustomCommitMessage: false,
hasWorkingTreeChanges: false,
shouldPushBeforePr: false,
});
assert.deepEqual(stages, [
"Preparing PR...",
"Generating PR content...",
"Creating pull request...",
]);
});

it("includes commit stages for commit+push when working tree is dirty", () => {
const stages = buildGitActionProgressStages({
action: "commit_push",
hasCustomCommitMessage: false,
hasWorkingTreeChanges: true,
pushTarget: "origin/feature/test",
});
assert.deepEqual(stages, [
"Generating commit message...",
"Committing...",
"Pushing to origin/feature/test...",
]);
});

it("includes granular PR stages for commit+push+PR actions", () => {
const stages = buildGitActionProgressStages({
action: "commit_push_pr",
hasCustomCommitMessage: true,
hasWorkingTreeChanges: true,
pushTarget: "origin/feature/test",
});
assert.deepEqual(stages, [
"Committing...",
"Pushing to origin/feature/test...",
"Preparing PR...",
"Generating PR content...",
"Creating pull request...",
]);
});

it("names the feature ref with Git nouns when no VCS terminology is supplied", () => {
const stages = buildGitActionProgressStages({
action: "commit",
hasCustomCommitMessage: true,
hasWorkingTreeChanges: true,
featureBranch: true,
});
assert.deepEqual(stages, ["Preparing feature branch...", "Committing..."]);
});
});

describe("resolveThreadBranchUpdate", () => {
it("returns a branch update when the action created a new branch", () => {
const update = resolveThreadBranchUpdate({
Expand Down
42 changes: 0 additions & 42 deletions apps/web/src/components/GitActionsControl.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,48 +111,6 @@ export function formatGitActionElapsed(startedAtMs: number | null, nowMs: number
return `${minutes}m ${seconds}s`;
}

export function buildGitActionProgressStages(input: {
action: GitStackedAction;
hasCustomCommitMessage: boolean;
hasWorkingTreeChanges: boolean;
pushTarget?: string;
featureBranch?: boolean;
shouldPushBeforePr?: boolean;
terminology?: ChangeRequestTerminology;
vcsTerminology?: VcsTerminology;
}): string[] {
const terminology = input.terminology ?? sourceControlClients.get(undefined).changeRequest;
const vcs = input.vcsTerminology ?? DEFAULT_VCS_TERMINOLOGY;
const branchStages = input.featureBranch ? [`Preparing feature ${vcs.refNoun}...`] : [];
const pushStage = input.pushTarget ? `Pushing to ${input.pushTarget}...` : "Pushing...";
const prStages = [
`Preparing ${terminology.shortLabel}...`,
`Generating ${terminology.shortLabel} content...`,
`Creating ${terminology.singular}...`,
];

if (input.action === "push") {
return [pushStage];
}
if (input.action === "create_pr") {
return input.shouldPushBeforePr ? [pushStage, ...prStages] : prStages;
}

const shouldIncludeCommitStages = input.action === "commit" || input.hasWorkingTreeChanges;
const commitStages = !shouldIncludeCommitStages
? []
: input.hasCustomCommitMessage
? ["Committing..."]
: ["Generating commit message...", "Committing..."];
if (input.action === "commit") {
return [...branchStages, ...commitStages];
}
if (input.action === "commit_push") {
return [...branchStages, ...commitStages, pushStage];
}
return [...branchStages, ...commitStages, pushStage, ...prStages];
}

export function buildMenuItems(
gitStatus: VcsStatusResult | null,
isBusy: boolean,
Expand Down
43 changes: 6 additions & 37 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,6 @@ function consumePasteAsTextShortcut(deadline: { current: number }): boolean {
}

export interface ChatComposerHandle {
collapseForTimelineNavigation: () => void;
restoreAfterTimelineReachedEnd: () => void;
focusAtEnd: () => void;
focusAt: (cursor: number) => void;
addDroppedFiles: (files: File[]) => void;
Expand Down Expand Up @@ -1053,7 +1051,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
const isMobileViewport = useMediaQuery("max-sm");
const isSoftwareKeyboardOpen = useSoftwareKeyboardOpen();
const [isComposerFocused, setIsComposerFocused] = useState(false);
const [scrollCollapsedTarget, setScrollCollapsedTarget] = useState<string | null>(null);
const [composerSubmissionError, setComposerSubmissionError] = useState<string | null>(null);
const [providerInputSubmissionError, setProviderInputSubmissionError] = useState<string | null>(
null,
Expand All @@ -1069,21 +1066,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
!isComposerFocused &&
!hasMultilinePrompt &&
pendingUserInputs.length === 0;
const isComposerScrollCollapsed =
!isMobileViewport &&
routeKind === "server" &&
settings.composerCollapseOnScroll &&
scrollCollapsedTarget === composerDraftTargetKey &&
!hasMultilinePrompt &&
!isEditingQueuedMessage &&
composerTrigger === null &&
pendingUserInputs.length === 0 &&
activePendingApproval === null &&
!isDragOverComposer &&
!isPreparingWorktree &&
composerSubmissionError === null &&
providerInputSubmissionError === null;
const isComposerCollapsed = isComposerCollapsedMobile || isComposerScrollCollapsed;

const composerEditorRef = useRef<ComposerPromptEditorHandle>(null);
const pasteAsTextShortcutUntilRef = useRef(0);
Expand Down Expand Up @@ -1236,7 +1218,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
const showComposerTopDrawer =
pendingUserInputs.length > 0 ||
(!isComposerCollapsedMobile && showPlanFollowUpPrompt && activeProposedPlan !== null);
const showCollapsedMobilePromptRow = isComposerCollapsed;
const showCollapsedMobilePromptRow = isComposerCollapsedMobile;
const showComposerAttachAction =
fileStagingLimit !== null &&
(!activePendingProgress ||
Expand Down Expand Up @@ -1636,7 +1618,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
cursorAdjacentToMention: boolean,
contextIds: string[],
) => {
setScrollCollapsedTarget(null);
refreshWorkspaceSnapshotOnTyping();
if (activePendingProgress?.activeQuestion && pendingUserInputs.length > 0) {
if (activePendingProgress.activeQuestion.allowCustomAnswer === false) return;
Expand Down Expand Up @@ -2365,7 +2346,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
};
const compactThreadContextStable = useStableCallback(compactThreadContext);
const expandMobileComposer = () => {
setScrollCollapsedTarget(null);
if (composerBlurFrameRef.current !== null) {
window.cancelAnimationFrame(composerBlurFrameRef.current);
composerBlurFrameRef.current = null;
Expand Down Expand Up @@ -2466,8 +2446,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
expandMobileComposer: expandMobileComposerStable,
});
const hasBlockingComposerTopDrawer = pendingUserInputs.length > 0;
const composerControlsCollapsed = isComposerCollapsed;
const showInlineRestingControls = isComposerCollapsed;
const composerControlsCollapsed = isComposerCollapsedMobile;
// The collapsed mobile web composer keeps a second controls row inside the card.
const showInlineRestingControls = isComposerCollapsedMobile;
const composerControlsHidden = showInlineRestingControls && !restingControlsVisible;
if (composerControlsHidden && isComposerModelPickerOpen) {
setIsComposerModelPickerOpen(false);
Expand Down Expand Up @@ -2704,11 +2685,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
};
const insertComposerTextAtEndStable = useStableCallback(insertComposerTextAtEnd);

// The scroll-collapsed editor is hidden, so it has to render before it can take focus.
const revealScrollCollapsedEditor = () => {
if (isComposerScrollCollapsed) flushSync(() => setScrollCollapsedTarget(null));
};

const typeComposerKey: ChatComposerHandle["typeKey"] = (event) => {
if (!composerVimActive) return insertComposerTextAtEnd(event.key);
// Vim runs keys as commands at its caret, which only its focused editor can do. A
Expand All @@ -2717,7 +2693,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
expandMobileComposer();
return true;
}
revealScrollCollapsedEditor();
composerEditorRef.current?.focus();
return (
composerEditorRef.current?.handleVimKey(event) || insertComposerText(event.key, "cursor")
Expand Down Expand Up @@ -3023,7 +2998,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
}, []);

const openModelPicker = () => {
setScrollCollapsedTarget(null);
if (composerControlsHidden) {
if (composerBlurFrameRef.current !== null) {
window.cancelAnimationFrame(composerBlurFrameRef.current);
Expand All @@ -3039,15 +3013,11 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
composerRef,
() => ({
focusAtEnd: () => {
revealScrollCollapsedEditor();
composerEditorRef.current?.focusAtEnd();
},
focusAt: (cursor: number) => {
revealScrollCollapsedEditor();
composerEditorRef.current?.focusAt(cursor);
},
Comment on lines 3013 to 3020
collapseForTimelineNavigation: () => setScrollCollapsedTarget(composerDraftTargetKey),
restoreAfterTimelineReachedEnd: () => setScrollCollapsedTarget(null),
addDroppedFiles: (files: File[]) => {
void addComposerAttachments(files).then((inserted) => {
if (!inserted) focusComposer();
Expand Down Expand Up @@ -3375,7 +3345,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
composerBlurFrameRef.current = null;
}
setIsComposerFocused(true);
setScrollCollapsedTarget(null);
}}
onBlurCapture={(event) => {
if (
Expand Down Expand Up @@ -3526,7 +3495,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
className={cn(
"relative px-4 pb-2",
"pt-3.5 sm:pt-4",
isComposerCollapsed && "hidden",
isComposerCollapsedMobile && "hidden",
)}
>
{isStashMenuOpen && !composerMenuOpen && (
Expand Down Expand Up @@ -3707,7 +3676,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
</div>
) : null}

{isComposerCollapsed && speechInput.state.phase === "idle" ? null : (
{isComposerCollapsedMobile && speechInput.state.phase === "idle" ? null : (
<div
data-chat-composer-footer="true"
className={cn(
Expand Down
10 changes: 1 addition & 9 deletions apps/web/src/components/chat/useTimelineFollow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,6 @@ interface TimelineFollowInput {
readonly timelineEntries: ReadonlyArray<TimelineEntry>;
readonly timelineAnchorMessageId: MessageId | null;
readonly setTimelineAnchor: Dispatch<SetStateAction<TimelineAnchor>>;
readonly onNavigateHistory: () => void;
readonly onReachedEnd: () => void;
}

/**
Expand Down Expand Up @@ -308,7 +306,6 @@ export function useTimelineFollow(input: TimelineFollowInput) {
);
const handlePageScrollStart = useStableCallback((key: PageScrollKey) => {
if ((key === "PageUp" && timelineRealContentOverflowsViewport()) || !isTimelineAtLogicalEnd()) {
input.onNavigateHistory();
// Through the viewport, so a running position restore stops too.
viewportRef.current?.navigate({ releasesFollow: true });
}
Expand All @@ -326,7 +323,6 @@ export function useTimelineFollow(input: TimelineFollowInput) {
};
const onComposerPageScrollReleaseStable = useStableCallback(onComposerPageScrollRelease);
const scrollToEnd = (animated: boolean = false) => {
input.onReachedEnd();
// Stops a running position restore; follow itself is re-armed below.
viewportRef.current?.navigate({ releasesFollow: false });
followStore.dispatch(visit, { type: "followEnd" });
Expand Down Expand Up @@ -368,10 +364,7 @@ export function useTimelineFollow(input: TimelineFollowInput) {
contentScrollsUp: timelineRealContentOverflowsViewport,
awayFromEnd,
});
if (releases) {
releaseFollowForNavigation();
input.onNavigateHistory();
}
if (releases) releaseFollowForNavigation();
};
const handleNavigationStable = useStableCallback(handleNavigation);
/**
Expand Down Expand Up @@ -495,7 +488,6 @@ export function useTimelineFollow(input: TimelineFollowInput) {
const onTimelineFooterGrowStable = useStableCallback(onTimelineFooterGrow);

const onIsAtEndChange = (isAtEnd: boolean) => {
if (isAtEnd) input.onReachedEnd();
const scroll = visitScroll();
if (!isAtEnd && readFollow().mode === "following") {
scroll.showScrollDebouncer.cancel();
Expand Down
2 changes: 0 additions & 2 deletions apps/web/src/components/guest/GuestPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,6 @@ function GuestThread({
timelineEntries: entries,
timelineAnchorMessageId: null,
setTimelineAnchor: ignore,
onNavigateHistory: ignore,
onReachedEnd: ignore,
});
const renderHtmlRender = useStableCallback(
(row: Extract<TimelineRow, { kind: "html-render" }>) => (
Expand Down
6 changes: 0 additions & 6 deletions apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1108,12 +1108,6 @@ export function ChatSettingsPanel() {
description:
"Use the system clipboard for Vim yanks, deletes, and pastes. When off, use a private Vim register.",
},
{
key: "composerCollapseOnScroll",
searchId: "composer-collapse",
description:
"Rest the composer of an existing thread into a single line when you scroll the conversation. Focus the composer or start typing to expand it again.",
},
] as const;

return (
Expand Down
6 changes: 0 additions & 6 deletions apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,12 +725,6 @@ export const SETTINGS_SEARCH_ITEMS = [
to: "/settings/chat",
searchTerms: ["nvim neovim vim yank paste register copy"],
},
{
id: "composer-collapse",
title: "Collapse composer on scroll",
to: "/settings/chat",
searchTerms: ["composer rest resting scroll wheel conversation timeline shrink minimize"],
},
{
id: "send-shortcut",
title: "Send shortcut",
Expand Down
Loading