Skip to content
Closed
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
18 changes: 13 additions & 5 deletions apps/web/src/cloud/linkEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const relayClientRpcError = (message: string) => (cause: unknown) =>

function ensureRelayClientAvailable(
environmentId: EnvironmentId,
skipConfirmation: boolean,
): Effect.Effect<void, CloudEnvironmentLinkError, EnvironmentRegistry> {
return Effect.gen(function* () {
const registry = yield* EnvironmentRegistry;
Expand All @@ -78,10 +79,13 @@ function ensureRelayClientAvailable(
});
}

const confirmed = yield* Effect.tryPromise({
try: () => requestRelayClientInstallConfirmation(status.version),
catch: relayClientRpcError("Could not confirm relay client installation."),
});
let confirmed = true;
if (!skipConfirmation) {
confirmed = yield* Effect.tryPromise({
try: () => requestRelayClientInstallConfirmation(status.version),
catch: relayClientRpcError("Could not confirm relay client installation."),
});
}
if (!confirmed) {
return yield* new CloudEnvironmentLinkError({
message: "Relay client installation was cancelled.",
Expand Down Expand Up @@ -403,6 +407,7 @@ export function linkPrimaryEnvironmentToCloud(input: {
readonly target: CloudLinkTarget;
readonly clerkToken: string;
readonly mode?: CloudLinkMode;
readonly skipRelayClientInstallConfirmation?: boolean;
}): Effect.Effect<
void,
CloudEnvironmentLinkError,
Expand All @@ -422,7 +427,10 @@ export function linkPrimaryEnvironmentToCloud(input: {
const relayClient = yield* ManagedRelay.ManagedRelayClient;
const environmentClient = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
if (managedTunnelsEnabled) {
yield* ensureRelayClientAvailable(EnvironmentId.make(input.target.environmentId));
yield* ensureRelayClientAvailable(
EnvironmentId.make(input.target.environmentId),
input.skipRelayClientInstallConfirmation ?? false,
);
}

const challenge = yield* relayClient
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/cloud/linkEnvironmentAtoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const linkPrimaryEnvironment = createRuntimeCommand(connectionAtomRuntime
readonly target: CloudLinkTarget;
readonly clerkToken: string;
readonly mode?: CloudLinkMode;
readonly skipRelayClientInstallConfirmation?: boolean;
}) => linkPrimaryEnvironmentToCloud(input),
});

Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/cloud/useCloudLinkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "./linkEnvironmentAtoms";
import { usePrimaryCloudLinkState } from "./primaryCloudLinkState";
import { resolveRelayClerkTokenOptions } from "./publicConfig";
import { shouldSkipConfirmation } from "../confirmDialog";

export interface CloudLinkDesiredState {
readonly managedTunnel: boolean;
Expand Down Expand Up @@ -78,6 +79,7 @@ export function useCloudLinkController() {
const linked = primaryCloudLinkState.data?.linked ?? false;

const reconcileCloudState = async (desired: CloudLinkDesiredState): Promise<boolean> => {
const skipRelayClientInstallConfirmation = shouldSkipConfirmation();
setOperationError(null);
const target = primaryCloudLinkState.target;
if (!target) {
Expand Down Expand Up @@ -120,6 +122,7 @@ export function useCloudLinkController() {
target,
clerkToken,
mode: desired.managedTunnel ? "managed" : "publish_only",
skipRelayClientInstallConfirmation,
});
if (linkResult._tag === "Failure") {
if (!isAtomCommandInterrupted(linkResult)) {
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ import {
serverUpdateGuidance,
} from "../versionSkew";
import { useAssetUrls } from "../assets/assetUrls";
import { shouldSkipConfirmation } from "../confirmDialog";

const IMAGE_ONLY_BOOTSTRAP_PROMPT =
"[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]";
Expand Down Expand Up @@ -4911,6 +4912,10 @@ function ChatViewContent(props: ChatViewProps) {
]);
const handleRestoreThreadBranch = useCallback(() => {
if (gitStatusQuery.data?.hasWorkingTreeChanges) {
if (shouldSkipConfirmation()) {
void handleSwitchCheckoutToThread();
return;
}
setBranchRestoreConfirmOpen(true);
return;
}
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/components/ConfirmDialogHost.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useSyncExternalStore } from "react";

import {
captureConfirmationTrigger,
completeConfirmDialogClose,
readConfirmDialogState,
registerConfirmDialogHost,
Expand Down Expand Up @@ -59,6 +60,17 @@ export function ConfirmDialogHost() {
);

useEffect(() => registerConfirmDialogHost(), []);
useEffect(() => {
const captureTrigger = (event: MouseEvent | PointerEvent) => captureConfirmationTrigger(event);
document.addEventListener("pointerdown", captureTrigger, true);
document.addEventListener("pointerup", captureTrigger, true);
document.addEventListener("click", captureTrigger, true);
return () => {
document.removeEventListener("pointerdown", captureTrigger, true);
document.removeEventListener("pointerup", captureTrigger, true);
document.removeEventListener("click", captureTrigger, true);
};
}, []);

const copy = resolveConfirmDialogCopy(state.status === "idle" ? "" : state.message);
const confirmVariant = state.status === "idle" ? "default" : state.variant;
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/GitActionsControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { type DraftId, useComposerDraftStore } from "~/composerDraftStore";
import { readLocalApi } from "~/localApi";
import { getSourceControlPresentation } from "~/sourceControlPresentation";
import { openPullRequestLink } from "~/lib/openPullRequestLink";
import { shouldSkipConfirmation } from "~/confirmDialog";

interface GitActionsControlProps {
gitCwd: string | null;
Expand Down Expand Up @@ -1281,6 +1282,7 @@ export default function GitActionsControl({
(action === "commit" || !!actionStatus?.hasWorkingTreeChanges || featureBranch);
if (
!skipDefaultBranchPrompt &&
!shouldSkipConfirmation() &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bypasses a decision dialog rather than a confirmation. The default-branch prompt (GitActionsControl.tsx:2011-2052) has three actions — Abort, the outline continueLabel ("Push to "), and the primary Checkout feature branch & continue — so skipping it hard-codes the non-primary option and pushes/commits directly to the default branch with no prompt and no undo. Because shouldSkipConfirmation() is a document-global flag for the whole event turn, any shift-click that reaches a git quick action gets that outcome, including shift-clicks the user meant for something else.

This is also inconsistent with how the rest of the PR treats choice dialogs (the WSL kind: "enable" modal and the multi-theme removal dialog are deliberately not bypassed). Suggest dropping the bypass here — skipDefaultBranchPrompt already covers the paths that legitimately continue without asking — and removing the now-unused import on line 95 if this is its only use.

         !skipDefaultBranchPrompt &&
-        !shouldSkipConfirmation() &&

Posted via Macroscope — UI Consistency

requiresDefaultBranchConfirmation(action, actionIsDefaultBranch) &&
actionBranch
) {
Expand Down
64 changes: 39 additions & 25 deletions apps/web/src/components/LegacySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
type SidebarThreadSortOrder,
} from "@t3tools/contracts/settings";
import { isDesktopLocalConnectionTarget } from "../connection/desktopLocal";
import { shouldSkipConfirmation } from "../confirmDialog";
import { useDesktopLocalBootstraps } from "../connection/useDesktopLocalBootstraps";
import { isElectron } from "../env";
import { useTerminalFocus } from "../hooks/useTerminalFocus";
Expand Down Expand Up @@ -662,12 +663,22 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr
(event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
if (event.shiftKey) {
void attemptArchiveThread(threadRef);
return;
}
setConfirmingArchiveThreadKey(threadKey);
requestAnimationFrame(() => {
confirmArchiveButtonRefs.current.get(threadKey)?.focus();
});
},
[confirmArchiveButtonRefs, setConfirmingArchiveThreadKey, threadKey],
[
attemptArchiveThread,
confirmArchiveButtonRefs,
setConfirmingArchiveThreadKey,
threadKey,
threadRef,
],
);
const handleArchiveImmediateClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
Expand Down Expand Up @@ -1501,6 +1512,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
actionProps: {
children: "Delete anyway",
onClick: () => {
const skipConfirmation = shouldSkipConfirmation();
void (async () => {
toastManager.close(warningToastId);
await new Promise<void>((resolve) => {
Expand All @@ -1514,30 +1526,32 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
thread.environmentId === memberProjectRef.environmentId &&
thread.projectId === memberProjectRef.projectId,
);
const confirmed = await api.dialogs.confirm(
latestProjectThreads.length > 0
? [
`Remove project "${member.title}" and delete its ${latestProjectThreads.length} thread${
latestProjectThreads.length === 1 ? "" : "s"
}?`,
`Path: ${member.workspaceRoot}`,
...(member.environmentLabel
? [`Environment: ${member.environmentLabel}`]
: []),
"This permanently clears conversation history for those threads.",
"This removes only this project entry.",
"This action cannot be undone.",
].join("\n")
: [
`Remove project "${member.title}"?`,
`Path: ${member.workspaceRoot}`,
...(member.environmentLabel
? [`Environment: ${member.environmentLabel}`]
: []),
"This removes only this project entry.",
].join("\n"),
{ variant: "destructive" },
);
const confirmed =
skipConfirmation ||
(await api.dialogs.confirm(
latestProjectThreads.length > 0
? [
`Remove project "${member.title}" and delete its ${latestProjectThreads.length} thread${
latestProjectThreads.length === 1 ? "" : "s"
}?`,
`Path: ${member.workspaceRoot}`,
...(member.environmentLabel
? [`Environment: ${member.environmentLabel}`]
: []),
"This permanently clears conversation history for those threads.",
"This removes only this project entry.",
"This action cannot be undone.",
].join("\n")
: [
`Remove project "${member.title}"?`,
`Path: ${member.workspaceRoot}`,
...(member.environmentLabel
? [`Environment: ${member.environmentLabel}`]
: []),
"This removes only this project entry.",
].join("\n"),
{ variant: "destructive" },
));
if (!confirmed) {
return;
}
Expand Down
11 changes: 8 additions & 3 deletions apps/web/src/components/clerk/T3ConnectUserProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
useManagedRelayEnvironments,
} from "../../cloud/managedRelayState";
import { useAtomCommand } from "../../state/use-atom-command";
import { shouldSkipConfirmation } from "../../confirmDialog";
import { Button } from "../ui/button";
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible";
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty";
Expand Down Expand Up @@ -232,9 +233,13 @@ export function T3ConnectUserProfilePage() {
environment={environment}
confirmationOpen={confirmingEnvironmentId === environment.environmentId}
mutationPending={deregisteringEnvironmentId !== null}
onConfirmationChange={(open) =>
setConfirmingEnvironmentId(open ? environment.environmentId : null)
}
onConfirmationChange={(open) => {
if (open && shouldSkipConfirmation()) {
void handleDeregister(environment);
return;
}
setConfirmingEnvironmentId(open ? environment.environmentId : null);
}}
onDeregister={(selected) => void handleDeregister(selected)}
/>
))}
Expand Down
26 changes: 16 additions & 10 deletions apps/web/src/components/projectScriptEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "~/lib/projectScriptKeybindings";
import { keybindingFromKeyboardEvent } from "~/components/settings/KeybindingsSettings.logic";
import { commandForProjectScript, nextProjectScriptId } from "~/projectScripts";
import { shouldSkipConfirmation } from "~/confirmDialog";
import {
AlertDialog,
AlertDialogClose,
Expand Down Expand Up @@ -240,6 +241,13 @@ export function ProjectScriptEditorDialog({
onClose();
};

const deleteScript = () => {
if (!request?.scriptId) return;
setDeleteConfirmOpen(false);
onClose();
onDelete(request.scriptId);
};

return (
<>
<Dialog
Expand Down Expand Up @@ -373,7 +381,13 @@ export function ProjectScriptEditorDialog({
type="button"
variant="destructive-outline"
className="mr-auto"
onClick={() => setDeleteConfirmOpen(true)}
onClick={() => {
if (shouldSkipConfirmation()) {
deleteScript();
return;
}
setDeleteConfirmOpen(true);
}}
>
Delete
</Button>
Expand All @@ -396,15 +410,7 @@ export function ProjectScriptEditorDialog({
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogClose render={<Button variant="outline" />}>Cancel</AlertDialogClose>
<Button
variant="destructive"
onClick={() => {
if (!request?.scriptId) return;
setDeleteConfirmOpen(false);
onClose();
onDelete(request.scriptId);
}}
>
<Button variant="destructive" onClick={deleteScript}>
Delete action
</Button>
</AlertDialogFooter>
Expand Down
18 changes: 13 additions & 5 deletions apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { pullRequestEnvironment } from "~/state/pullRequests";
import { useAtomCommand } from "~/state/use-atom-command";
import { vcsEnvironment } from "~/state/vcs";
import { formatRelativeTimeLabel } from "~/timestampFormat";
import { shouldSkipConfirmation } from "~/confirmDialog";

import {
AlertDialog,
Expand Down Expand Up @@ -1026,6 +1027,15 @@ export function PullRequestDetailPanel({
? mergeMethod
: (allowedMergeMethods[0] ?? "merge");
const selectedMergeMethodLabel = MERGE_METHOD_LABELS[selectedMergeMethod];
const requestConfirmedAction = (action: "merge" | "close" | "enable-auto-merge") => {
if (!shouldSkipConfirmation()) {
setConfirmation({ open: true, action });
return;
}
if (action === "merge") void perform("merge", selectedMergeMethod);
if (action === "enable-auto-merge") void perform("enable-auto-merge", selectedMergeMethod);
if (action === "close") void perform("close");
};
const conflicting = detail?.state === "open" && detail.mergeability === "conflicting";
// Only an outright yes arms it. A host that reports nothing has not said the merge is already
// spoken for, and an off switch for something that may not be on says the wrong thing twice.
Expand Down Expand Up @@ -1303,7 +1313,7 @@ export function PullRequestDetailPanel({
<Button
size="xs"
disabled={actionPending}
onClick={() => setConfirmation({ open: true, action: "merge" })}
onClick={() => requestConfirmedAction("merge")}
>
{pendingAction === "merge" ? "Merging..." : selectedMergeMethodLabel}
</Button>
Expand Down Expand Up @@ -1397,9 +1407,7 @@ export function PullRequestDetailPanel({
allowedMergeMethods.length > 0 ? (
<MenuItem
disabled={actionPending}
onClick={() =>
setConfirmation({ open: true, action: "enable-auto-merge" })
}
onClick={() => requestConfirmedAction("enable-auto-merge")}
>
<GitMergeIcon className="size-3.5" />
Enable auto-merge
Expand Down Expand Up @@ -1458,7 +1466,7 @@ export function PullRequestDetailPanel({
<MenuItem
variant="destructive"
disabled={actionPending}
onClick={() => setConfirmation({ open: true, action: "close" })}
onClick={() => requestConfirmedAction("close")}
>
<GitPullRequestClosedIcon className="size-3.5" />
Close pull request
Expand Down
Loading
Loading