Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ed6e8d5
fix: clean up unused migration destinations before exit
HarshMN2345 Sep 15, 2026
a2f05e3
test: preserve existing migration destinations on cancellation
HarshMN2345 Sep 15, 2026
0f4fa4a
test: preserve migration destination when exit is dismissed
HarshMN2345 Sep 15, 2026
3ac1341
test: reuse the destination when reviewing migration settings
HarshMN2345 Sep 15, 2026
b6d10cc
test: release the old destination before creating a replacement
HarshMN2345 Sep 15, 2026
c35280d
test: delete migration projects through their returned region
HarshMN2345 Sep 15, 2026
6297c1f
test: recover from failed migration destination creation
HarshMN2345 Sep 15, 2026
a005ed3
test: keep migration setup open when project cleanup fails
HarshMN2345 Sep 15, 2026
0ca760b
test: treat an already removed migration destination as cancelled
HarshMN2345 Sep 15, 2026
0137b47
test: wait for destination cleanup before closing migration setup
HarshMN2345 Sep 15, 2026
ce2396d
test: clean up projects created while migration exit is pending
HarshMN2345 Sep 15, 2026
2700a17
test: retain destinations after an ambiguous migration response
HarshMN2345 Sep 15, 2026
d63306c
test: finish migrations with the prepared destination and reset setup
HarshMN2345 Sep 15, 2026
58d1154
test: preserve destination data during an active import request
HarshMN2345 Sep 15, 2026
52f7ed4
test: run migration cleanup for confirmed keyboard exits
HarshMN2345 Sep 15, 2026
c5d5c2e
fix: keep migration destination display tied to the prepared project
HarshMN2345 Sep 15, 2026
66569f4
fix: confirm migration cleanup before browser navigation
HarshMN2345 Sep 15, 2026
21e5a2c
test(migrations): exercise exits through the mounted shell navigation…
HarshMN2345 Sep 15, 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
16 changes: 12 additions & 4 deletions src/lib/layout/shell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,19 @@
beforeNavigate((navigation) => {
if (navigation.willUnload) return;
if (!($wizard.show || $wizard.cover)) return;
if (navigation.type === 'popstate') {
if ($wizard.exitHandler) {
navigation.cancel();
}
if (navigation.type !== 'leave') {
wizard.hide();
$wizard.exitHandler(
navigation.type === 'popstate' ? null : (navigation.to?.url.href ?? null)
);
Comment on lines +114 to +118

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.

P1 Back Navigation Is Lost

When the user presses browser Back, this code cancels the navigation and passes null to the exit handler. After cleanup succeeds, the wizard closes, but no destination exists to resume the canceled history traversal. The user therefore remains on the same route and must press Back again.

Knowledge Base Used: Console application shell

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/layout/shell.svelte
Line: 114-118

Comment:
**Back Navigation Is Lost**

When the user presses browser Back, this code cancels the navigation and passes `null` to the exit handler. After cleanup succeeds, the wizard closes, but no destination exists to resume the canceled history traversal. The user therefore remains on the same route and must press Back again.

**Knowledge Base Used:** [Console application shell](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/console/-/docs/console-application-shell.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

return;
} else {
if (navigation.type === 'popstate') {
navigation.cancel();
}
if (navigation.type !== 'leave') {
wizard.hide();
}
}

if (!isInDatabasesRoute(navigation.from.route)) {
Expand Down
60 changes: 43 additions & 17 deletions src/lib/layout/wizard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
columnSize?: 's' | 'm' | 'l';
stickySide?: boolean;
onExit?: () => void;
beforeExit?: () => Promise<boolean>;
}
| {
title?: string;
Expand All @@ -30,6 +31,7 @@
columnSize?: 's' | 'm' | 'l';
stickySide?: boolean;
onExit?: () => void;
beforeExit?: () => Promise<boolean>;
};

export let title: $$Props['title'] = '';
Expand All @@ -42,6 +44,42 @@
export let columnSize: $$Props['columnSize'] = 'm';
export let stickySide: $$Props['stickySide'] = false;
export let onExit: $$Props['onExit'] = undefined;
export let beforeExit: $$Props['beforeExit'] = undefined;

let exiting = false;
let pendingHref: string | null = null;

$: if (!showExitModal && !exiting) pendingHref = null;

function requestExit(href: string | null) {
if (exiting) return;
pendingHref = href;
if (confirmExit) {
showExitModal = true;
} else {
void exit();
}
}

async function exit() {
if (exiting) return;
exiting = true;
const destination = pendingHref;
try {
if (beforeExit && !(await beforeExit())) return;
trackEvent('wizard_exit', { from: 'prompt' });
wizard.hide();
onExit?.();
onExit = null;
if (destination) {
// Navigation URLs already include the application base path.
// eslint-disable-next-line svelte/no-navigation-without-resolve
await goto(destination);
}
} finally {
exiting = false;
}
}

function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
Expand All @@ -60,7 +98,10 @@

const goBack = () => goto(href);

onMount(() => ($isNewWizardStatusOpen = true));
onMount(() => {
$isNewWizardStatusOpen = true;
if (beforeExit) return wizard.setExitHandler(requestExit);
});

onDestroy(() => ($isNewWizardStatusOpen = false));
</script>
Expand Down Expand Up @@ -95,22 +136,7 @@
</Layout.Wizard>

{#if showExitModal}
<WizardExitModal
{href}
bind:show={showExitModal}
on:exit={() => {
trackEvent('wizard_exit', {
from: 'prompt'
});

wizard.hide();
if (onExit) {
onExit();

// clear exit
onExit = null;
}
}}>
<WizardExitModal {href} bind:show={showExitModal} on:exit={exit}>
<slot name="exit">
Are you sure you want to exit from this process? All data will be deleted. This action
is irreversible.
Expand Down
51 changes: 51 additions & 0 deletions src/lib/stores/wizard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { get } from 'svelte/store';
import { wizard } from './wizard';

vi.mock('$lib/actions/analytics', () => ({ trackEvent: vi.fn() }));

describe('wizard exit handler ownership', () => {
beforeEach(() => wizard.hide());

it('unregisters the handler owned by the closing component', () => {
const handler = vi.fn();
const unregister = wizard.setExitHandler(handler);

expect(get(wizard).exitHandler).toBe(handler);
unregister();

expect(get(wizard).exitHandler).toBeNull();
});

it('does not unregister a replacement component handler', () => {
const unregister = wizard.setExitHandler(vi.fn());
const replacement = vi.fn();
wizard.setExitHandler(replacement);

unregister();

expect(get(wizard).exitHandler).toBe(replacement);
});

it('clears the previous handler when another wizard starts', () => {
const unregister = wizard.setExitHandler(vi.fn());

wizard.start(() => ({}));
expect(get(wizard).exitHandler).toBeNull();

const replacement = vi.fn();
wizard.setExitHandler(replacement);
unregister();
expect(get(wizard).exitHandler).toBe(replacement);
});

it('clears navigation interception when the wizard is hidden', () => {
wizard.start(() => ({}));
wizard.setExitHandler(vi.fn());

wizard.hide();

Comment on lines +12 to +47

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.

P2 Tests Mirror Private State

These tests directly inspect the private exitHandler store field and mirror its registration and replacement logic instead of testing observable wizard behavior. The migration suite repeats this problem by invoking get(wizard).exitHandler(...) and asserting private store state. This violates the repository directive to test observable behavior rather than source implementation, so the tests must be rewritten before merging to exercise navigation through a mounted wizard.

Context Used: Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/stores/wizard.test.ts
Line: 12-47

Comment:
**Tests Mirror Private State**

These tests directly inspect the private `exitHandler` store field and mirror its registration and replacement logic instead of testing observable wizard behavior. The migration suite repeats this problem by invoking `get(wizard).exitHandler(...)` and asserting private store state. This violates the repository directive to test observable behavior rather than source implementation, so the tests must be rewritten before merging to exercise navigation through a mounted wizard.

**Context Used:** Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

expect(get(wizard).show).toBe(false);
expect(get(wizard).exitHandler).toBeNull();
});
});
16 changes: 16 additions & 0 deletions src/lib/stores/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type WizardStore = {
cover?: Component;
interceptor?: () => Promise<void>;
finalAction?: () => Promise<void>;
exitHandler?: (href: string | null) => void;
nextDisabled: boolean;
step: number;
interceptorNotificationEnabled: boolean;
Expand All @@ -27,6 +28,7 @@ function createWizardStore() {
nextDisabled: false,
step: 1,
finalAction: null,
exitHandler: null,
props: {}
});

Expand All @@ -49,6 +51,7 @@ function createWizardStore() {
n.cover = null;
n.nextDisabled = false;
n.finalAction = null;
n.exitHandler = null;
n.props = props;
trackEvent('wizard_start');
return n;
Expand All @@ -65,6 +68,18 @@ function createWizardStore() {
return n;
});
},
setExitHandler: (handler: WizardStore['exitHandler']) => {
update((n) => {
n.exitHandler = handler;
return n;
});
return () => {
update((n) => {
if (n.exitHandler === handler) n.exitHandler = null;
return n;
});
};
},
hide: () =>
update((n) => {
n.show = false;
Expand All @@ -76,6 +91,7 @@ function createWizardStore() {
n.cover = null;
n.nextDisabled = false;
n.finalAction = null;
n.exitHandler = null;

return n;
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>Choose migration resources</p>
Loading