Skip to content

Commit c2fae1b

Browse files
fix(web): confirm profile removal and keep the table honest
Removing a profile deleted it and wiped its cookies and cache from a single icon-menu click, while every comparable destructive action in Settings confirms first. It now routes through the same AlertDialog. The Default badge resolved against the unfiltered profile list while the table renders only non-incognito rows, so a stored default of "incognito" left the section with no default marked at all. It now resolves against the rows that render. Reopening the import menu kept the previous source list on screen while the refresh was in flight, leaving a source that had since become unavailable selectable; the list is cleared first so the menu shows its loading state. A cookie sidecar that exists but cannot be copied is no longer ignored alongside the missing-file case. SQLite would open the snapshot without the write-ahead log and return a cookie set silently missing its newest transactions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b629dd9 commit c2fae1b

2 files changed

Lines changed: 87 additions & 60 deletions

File tree

apps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,17 @@ const snapshotCookieDatabase = Effect.fn("ChromiumCookies.snapshotCookieDatabase
160160
const directory = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3code-cookie-import-" });
161161
const target = path.join(directory, "Cookies");
162162
yield* fileSystem.copyFile(cookiePath, target);
163-
// The sidecars only exist while the browser holds the database open, so a
164-
// missing one is normal rather than a failure.
163+
// A sidecar only exists while the browser holds the database open, so an
164+
// absent one is normal. Anything else — a permission error, a partial read —
165+
// is not: SQLite would then open the snapshot without the write-ahead log
166+
// and quietly return a cookie set missing its most recent transactions.
165167
yield* Effect.forEach(["-wal", "-shm"], (suffix) =>
166-
fileSystem.copyFile(`${cookiePath}${suffix}`, `${target}${suffix}`).pipe(Effect.ignore),
168+
fileSystem.copyFile(`${cookiePath}${suffix}`, `${target}${suffix}`).pipe(
169+
Effect.catchIf(
170+
(error) => error.reason._tag === "NotFound",
171+
() => Effect.void,
172+
),
173+
),
167174
);
168175
return target;
169176
});

apps/web/src/components/settings/IntegrationsSettings.tsx

Lines changed: 77 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ import {
6464
MenuTrigger,
6565
} from "../ui/menu";
6666
import { toastManager } from "../ui/toast";
67+
import {
68+
AlertDialog,
69+
AlertDialogClose,
70+
AlertDialogDescription,
71+
AlertDialogFooter,
72+
AlertDialogHeader,
73+
AlertDialogPopup,
74+
AlertDialogTitle,
75+
} from "../ui/alert-dialog";
6776
import { Button } from "../ui/button";
6877
import { DraftInput } from "../ui/draft-input";
6978
import { NumberField, NumberFieldGroup, NumberFieldInput } from "../ui/number-field";
@@ -533,12 +542,18 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
533542
const updateSettings = useUpdatePrimarySettings();
534543
const environmentId = usePrimaryEnvironment()?.environmentId;
535544
const [sources, setSources] = useState<ReadonlyArray<BrowserImportSource> | null>(null);
545+
const [profilePendingRemoval, setProfilePendingRemoval] = useState<BrowserProfile | null>(null);
536546
const [busy, setBusy] = useState(false);
537547
const [profilePendingRemoval, setProfilePendingRemoval] = useState<BrowserProfile | null>(null);
538548

539549
const profiles = resolveBrowserProfiles(userProfiles);
550+
// Incognito is deliberately not a row — it holds nothing to manage — so the
551+
// default has to resolve against the list that renders. A stored
552+
// `browserDefaultProfileId` of "incognito" would otherwise leave the section
553+
// with no Default badge at all.
554+
const listedProfiles = profiles.filter((profile) => profile.kind !== "incognito");
540555
const resolvedDefaultId =
541-
findBrowserProfile(profiles, defaultProfileId)?.id ?? DEFAULT_BROWSER_PROFILE_ID;
556+
findBrowserProfile(listedProfiles, defaultProfileId)?.id ?? DEFAULT_BROWSER_PROFILE_ID;
542557

