Skip to content

Commit e896089

Browse files
committed
fix(app-shell): keep the declared home when no package is left after a delete
Narrows the previous commit to the list[0] arm. With no package left, the eviction still lands on useHomePath()'s declared home, the recovery exit the home-path source scan pins for this site; only the "a sibling survives" case moves to the Studio landing. The two "nothing left" deletion pins are back to their original text and stay as controls. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
1 parent e8f3a89 commit e896089

3 files changed

Lines changed: 36 additions & 35 deletions

File tree

‎.changeset/11784-package-delete-confirm.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ also drops the object tables.
1313

1414
After deleting the package that is open in Studio, Studio now returns to its landing (`/studio`),
1515
where the next package can be picked or a new one created. It used to open whichever package the
16-
list started with, or, with no package left, leave Studio for the home page. The console's Packages
17-
page is unchanged: a delete there reloads the list in place.
16+
list started with. With no package left at all it still goes to the home page, as before. The
17+
console's Packages page is unchanged: a delete there reloads the list in place.
1818

1919
Discard changes (N) used to discard every pending draft at once. It now asks first, naming the count.
2020

‎packages/app-shell/src/views/studio-design/StudioDesignSurface.packageDeletionInference.test.tsx‎

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@
3333
* degrade into "never navigate", which strands the author on a package
3434
* that no longer exists.
3535
*
36-
* Where a REAL deletion lands changed with objectui#11784 (triage comment
37-
* 6041966855: "return to `/studio` after a delete"): the Studio landing,
38-
* whatever is left. It used to open `list[0]` — whichever package the list
39-
* started with, so a delete read as "Studio moved me into another app" — or,
40-
* with nothing left, the home path. The three REAL-deletion cases below say
41-
* the new destination; the FAILED-refresh cases and the "package still there"
42-
* case are unchanged, as controls.
36+
* Where a REAL deletion lands while other packages remain changed with
37+
* objectui#11784 (triage comment 6041966855: "return to `/studio` after a
38+
* delete"): the Studio landing. It used to open `list[0]`, whichever package
39+
* the list started with, so a delete read as "Studio moved me into another
40+
* app". The "sibling survives" case says the new destination; every other case
41+
* here is unchanged and stays as a control, the two "nothing left" cases
42+
* included (that arm is still objectui#7373's declared home).
4343
*
4444
* ⛔ The `.catch` is deliberately still a `.catch` (one 503 must not take the
4545
* Studio down — objectui#7368's ruling) and there is deliberately still no
@@ -301,28 +301,24 @@ describe('Studio package lifecycle — a failed refresh is not a deletion (#7821
301301
expect(await screen.findByText('Packages (apps)')).toBeInTheDocument();
302302
});
303303

304-
it('REAL deletion, nothing left: returns to the Studio landing, not /home (objectui#11784)', async () => {
304+
it('REAL deletion, nothing left: still navigates to /home (behaviour unchanged)', async () => {
305305
const lifecycle = await openLifecycleSheet();
306306

307307
// The list came back — successfully — and the package is gone from it.
308308
fetchPackagesMock.mockResolvedValue([]);
309309
fireEvent.click(lifecycle);
310310

311-
// Triage 6041966855: "return to `/studio` after a delete". The landing
312-
// needs no package to open; it offers "New package".
313-
expect(await screen.findByTestId('studio-landing')).toBeInTheDocument();
314-
expect(where()).toBe('/studio');
315-
expect(screen.queryByTestId('home-page')).not.toBeInTheDocument();
311+
expect(await screen.findByTestId('home-page')).toBeInTheDocument();
312+
expect(where()).toBe('/home');
316313
// A successful refresh is not a failure: nothing was reported.
317314
expect(toastError).not.toHaveBeenCalled();
318315
});
319316

320-
it('REAL deletion, nothing left, a DECLARED landing: still the Studio landing, never the launcher (objectui#11784, objectui#7373)', async () => {
317+
it('REAL deletion, nothing left, a DECLARED landing: evicts to it, not to the launcher (objectui#7373)', async () => {
321318
// Same eviction as the case above — the only difference is that this
322-
// deployment declares where home is. objectui#7373 kept this eviction out
323-
// of the environment launcher by following the declared landing; since
324-
// objectui#11784 (triage 6041966855: "return to `/studio` after a delete")
325-
// it does not leave Studio at all, so neither home is consulted.
319+
// deployment declares where home is. Pre-#7373 the destination was the
320+
// `/home` literal either way, which on a control plane drops the author
321+
// into the environment launcher.
326322
const lifecycle = await openLifecycleSheet([row(PACKAGE_ID)], [
327323
{ name: 'cloud_control', label: 'Cloud', isDefault: true },
328324
{ name: 'account', label: 'Account' },
@@ -331,25 +327,22 @@ describe('Studio package lifecycle — a failed refresh is not a deletion (#7821
331327
fetchPackagesMock.mockResolvedValue([]);
332328
fireEvent.click(lifecycle);
333329

334-
expect(await screen.findByTestId('studio-landing')).toBeInTheDocument();
335-
expect(where()).toBe('/studio');
336-
expect(screen.queryByTestId('declared-landing')).not.toBeInTheDocument();
337-
expect(screen.queryByTestId('home-page')).not.toBeInTheDocument();
330+
expect(await screen.findByTestId('declared-landing')).toBeInTheDocument();
331+
expect(where()).toBe('/apps/cloud_control');
338332
expect(toastError).not.toHaveBeenCalled();
339333
});
340334

341335
it('REAL deletion, a sibling survives: returns to the Studio landing, not into the sibling (objectui#11784)', async () => {
342336
// The card's measured complaint: after the delete, Studio landed on
343-
// another package (showcase) — `list[0]` — instead of the Studio landing.
344-
// Triage 6041966855: "return to `/studio` after a delete".
337+
// another package (showcase), the list's first entry, instead of the
338+
// Studio landing. Triage 6041966855: "return to `/studio` after a delete".
345339
const lifecycle = await openLifecycleSheet([row(PACKAGE_ID), row(SIBLING_ID)]);
346340

347341
fetchPackagesMock.mockResolvedValue([row(SIBLING_ID)]);
348342
fireEvent.click(lifecycle);
349343

350344
expect(await screen.findByTestId('studio-landing')).toBeInTheDocument();
351345
expect(where()).toBe('/studio');
352-
expect(where()).not.toBe(`/studio/${SIBLING_ID}/interfaces`);
353346
expect(screen.queryByTestId('home-page')).not.toBeInTheDocument();
354347
});
355348

‎packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ function PackageSwitcher({
431431
}): React.ReactElement {
432432
const navigate = useNavigate();
433433
const locale = useMetadataLocale();
434+
// objectui#7373 — where the deleted-package eviction below lands when no
435+
// other package is left to open: the DECLARED landing, the launcher only when
436+
// the deployment declares none.
437+
const homePath = useHomePath();
434438
const [open, setOpen] = React.useState(false);
435439
const [pkgs, setPkgs] = React.useState<PkgEntry[] | null>(null);
436440
/**
@@ -658,7 +662,8 @@ function PackageSwitcher({
658662

659663
// A lifecycle action ran in the sheet — refresh the list AND the managed
660664
// snapshot (so an edit shows immediately). If the managed package was the one
661-
// we're editing and it's now gone (deleted), return to the Studio landing.
665+
// we're editing and it's now gone (deleted), return to the Studio landing
666+
// (home, when no package is left).
662667
const onManageChanged = React.useCallback(async () => {
663668
/**
664669
* `null` means "the refresh did not tell us anything", which is NOT the
@@ -699,12 +704,15 @@ function PackageSwitcher({
699704
// either way: the author stays put, and the managed snapshot below is
700705
// still refreshed (that call reports its own outcome).
701706
if (list !== null && !list.some((p) => p.id === managedId)) {
702-
// Deleted — only navigate away if it was the package we're editing, and
703-
// then to the Studio landing (objectui#11784), where the author picks the
704-
// next package or creates one. This used to open `list[0]` — whichever
705-
// package the list happened to start with, so a delete read as "Studio
706-
// moved me into another app" — or, with nothing left, the home path.
707-
if (managedId === packageId) navigate('/studio');
707+
// Deleted — only navigate away if it was the package we're editing.
708+
// While other packages remain, that is the Studio landing
709+
// (objectui#11784), where the author picks the next one or creates one;
710+
// this used to open `list[0]`, whichever package the list started with,
711+
// so a delete read as "Studio moved me into another app". With nothing
712+
// left it is still the declared home (objectui#7373).
713+
if (managedId === packageId) {
714+
navigate(list.length > 0 ? '/studio' : homePath);
715+
}
708716
return;
709717
}
710718
/**
@@ -782,7 +790,7 @@ function PackageSwitcher({
782790
);
783791
setManageOpen(false);
784792
}
785-
}, [manage, packageId, navigate, fetchFullPackage, locale]);
793+
}, [manage, packageId, navigate, fetchFullPackage, locale, homePath]);
786794

787795
return (
788796
// Radix Popover (portaled to <body>) — the top bar is `overflow-x-auto`,

0 commit comments

Comments
 (0)