…on a marketplace-off runtime that mounts install-local (objectui#11627)
On an offline boot (OS_CLOUD_URL=off) the runtime config reports
features.marketplace false and features.installLocal true. Installed Apps
links each local install's Details to MarketplacePackagePage, which returned
the MarketplaceDisabled notice before anything else, so the local re-seed /
purge menu was unreachable and no install-local request was ever issued.
The marketplace-off branch now draws, for an admin whose package is a local
install, the package header and the same local menu above the unchanged
notice. The local install is matched on the ledger entry's packageId (the id
Installed Apps builds the link from); the menu acts on its manifest id. The
catalog fetch, cloud-installation probe, install-to-cloud CTA, readme and
version list stay refused. listLocalInstalls is no longer gated on
features.marketplace, since its answer now has a consumer there.
The objectui#5620 case that pinned "never fires on a marketplace-off runtime,
even for an admin with installLocal on" pinned the branch this removes and is
replaced by the two marketplace-off cases where the answer still has no
consumer (installLocal off; a refused viewer).
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
Fixes #11627
Clause-②: no
On a runtime with no marketplace that still mounts install-local (an offline boot,
OS_CLOUD_URL=off), a local install's Details page now offers its local menu: re-seed sample data, purge sample data, uninstall from this runtime. Everything that needs a marketplace stays refused.What changed
MarketplacePackagePageanswered!marketplaceEnabledwithMarketplaceDisabledbefore anything else, so the local menu further down was unreachable. Now:features.installLocal(the samegetRuntimeConfig()read the page already used, no new loader) and the viewer is an admin, the page draws the package header (manifest id, installed version) and the same local menu, above the unchanged notice. The menu is the same element the catalog view draws, shared rather than copied. It issuesPOST /api/v1/marketplace/install-local/MANIFEST_ID/reseed-sample-dataand.../purge-sample-data, as on a marketplace-on runtime.packageId, the field Installed Apps builds its Details link from. The menu then acts on that entry's manifest id. It matches on that one field, with no alias. For an inline install the two ids are equal; for a package installed from the catalog earlier they differ, and a pin covers that case.listLocalInstallsanswers.listLocalInstallsis no longer gated onfeatures.marketplace, because its answer now has a consumer there. It is still gated onfeatures.installLocaland on the admin verdict.No export, prop, type member or i18n key is added.
MarketplacePackagePagetakes no props, and every string it draws already existed.Re-judged pin
The objectui#5620 case
never fires, even with installLocal on and the viewer an adminpinned exactly the branch this card removes. It is replaced, not respelled, by the two marketplace-off cases where the answer still has no consumer: installLocal off, and a refused viewer. The suite header records why.Evidence
All gate readings below are at
b8dac39(after the oneorigin/mainmerge).Live before / after. Setup: an offline showcase (
OS_CLOUD_URL=off,--fresh) booted from a private objectstack worktree at main08adfead, thenos package install examples/app-crm/dist/objectstack.json -rinto it. The console ran from this branch's worktree. Chromium drove the card's path: Setup, Installed Apps, Details./api/v1/runtime/configservedfeatures.marketplace: falseandfeatures.installLocal: true. Installed Apps listedcom.example.crmv4.0.0, and Details opened/apps/setup/system/marketplace/com.example.crm.git diff HEAD): the notice only, no menu trigger, and no install-local call from the detail page.com.example.crmwith "Installed · v4.0.0" and the "More install options" trigger, above the same notice. The menu items were "Re-seed sample data", "Purge sample data" and "Uninstall from this runtime". The wire showedPOST /api/v1/marketplace/install-local/com.example.crm/reseed-sample-dataandPOST /api/v1/marketplace/install-local/com.example.crm/purge-sample-data. No request went to a catalog path or to/cloud-connection/installation, and no install-to-cloud button was drawn. The server's answers are not this card's to grade (see Acceptance notes).Pins.
MarketplacePackagePage.offlineLocalMenu-11627.test.tsxruns at the wire: the realmarketplaceApihelpers and the realinitRuntimeConfig()over one path-routedfetchstub. Its cases:packageIdand acts on the manifest id, and the manifest id alone does not match.Reverse validation (fix committed first; the base page swapped in under a trap that restores from
HEAD; restore proved by blob hashe06fd029equalsHEADand an emptygit diff HEAD). Predicted before the run: every case that needs the local view or a ledger read goes red. Observed:Tests 7 failed | 1 passed (8). The one green case is "MUST NOT CHANGE without install-local", as predicted.Gates. Every exit code was captured before any pipe.
pnpm exec turbo run build --filter=@object-ui/app-shell^... --concurrency=2: 28/28 successful, exit 0.pnpm --filter @object-ui/app-shell type-check(the script name was echoed; it runstsc --noEmitand thentsconfig.test.json): exit 0. A--listFilesOnlyread of the test project lists all three touched files.pnpm exec vitest runover the 17 test files that nameMarketplacePackagePage(git grep -l MarketplacePackagePageover test files):Test Files 17 passed (17),Tests 357 passed (357).check:new-line-citations,check:control-bytes,check:i18n-keys,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:changeset-claims,check:pending-changeset-literals,check:shell-escape-residue,check:metadata-write-doors,check:handler-key-reads,check:unreferenced-sourcesandcheck:comment-mask-corpusall exited 0. So didscripts/check-changeset-presence.mjs,scripts/check-changeset-no-major.mjs, andcheck-governed-queue-guard.mjs --test(NOT GOVERNED).Declared narrowings. CI runs the full farm.
pnpm exec vitest run packages/app-shell/. Reason: the shared verify lock queued for about 70 minutes behind full app-shell runs of the same size. The run was narrowed to the 17 test files that name the page.eslint .. It linted 3 files (counted from--format json) with 0 errors and 11 warnings, the same 11 that the base page blob gives through--stdin(10no-explicit-any, 1no-unused-vars), so the diff adds none. This narrowing cannot change the verdict on any untouched file. The config extendstseslint.configs.recommendedwith noparserOptions.project, so type-aware linting is off, and no custom rule undereslint-rules/reads the disk. So no file's verdict depends on another file's content.Acceptance notes
422 RESEED_NO_ROWSwith the message "The package declares no seedable records for this runtime." That message is false. Measured on main08adfead: right after a purge, the same call inserted 28 rows, and an immediate second call answered the 422 again. The handler's zero-errors branch reads "the loader skipped every row" as "the package declares none". In the console, "Re-seed sample data" therefore shows a red banner with that sentence whenever the data is present.08adfeadin this run.content/docsdescribes the package page's per-flag behaviour (grep), so none changed.The implementing session is
https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL.Generated by Claude Code