Repository navigation
fix(console): after an install into this environment, wait until its app is served before refreshing the app list (objectui#12087) - #12096
Conversation
…app is served before refreshing the app list The runtime serves the pre-install kernel while it rebuilds after a cloud install. The package page refreshed and persisted the app list the moment the install answered, so the cached list was the pre-install one and the installed app never appeared in the installing tab. The page now reads GET /meta/app past the metadata cache on a bound until an app wears the package's manifest id as _packageId, and only then drops the session seed, refreshes the cache and pulses the bus. It shows the deploying state until then, offers to open the app once served, and on expiry says the app has not appeared and offers to check again. Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
…the stale-window case The stale-seed assertion is the claim the case exists for, so it is the one a regression to an in-window refresh should trip first. Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
…age stays out of the eager closure Importing extractItems, a value of MetadataProvider.tsx, from the lazy package page made rolldown park the provider in the page's chunk, which the entry then imported statically: the console build's declared-lazy-views guard refused it. The deploy wait reads GetMetaItemsResponse.items from the SDK client directly, the shape that client declares. Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
|
Contract reviewServed-tier: ① Derived judgmentsDiff read as
② Semver levelFAIL on this section. The changeset Remedy, one file, no code change: set ③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
REWORK — PR objectui#12096 (head
|
…eys gains five members `TranslationKeys` is `typeof en`, exported from the `@object-ui/i18n` entry, so the five `marketplace.install` keys widen a published type. The changeset said no type on the package entries changed; it now says that the en pack and TranslationKeys gain five members, and that nothing on the `@object-ui/app-shell` entry changes. Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Re-render on the patch-round head. The hop ① Derived judgments
② Semver levelConsistent on this head. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
✅ 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
|
Contract reviewServed-tier: Re-render on the landing head. The hop from the PASS head ① Derived judgments
② Semver levelConsistent on this head, and re-read against the code at this head. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #12087
Clause-②: yes
What changed
After a marketplace install into the environment this console renders, the package page now waits until that environment serves the installed package's app. Only then does it refresh and persist the metadata cache.
The wait.
waitForServedApp(new,packages/app-shell/src/console/marketplace/waitForServedApp.ts) readsGET /meta/appthrough the same SDK clientMetadataProvideruses, but past the cache, so nothing it reads is persisted. The first read goes out at once, with no sleep before it. After that it reads everySERVED_APP_POLL_INTERVAL_MS(5 s) untilSERVED_APP_WAIT_CAP_MS(5 min) runs out. Both constants document what they bound: the request rate, and the wait. Neither is a guess at the rebuild duration.The predicate. Some served app has
_packageId === manifest_id._packageIdis the spec's owning package machine id (MetadataProtectionFields) and the ADR-0048 route key thatappRouteSegmentalready reads.PackageManifestSchemadeclares that the manifest id "must match the parentsys_package.manifest_id".Only once served: the page drops the
objectui:metadata:*session seed, runsrefreshMetadata()and sendsemitMetadataRefresh(). Before this change all three ran the moment the install answered. The bus pulse was a second in-window persist, because the provider re-reads every loaded type on it.What the user sees. While waiting, the page shows 「正在部署应用…」 (
marketplace.install.deploying). It shows this in the dialog, and on the page once the dialog is closed, so closing the dialog does not hide the state. Once the app is served, it showsdeployedand offers 「打开 {name}」 (openApp). The button navigates to/apps/followed byappRouteSegment(app), for the first active, non-hidden app of the package. On expiry it showsdeployTimeoutwith "Check again" (checkAgain). That message says the install was recorded, that the app has not appeared, and the possible reasons. It claims no success and refreshes nothing.Not tied to the page's lifetime. The wait is not cancelled on unmount. An operator who goes to 「我的应用」 while the app deploys still gets the refreshed cache when it lands.
Suggested audience bindings (ADR-0090 D5) now mount once the app is served. The panel reads the runtime once, on mount, and before the app is served that read would reach the pre-install kernel.
Installs into another environment are judged by the same facts
installPackageroutes on (installLandsInThisEnvironment):defaultEnvironmentIdequals the picked environment.A cross-environment install shows
marketplace.install.successas before. It no longer refreshes this console's own metadata; the old comment called that refresh "a harmless no-op".Five locale keys are added under
marketplace.installin all ten packs:deploying,deployed,openApp,deployTimeout,checkAgain. Noenvalue changed. That is the reason forClause-②: yes.MetadataProvider.tsxis untouched.Measured first
Each item below names the PM's mechanism assumption it answers.
refreshMetadata('app')calls belong to the install-local and uninstall-local flows. Their producer is objectstackpackages/cloud-connection/src/marketplace-install-local-plugin.ts, read at86da1949. The install awaitsmanifestService.register(manifest)before it answers 200. The uninstall withdraws the package from the running kernel, or reports that it stays until a restart. Neither has a rebuild window, so neither has this defect, and both are unchanged.appsstraight off its entry, with no TTL re-read, so a stale list stays until something callsensureType('app')orrefresh(). That is the persistence the card measured.installation.{id, environment_id, package_id, version}. The detail response carries no app list.manifest_idis the only datum, so the predicate rests on it./meta/applacks the app in both cases./cloud-connection/installationanswersinstalled: truein both.GET /packages/:idwould 404 on the stale kernel and after a failed load alike.requiredPermissionsorrequiresService(filterAppForUser).registerItemstamps_packageIdbut not_packageVersion, so/meta/appcannot tell the old version from the new one.Tests
All runs were at
8810eb080and went through the shared verify lock, so the seconds in the logs are shared-box seconds.waitForServedApp-12087.test.ts(6 cases, virtual clock):floor(cap / interval) + 1reads;MarketplacePackagePage.waitServedApp-12087.test.tsx(3 cases) uses the realMetadataProviderand the real assistant bus. It records every sessionStorage write of theappseed.pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/console/marketplace/ packages/i18n/gaveTest Files 108 passed (108)andTests 1488 passed | 13 skipped (1501).ablation-replace.mjsin wrap mode. The mutation put the pre-fix in-windowrefreshMetadata()+emitMetadataRefresh()back at the start of the wait.d05b45979736to2c6c92d29fea, marker count 1.Tests 2 failed | 1 passed (3). Both failures are the persisted-seed assertion (expected [ { afterInstall: true, …(1) }, …(1) ] to deeply equal []). The control stays green, as predicted.git diff HEADempty.Gates (at
8810eb080)pnpm turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n --concurrency=2gaveTasks: 31 successful, 31 total. Both type-checks were cache misses and ran.tsc -p tsconfig.test.json --listFilesOnlylists both new test files.check:i18n-keys,check:i18n-drift(5 keys added, 0envalues changed) andcheck:i18n-dead-keys(report-only; none of the new keys listed) all exit 0.check:new-line-citations(0 new) andcheck:control-bytesexit 0.check-changeset-presence,changeset:check,check-changeset-claims,check-changeset-overwriteandcheck:pending-changeset-literals.check-vi-mock-specifiers,check-vi-mock-override-shape,check-vi-mock-inherit,check-test-path-roots,check:unreferenced-sources,check:side-effects-array,check:esm-specifiersandcheck:phantom-depsall exit 0.check-type-check-coverageandcheck-lint-coverageexit 0.anypatterns and the unusedcodein this file, the same count as at the base.pnpm turbo run build --filter=@object-ui/console, thencheck:eager-closure, reads 3169.7 KB gzipped against a 3204.6 KB budget, headroom 34.8 KB.check:eager-locale-cataloguesexits 0.de302c73: +125 bytes gzipped in total, with the eager chunk count unchanged.i18n-locale-engrew by 136 bytes. The other chunks moved within plus or minus 7 bytes of hash churn.de302c73and runningvite build.declared-lazy-viewsguard. ImportingextractItems, a value ofMetadataProvider.tsx, from this lazy page made rolldown place the provider in the page's chunk, and the entry imported that chunk statically. The chunk's module list was read with a temporary diagnostic, which was not committed.8810eb080readsGetMetaItemsResponse.items, the SDK's declared shape, instead, and the guard passes.c88aaf17b(review record6098618574):@object-ui/i18nis nowminor, becauseTranslationKeys(typeof en, exported from the@object-ui/i18nentry) gains the fivemarketplace.installmembers.@object-ui/app-shellstayspatch, and nothing declaresmajor. Atc88aaf17b,check-changeset-presence,changeset:check,check-changeset-claims,check-changeset-overwrite,check:pending-changeset-literals,check:control-bytesandcheck:new-line-citationsall exit 0, andpnpm changeset statuslistsminorfor@object-ui/app-shelland@object-ui/i18n. That commit changes only the changeset.check:readme-exportsexits 1 with PREREQUISITE NOT MET: thecliandplugin-aidist directories are not built here. That is NOT MEASURED, and those packages are outside this diff.Acceptance notes
None of these is filed. Each is listed for the seat.
MarketplacePage,doOrgInstall) has the same stale window on its cloud-managed branch. It installs into the current environment and pulsesemitMetadataRefresh()at once. This is a code reading, not reproduced. The file is outside this claim's surface, so it is untouched.waitForServedApptakespkg.manifest_idas it is. Carrier: a follow-up on objectui#12087.hotLoaded: trueeven on its lenient path, where the hot register failed and the package "will load on next restart". The page then showslocalSuccess("should now appear") over a list without the app. This is a code reading of objectstackmarketplace-install-local-plugin.ts. Carrier: none.mainrefreshed whendefaultEnvironmentIdwas null as well as when it equalledselectedEnv. The head waits and refreshes only whendefaultEnvironmentIdequalsselectedEnv: a runtime that names no environment cannot be the control-plane install's target. On the same-origin proxy path, where a cloud base is set, the head still waits and refreshes whateverdefaultEnvironmentIdsays, because that proxy installs into the environment the hostname names.Session:
https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7zGenerated by Claude Code