543558
const uniqueName = (base: string) => {
544559
const taken = new Set(profiles.map((profile) => profile.name));
@@ -599,6 +614,10 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
599614

600615
const loadSources = () => {
601616
if (!previewBridge) return;
617+
// Cleared first: availability changes while the app runs (quitting a
618+
// browser clears `browserRunning`), and showing the previous answer during
619+
// the refresh lets the user start an import the source no longer supports.
620+
setSources(null);
602621
void previewBridge
603622
.listBrowserImportSources()
604623
.then(setSources)
@@ -740,69 +759,70 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
740759
}
741760
>
742761
<div className="mt-2 overflow-hidden rounded-lg border border-border/60">
743-
{profiles
744-
.filter((profile) => profile.kind !== "incognito")
745-
.map((profile, index) => {
746-
const builtIn = isBuiltInBrowserProfileId(profile.id);
747-
const isDefault = profile.id === resolvedDefaultId;
748-
return (
749-
<div
750-
key={profile.id}
751-
className={cn(
752-
"flex items-center gap-3 px-3 py-2",
753-
index > 0 && "border-t border-border/60",
762+
{listedProfiles.map((profile, index) => {
763+
const builtIn = isBuiltInBrowserProfileId(profile.id);
764+
const isDefault = profile.id === resolvedDefaultId;
765+
return (
766+
<div
767+
key={profile.id}
768+
className={cn(
769+
"flex items-center gap-3 px-3 py-2",
770+
index > 0 && "border-t border-border/60",
771+
)}
772+
>
773+
<span className="flex min-w-0 flex-1 items-center gap-2">
774+
{builtIn ? (
775+
<span className="truncate text-sm text-foreground">{profile.name}</span>
776+
) : (
777+
<DraftInput
778+
nativeInput
779+
size="sm"
780+
className="w-full max-w-56"
781+
aria-label={`Rename ${profile.name}`}
782+
disabled={disabled}
783+
maxLength={BROWSER_PROFILE_NAME_MAX_LENGTH}
784+
value={profile.name}
785+
onCommit={(next) => renameProfile(profile.id, next)}
786+
/>
754787
)}
755-
>
756-
<span className="flex min-w-0 flex-1 items-center gap-2">
757-
{builtIn ? (
758-
<span className="truncate text-sm text-foreground">{profile.name}</span>
759-
) : (
760-
<DraftInput
761-
nativeInput
762-
size="sm"
763-
className="w-full max-w-56"
764-
aria-label={`Rename ${profile.name}`}
788+
{isDefault ? <Badge>Default</Badge> : null}
789+
</span>
790+
<Menu>
791+
<MenuTrigger
792+
render={
793+
<Button
794+
size="icon-sm"
795+
variant="ghost-muted"
765796
disabled={disabled}
766-
maxLength={BROWSER_PROFILE_NAME_MAX_LENGTH}
767-
value={profile.name}
768-
onCommit={(next) => renameProfile(profile.id, next)}
797+
aria-label={`${profile.name} options`}
769798
/>
770-
)}
771-
{isDefault ? <Badge>Default</Badge> : null}
772-
</span>
773-
<Menu>
774-
<MenuTrigger
775-
render={
776-
<Button
777-
size="icon-sm"
778-
variant="ghost-muted"
779-
disabled={disabled}
780-
aria-label={`${profile.name} options`}
781-
/>
782-
}
799+
}
800+
>
801+
<MoreVertical />
802+
</MenuTrigger>
803+
<MenuPopup align="end" className="min-w-44">
804+
<MenuItem
805+
disabled={isDefault}
806+
onClick={() => updateSettings({ browserDefaultProfileId: profile.id })}
783807
>
784-
<MoreVertical />
785-
</MenuTrigger>
786-
<MenuPopup align="end" className="min-w-44">
808+
Set as default
809+
</MenuItem>
810+
<MenuItem onClick={() => clearProfileData(profile.id, profile.name)}>
811+
Clear cookies and cache
812+
</MenuItem>
813+
{builtIn ? null : (
787814
<MenuItem
788-
disabled={isDefault}
789-
onClick={() => updateSettings({ browserDefaultProfileId: profile.id })}
815+
variant="destructive"
816+
onClick={() => setProfilePendingRemoval(profile)}
790817
>
791-
Set as default
792-
</MenuItem>
793-
<MenuItem onClick={() => clearProfileData(profile.id, profile.name)}>
794-
Clear cookies and cache
818+
Remove profile and data
795819
</MenuItem>
796-
{builtIn ? null : (
797-
<MenuItem variant="destructive" onClick={() => removeProfile(profile.id)}>
798-
Remove profile and data
799-
</MenuItem>
800-
)}
801-
</MenuPopup>
802-
</Menu>
803-
</div>
804-
);
805-
})}
820+
)}
821+
</MenuPopup>
822+
</Menu>
823+
</div>
824+
);
825+
})}
806826
</div>
807827
<AlertDialog
808828
open={profilePendingRemoval !== null}

0 commit comments

Comments
 (0)