Observation
Found while fixing objectui#7821 (the onManageChanged false-deletion eviction), in the
same component and one callback above it. Deliberately not fixed there: that card is
scoped to the deletion inference, and this is a different swallowed failure with a
different wrong outcome.
fetchFullPackage in packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx
(origin/main 384715bb1, the PackageSwitcher helper that backs openManage):
const fetchFullPackage = React.useCallback(async (id: string) => {
const res = await fetch('/api/v1/packages', {
credentials: 'include',
headers: { Accept: 'application/json' },
cache: 'no-store',
});
const data = (await res.json()) as unknown;
const root = (data as { data?: unknown })?.data ?? data;
const list = (Array.isArray(root) ? root : ((root as { packages?: unknown[] })?.packages ?? [])) as ...;
return list.find((p) => (p?.manifest?.id ?? p?.id) === id) ?? null;
}, []);
res.ok is never read. The endpoint answers an error the way the rest of this platform
does — a JSON envelope with success: false and an error object — so on a 4xx/5xx
that envelope parses cleanly: root is the error object, it is not an array and has
no packages, so list is [] and the function returns null. Nothing throws, so
openManage's catch (which does toast, formatMetadataError) never runs:
setManage(await fetchFullPackage(id)); // null
setManageOpen(true); // …and the sheet opens anyway
Net effect: an author clicking "Package info & settings" during an outage gets the
management sheet opened on a null package — no toast, no error, no explanation —
instead of being told the lookup failed. Same defect family as objectui#7368 and
objectui#7821, third variant: not a lost toast and not an inverted decision, but a
failure laundered into a successful-looking empty result.
Two adjacent observations on the same function, offered as context rather than as
separate claims:
- It reaches
/api/v1/packages with a raw fetch, which AGENTS.md §7 bans in
components in favour of @objectstack/client; the sibling fetchPackages in
./packages-io.ts already goes through the sanctioned path and already owns the
two-shape envelope parsing this function re-implements by hand.
- It is a fourth call to the same endpoint on this surface (the switcher list, the
writability courtesy gate and the namespace lookup are the other three, per
objectui#7368).
Worth deciding
- Check
res.ok (and the success: false envelope) and throw, so openManage's
existing catch reports it on this file's shared studio-package-list sonner id and
the sheet does not open. Cheapest, and reuses the posture that already exists here.
- Do not open the sheet on a
null result regardless of how the null arose — a
guard at the call site rather than at the fetch.
- Retire the hand-rolled fetch in favour of the
packages-io reader plus a
full-record variant, which would make 1 and the two adjacent observations one change.
Not obviously the same call as objectui#7368's (that one deliberately DEGRADES because
the top bar must survive a 503); here the affordance is a modal the author explicitly
asked for, so refusing to open it is a live option.
Re-check
grep -n "fetchFullPackage" -A 14 packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx
Related, not duplicates: objectui#7368 (the switcher's silence, merged), objectui#7821
(the false-deletion eviction, this file, in review), objectui#7373 (where a recovery
redirect should land).
Generated by Claude Code
Observation
Found while fixing objectui#7821 (the
onManageChangedfalse-deletion eviction), in thesame component and one callback above it. Deliberately not fixed there: that card is
scoped to the deletion inference, and this is a different swallowed failure with a
different wrong outcome.
fetchFullPackageinpackages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(
origin/main384715bb1, thePackageSwitcherhelper that backsopenManage):res.okis never read. The endpoint answers an error the way the rest of this platformdoes — a JSON envelope with
success: falseand anerrorobject — so on a 4xx/5xxthat envelope parses cleanly:
rootis the error object, it is not an array and hasno
packages, solistis[]and the function returnsnull. Nothing throws, soopenManage'scatch(which does toast,formatMetadataError) never runs:Net effect: an author clicking "Package info & settings" during an outage gets the
management sheet opened on a
nullpackage — no toast, no error, no explanation —instead of being told the lookup failed. Same defect family as objectui#7368 and
objectui#7821, third variant: not a lost toast and not an inverted decision, but a
failure laundered into a successful-looking empty result.
Two adjacent observations on the same function, offered as context rather than as
separate claims:
/api/v1/packageswith a rawfetch, which AGENTS.md §7 bans incomponents in favour of
@objectstack/client; the siblingfetchPackagesin./packages-io.tsalready goes through the sanctioned path and already owns thetwo-shape envelope parsing this function re-implements by hand.
writability courtesy gate and the namespace lookup are the other three, per
objectui#7368).
Worth deciding
res.ok(and thesuccess: falseenvelope) and throw, soopenManage'sexisting
catchreports it on this file's sharedstudio-package-listsonner id andthe sheet does not open. Cheapest, and reuses the posture that already exists here.
nullresult regardless of how the null arose — aguard at the call site rather than at the fetch.
packages-ioreader plus afull-record variant, which would make 1 and the two adjacent observations one change.
Not obviously the same call as objectui#7368's (that one deliberately DEGRADES because
the top bar must survive a 503); here the affordance is a modal the author explicitly
asked for, so refusing to open it is a live option.
Re-check
Related, not duplicates: objectui#7368 (the switcher's silence, merged), objectui#7821
(the false-deletion eviction, this file, in review), objectui#7373 (where a recovery
redirect should land).
Generated by Claude Code