Repository navigation
fix(app-shell): the marketplace load error names its real cause, and no empty state under it - #11724
Merged
objectstack-fleet[bot] merged 2 commits intoOct 6, 2026
Conversation
…no empty state under it
A 403 the server answered in text/plain ("Host not in allowlist: ...") was
shown as the bare status text "Forbidden", followed by the fixed "check that
the runtime is online" hint and then "No apps have been approved for the
marketplace yet."
- call() reads the body once as text and, when it is not JSON and is
text/plain, uses that text as the failure's message.
- MarketplacePage keeps the failure typed: the online hint is drawn only
when no server answered, or the answer was 502/503/504.
- The empty state is not drawn while a load error is shown, and a catalog
failure no longer discards the side loads (org packages, installed count).
Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju
Co-authored-by: Claude <noreply@anthropic.com>
…ell patch Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Oct 6, 2026
This was referenced Oct 6, 2026
objectstack-fleet
Bot
deleted the
claude/issue-11688-marketplace-load-error-cause
branch
October 6, 2026 15:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11688
Clause-②: no
What was wrong (measured on
mainatc910630)Browse Marketplace showed "Failed to load marketplace · Forbidden · This runtime serves the marketplace catalog itself. Check that the runtime is online.", and under it "No apps have been approved for the marketplace yet." Traced hop by hop:
MarketplaceProxyPlugin, objectstackpackages/cloud-connection) forwards the upstream status,content-typeand body unchanged (passthroughResponse/consumeAndMaybeCache).fetchof the catalog URL oncloud.objectos.aireturns403 Forbidden,content-type: text/plain, bodyHost not in allowlist: cloud.objectos.ai. Add this host to your network egress settings to allow access.(the sweep's exact text).call()inmarketplaceApi.tsparsed the body withres.json(), which throws on plain text, soreadApiErrorfell back tores.statusText: "Forbidden".MarketplacePagedrew one of the two "is it online" hints under every failure, and its empty-state branch keyed on the item count alone.The dispatch's mechanism assumption 1 holds; the body is
text/plain, not a JSON{ error }.What changed
call()(the browse requests: the catalog list and the package detail) reads the body once as text and parses JSON from it. When the response failed, the body is not JSON andcontent-typeistext/plain, that text is the failure's message. JSON envelopes read exactly as before throughreadApiError; any other body that is not JSON (an HTML error page) still shows the status text. A failure a server answered still carries itsstatus; a request no server answered rejects with whatfetchthrew, which has none.MarketplacePagekeeps the failure typed (LoadFailure: the message, and whether the online hint applies). The hint, which still names the control plane the server reported (the objectui#5504 rule, unchanged), is drawn only when no server answered (nostatus) or the answer was 502, 503 or 504. The server's text is a React text child, neverdangerouslySetInnerHTML.main,Promise.allrejected and the "Your organization" section and the installed count were never set.No locale key was added or moved: the server's text plus the two existing hints cover every case, so
Clause-②: nostands.Where this departs from the dispatch's suggested route, and why
502MARKETPLACE_PROXY_FAILEDenvelope (message "fetch failed"). A kind-only rule would drop the hint that names the plane in exactly the case objectui#5504 wrote it for. So 502, 503 and 504 keep the hint, and every other answered status shows the server's text alone. The card's Done-when allows "a hint chosen by status".failedHintConfiguredwith a 403 would draw again the wrong-cause line this card retires.status, not from a new error class. The page tests mock../marketplaceApiwith factories that list specific exports. A runtime import of a class forinstanceofwould throw inside the catch path of every one of them.call()already stampedstatuson every answered failure, so the discriminant is whatfetchyielded, with no message matching.Tests
All at
68c0d13, the final head, unless marked.68c0d13adds only the changeset to9f2ed7f, so no TypeScript program or lint input moved between the two.pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build: exit 0, 29 projects.pnpm exec vitest run --maxWorkers=2 packages/app-shell/:Test Files 1032 passed | 1 skipped (1033),Tests 10173 passed | 9 skipped (10182), lockVERDICT command-exit 0.MarketplacePage.loadFailureCause-11688.test.tsx, at9f2ed7f): 8/8 passed. The cases: the plain-text 403 allowlist refusal under both config shapes, a JSON envelope refusal, an HTML error page, a network failure, the runtime proxy's 502, the empty-catalog control, and the partial failure.pnpm --filter @object-ui/app-shell type-check(at9f2ed7f): exit 0. The new test is intsconfig.test.json's program (--listFiles: 1 hit).pnpm --filter @object-ui/app-shell lint(at9f2ed7f): exit 0, 0 errors. Warnings per touched file, BASE then HEAD:MarketplacePage.tsx3 then 2 (oneanyremoved),marketplaceApi.ts36 then 36, the new test 0.check:new-line-citations(0 new citations),check:control-bytes,check:changeset-claims,check:pending-changeset-literals,changeset:check(fixed group, nomajor),scripts/check-changeset-presence.mjs,check:i18n-keys,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots, andscripts/check-governed-queue-guard.mjs --testover the four paths (NOT GOVERNED).Ablation (one leg: the failure-kind split reverted)
Run at
9f2ed7f, with the fix committed, through objectstack'sscripts/ablation-replace.mjsin wrap mode (its own restore trap), plus a shelltrapthat restores fromHEAD:showOnlineHint: status === undefined || UNREACHABLE_STATUSES.has(status),becameshowOnlineHint: true,, which draws the hint under every failure, as onmain. On disk: anchor 1 to 0, replacement 0 to 1, blob9a231e78todf420a1c.Tests 3 failed | 5 passed (8); all three fail atexpect(screen.queryByTestId('marketplace-load-hint')).toBeNull().HEAD(9a231e78), andgit diff HEADis empty (0 bytes).Acceptance notes (observed, not changed here)
failedHintSameOriginsays "This runtime serves the marketplace catalog itself", keyed on runtime-configcloudUrlbeing empty. objectstack's ownisControlPlaneDeclinedheader says the CLI passescontrolPlaneUrl: ''on both the cloud-connected and the air-gapped arm, whileresolveCloudUrlpoints the marketplace proxy athttps://cloud.objectos.aiby default. The sweep's runtime was exactly that shape. This PR stops drawing the hint under a 403; it still appears there for a network failure or a 502/503/504. Reported to the seat as a finding, with no edit here.installLocal,installPackage,uninstallLocal, the local sample-data actions andreseedSampleData/purgeSampleDatainmarketplaceApi.tsstill parse JSON only, so atext/plainrefusal there still shows the status text. Carrier: none.text/plainrefusal as its own answer, not in the{ success: false, error: { code, message } }envelope its own failures use. Carrier: none.MarketplacePage.disabledState.test.tsxmodel "control plane merely down" as a rejection with nostatus(CATALOG_404). Under this PR that is the no-response leg, so they stay green unchanged; the real 502 shape is pinned in the new file. They are not renamed here.Changeset
.changeset/11688-marketplace-load-error-cause.md:@object-ui/app-shellpatch(a rendered-behaviour fix in a published package; no key, no export).Generated by Claude Code