Repository navigation
fix(console,i18n): install answers say what the runtime served (objectui#12098) - #12129
Conversation
…2098) An install-local answered `hotLoaded: false` (objectstack#22695) now reads "installed, loads when the runtime next restarts" on the package page and the catalog strip, and refreshes no app list. The served-app wait now also matches the version the install answer names against `_packageVersion` (objectstack#22689), so an upgrade keeps waiting while the stale kernel serves the old version; an app with no `_packageVersion` counts on its id. New locale key `marketplace.install.localLoadsAtRestart` in all ten packs. Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
… case asserts the waiting state (objectui#12098) Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
…objectui#12098) Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD Co-authored-by: Claude <noreply@anthropic.com>
…ame (objectui#12098) Claude-Session: https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsD 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: Inputs: card objectui#12098 (its body and its three comments: the unlock scan 6102113934, the claim 6106224958, the os-dev-report 6106762868); PR objectui#12129 (its body, its 19-file list, the net diff against ① Derived judgments
② Semver levelChangeset Clause-②: ③ Boundary flagsDev deviations (os-dev-report 6106762868), each answered:
Out-of-scope findings (carrier none; noted in the PR's Acceptance notes), each answered:
open_questions: none filed; none owed. Check-runs on the head, read at 2026-10-11T07:48:47Z: 31 completed with conclusion success (among them Lint, Build and E2E, Bundle Analysis, Changeset Declaration, Changeset Bump Policy, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report, Governed Surface Queue Guard, Line Citation Gate, Inert vi.mock Specifier Check, Control Byte Scan, Test (dist pins), Test (shard 4/8), Test (shard 8/8)); 3 completed with conclusion skipped (the coverage matrix placeholder, Test (coverage), dependabot); 8 still in progress with no conclusion yet (Type Check, Test shards 1, 2, 3, 5, 6 and 7 of 8, Spec Main Shape Gate). No failure at that read. The seat's landing check ② (every check green) was therefore not yet met at that read; this record is the landing check ① record and does not stand in for ②. Implemented-by: VERDICT: PASS |
Fixes #12098
Clause-②: yes
What changes
Two readers of an install answer now say what the runtime served.
hotLoaded: false. The runtime installed the package, but its running kernel could not register it. This is the lenient path for a cloud-fetched manifest (install-local: a cloud install whose hot-register fails answers 200 withhotLoaded: true(the lenient path), so a console says the app "should now appear" over a kernel that does not have it objectstack#22695). The package loads at the runtime's next restart.doInstallLocal) used to showmarketplace.install.localSuccess("should now appear in the app switcher") and refresh the app list. It now shows the newmarketplace.install.localLoadsAtRestart: installed, loads when the runtime next restarts, not in the app switcher until then. It does not runrefreshMetadata('app')oremitMetadataRefresh().hotLoaded: truekeeps today's wording and refresh.waitForServedApptakes aversionoption. Its type isstring | undefinedand it is required, so every caller has to decide what to pass. An app that wears_packageVersioncounts only at that version. An app that wears none comes from a runtime that predates registry: an app registered throughregisterItemis served without the declared_packageVersion(the artifact loader path stamps it), so a console cannot tell an upgrade's old app from its new one objectstack#22689, and it counts on its_packageIdalone, as today.installation.version). An answer that names none waits on the id alone. "Check again" waits for the same version.6102113934).One new locale key,
marketplace.install.localLoadsAtRestart, is in all ten packs with a real translation. No spec type is imported.Premises, measured on objectui
main29b99490and objectstackmaine84aeb36(read throughgh apicontents)doInstallLocalshowedlocalSuccesson any200, then ranrefreshMetadata('app')andemitMetadataRefresh(). It never readhotLoaded.LocalInstallResultalready typeshotLoaded: boolean, somarketplaceApi.tsis unchanged.isAppOfmatched_packageIdalone._packageVersionis declared beside_packageIdinpackages/spec/src/kernel/metadata-protection.zod.ts("Owning package version."). objectstack PR #22724 (b62260cb) stamps it on every app the registry path registers, at the top level of each item thatGET /meta/appserves. That is the list the wait already polls.installResp.installation.version(the answer),data.package.latest_version.version(the catalog), anddata.versions[0].version. The wait uses the answer's version only. A guessed catalog version that was not the one installed would turn a good install into a five-minute timeout. With no version, the worst case is today's early refresh.falsenothing of the package is served before the restart. So no wait runs: the page sets the message and returns.packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts.marketplace-install-local-plugin.tsanswershotLoaded: hotLoadError === undefinedand addshotLoadErroronly besidefalse. The field and its type are unchanged.Where it lands, and one addition beyond the claim's wording
waitForServedApp.ts: the predicate and its docblock.MarketplacePackagePage.tsx: thehotLoadedreader, and the version carried into the wait and "Check again".MarketplacePage.tsx: the catalog'swaitForServedAppcall passes the answer's version. The claim names this call.hotLoadedtoo.git grep "installLocal("outside tests finds two readers of the install-local answer: the package page and this branch.versionId: 'latest'. That is a cloud-fetched manifest, the lenient path's own case.false. The file is on the claim's surface. The fix is the same defect, the same key and the same shape, and adds no new gate family.de-quote-pairing-3876.test.tsmoves from 76 to 77: the newdevalue quotes the name („NAME“), one matched pair.content/docsandpackages/app-shell/README.mdwere searched for the install wording,install-local,hotLoadedand_packageId, and the only hits were unrelated. No doc edit.Tests
waitForServedApp-12098.test.ts(unit, virtual clock):served: false;_packageVersionis accepted on the id at the first read.MarketplacePackagePage.servedTruth-12098.test.tsx(the realMetadataProvider, the real bus, and the realwaitForServedAppon a virtual clock):200withhotLoaded: falseshowslocalLoadsAtRestart, notlocalSuccess. After the install there are 0GET /meta/appreads, 0 pulses, 0appseed writes and no wait. CONTROL:hotLoaded: truekeepslocalSuccess, reads the list and sends one pulse.version: '1.1.0'. While 1.0.0 is served there is no pulse, no seed write and the status stays "Deploying". At the landing interval there is one pulse, and every seed write after the install carries the 1.1.0 app. CONTROL: an app with no_packageVersionis accepted on the first read with no sleep.MarketplacePage.orgInstallWaitServed-12097.test.tsx:'1.0.0');hotLoaded: true. It lacked the field, which the declared type requires;hotLoaded: falsebranch: the restart wording, no pulse, no seed write and no wait.waitForServedApp-12087.test.ts: its six calls passversion: undefined, the id-only reading those cases pin.Runs, all from the repo root under the shared verify lock:
vitest run packages/app-shell/src/console/marketplace/: 29 files and 183 tests passed, atb1db713. The only later commit,35e1bb0, touches onlypackages/i18n/src/__tests__/de-quote-pairing-3876.test.ts.vitest run packages/i18n/: 82 files passed, 1316 tests passed and 13 skipped, at35e1bb0.pnpm --filter @object-ui/app-shell type-check(it echoestsc --noEmit && tsc -p tsconfig.test.json): exit 0 atb1db713, after the dependency closure was built.pnpm --filter @object-ui/i18n type-check: exit 0 at35e1bb0.Reverse verification
Run at
e69d49b. Each leg went throughscripts/ablation-replace.mjs: the anchor must hit, the mutation is proven on disk by count and blob, and the restore is proven as blob equals HEAD withgit diff HEADempty. The tests resolve the sources by relative import, so nodist/sits in the path.isAppOfset toreturn true;. Predicted 3 red, observed 3 red: the two unit upgrade cases and the page's upgrade case. Both CONTROLs stayed green.if (!result.hotLoaded)set toif (false). 1 red: thehotLoaded: falsecase.if (true). 1 red: the cataloghotLoaded: falsecase.marketplace.org.installed. 1 red: the same case.Gates
Gates run at
35e1bb0, the final head, each exit 0 unless stated:pnpm check:i18n-keys,check:i18n-drift(1 key added, 0 en values changed),check:i18n-dead-keys,check:i18n-designer-parity.node scripts/check-changeset-presence.mjs,check:changeset-claims,check:pending-changeset-literals.check:new-line-citations(0 new),check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:phantom-deps.check:self-import,check:esm-specifiers,check:unreferenced-sources,check:spec-symbols,check:side-effects-array,check:unused-deps,check:doc-example-readers.column-identity.ratchet.test.tsandone-authority-per-exported-name-6273.test.ts(2 files, 18 tests); the root suitevitest run scripts/__tests__/(179 files passed, 2 skipped; 5452 tests passed).apps/consolebuild at35e1bb0:check:eager-closurepassed at 3171.7 KB gzipped against a 3204.6 KB budget;check:eager-locale-cataloguespassed:enis the only resident catalogue;waitForServedApp,MarketplacePackagePage,MarketplacePage) sits in lazy chunks, none of them among the 290 eager files;enentry alone: 162 raw bytes, 37 gzip bytes. This was measured on the head chunk with and without the entry. No base build was made, so it is not a base-to-head bundle diff.eslint --no-inline-config --format jsonover the branch's 18 changed.ts/.tsxfiles gives 0 errors. The 13 warnings are the base counts unchanged: 11 inMarketplacePackagePage.tsxand 2 inMarketplacePage.tsx, measured on the base blobs.eslint.config.jssets noparserOptions.projectorprojectService, so this diff cannot move a verdict on an untouched file. Repo-widepnpm lintis left to CI.Acceptance notes
hotLoadError, which is served besidefalse, is not shown. The card asks for the restart wording only. No carrier.installation.versionis the manifest version the runtime stamps as_packageVersion. The cloud repo is not readable from this seat. If the two ever differed, the wait would end in the timeout state with "Check again", not in a refresh of a wrong list.hotLoaded: falselocal result, the suggested-bindings panel still mounts, as before. Whether the runtime records suggestions for a package its kernel did not register was not measured.Session:
https://claude.ai/code/session_01TYgwmFK1q4KJ6Qq2WRLzsDGenerated by Claude Code