Skip to content

fix(console): the catalog's org install waits until its app is served before refreshing (objectui#12097) - #12115

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12097-org-install-wait-served
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12097-org-install-wait-served

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12097
Clause-②: yes

What changed

On a cloud-managed environment, the catalog page's org install (doOrgInstall in packages/app-shell/src/console/marketplace/MarketplacePage.tsx) sent emitMetadataRefresh() the moment installPackage answered. That answer lands inside the runtime's stale-while-rebuild window, so every mounted MetadataProvider re-read and persisted the pre-install app list. This is the package-page defect from objectui#12087, on the "Your organization" Install button.

  • The wait. The cloud branch now calls the one waitForServedApp from objectui#12096, unchanged, with the row's manifest_id. It sends the pulse only once an app whose _packageId is that manifest id is served. There is no second copy of the wait: the interval and the bound are the module's defaults (SERVED_APP_POLL_INTERVAL_MS, SERVED_APP_WAIT_CAP_MS). As on the package page, the wait is not awaited and not cancelled on unmount.
  • What the strip says. A status box at the top of the strip, above the package cards, carries the installed package's name and the package page's three states in the package page's words: marketplace.install.deploying while it waits; marketplace.install.deployed plus marketplace.install.openApp once the app is served; marketplace.install.deployTimeout plus marketplace.install.checkAgain when the wait expires. The cloud branch no longer says marketplace.org.installed ("Installed NAME") before the app is served, and on expiry it refreshes nothing.
  • One status for the whole strip. A later install's status replaces an earlier one's. An earlier wait that settles late never overwrites the later status, but its pulse still goes out, because its app is served.
  • The installLocal branch is unchanged. The runtime hot-registers the package before it answers, so the strip says "Installed NAME" and pulses at once, as before. The test pins this as its CONTROL.

Measured before building (the dispatch's mechanism assumptions)

  1. On main, the cloud branch refreshes at once: confirmed. The new pin fails against main's MarketplacePage.tsx (base 5330afd1f). The bus carried one pulse at virtual t=0, and at that point the runtime was not serving the app yet:
    • AssertionError: expected [ { at: +0, served: false } ] to deeply equal []
    • Tests 2 failed | 1 passed (3). The CONTROL is green on main.
  2. waitForServedApp can be called unchanged with the row's manifest_id: confirmed at the producer.
    • The "Your organization" rows come from the runtime proxy GET /api/v1/cloud-connection/org-packages in objectstack packages/cloud-connection/src/cloud-connection-plugin.ts. That proxy passes the control plane's items through unchanged.
    • The control plane builds each row in cloud packages/service-cloud/src/routes/package-publish.ts (GET /cloud/org-packages) as manifest_id: p.manifest_id from sys_package. That field is declared required: true and readonly: true in cloud packages/service-tenant/src/objects/sys-package.object.ts.
    • OrgPackageSummary.manifest_id is already a required string. No fallback was added. The installLocal branch's pkg.manifest_id || pkg.id is untouched.
  3. The package page's copy is reusable, so no locale key is added. The five marketplace.install keys from objectui#12096 say exactly what this install's states mean. The one thing the catalog adds is which package the status is about, because the strip lists several packages and the status sits above them all. That is supplied as the row's display name, drawn as the status box's title (the same raw display_name the org card already renders), not by a second, NAME-carrying copy of each sentence in ten packs. The meaning is shared and the copy is shared, so the two pages cannot drift. @object-ui/i18n does not move and the changeset declares @object-ui/app-shell patch only.

Route versus the suggested one

The dispatch suggested mirroring the package page's "drop the seed, refreshMetadata(), pulse". This PR keeps the catalog page's own post-install action, the pulse, and moves it behind the wait. This page did not drop the seed or call refreshMetadata() before, and the pulse already makes every mounted provider refresh and re-persist, this page's own included. Adding those two would be a second behaviour change beyond the card's scope, which is "Before the pulse, the org install waits on waitForServedApp". The test asserts on the seed the real provider writes: after the install, no app seed write ever lacks the installed app.

Clause-②

  • Exports added: none. The @object-ui/app-shell entry's exports, props and types are unchanged, and MarketplacePage keeps its signature.
  • Locale keys added: none, in all ten packs. The page reads five existing marketplace.install keys (deploying, deployed, deployTimeout, openApp, checkAgain).
  • Clause-②: yes stays as the claim declared it, for the seat's contract review to record.

Evidence (head b32312a)

  • Test on the virtual clock. MarketplacePage.orgInstallWaitServed-12097.test.tsx runs the real waitForServedApp at its real interval and bound. Only now and sleep are swapped, and the test opens each sleep one interval at a time. It mounts the real MetadataProvider and the real assistant bus over a runtime that serves the pre-install list for 60 s of virtual time after the install.
    • Stale window: after each of the eleven stale intervals there is no pulse and no pre-install seed write. At 60 s there is exactly one pulse, with the app served, plus "Open Acme CRM".
    • Expiry: after 60 intervals the clock reads exactly SERVED_APP_WAIT_CAP_MS. The status reads deployTimeout, with no pulse, no success and no seed write. "Check again" finds the app on its first read.
    • CONTROL: installLocal pulses at once, starts no wait and says "Installed Acme CRM".
  • Ablation (fix committed, then mutated with automatic restore).
    • Made with ablation-replace.mjs, which replaced the wait call with emitMetadataRefresh();. Anchor count went 1 to 0, and the blob went b3073eed036d to 244d714449af.
    • Result: Tests 2 failed | 1 passed (3). The two waits are red and the CONTROL is green.
    • Restored: the blob equals HEAD and git diff HEAD is empty.
  • Gates.
    • pnpm --filter @object-ui/app-shell type-check exit 0. The output echoes type-check, and --listFilesOnly confirms that tsconfig.test.json covers the new test file.
    • pnpm exec vitest run packages/app-shell/src/console/marketplace/ packages/i18n/src/__tests__/marketplace-preview-namespace-3546.test.tsx: Test Files 28 passed (28), Tests 222 passed (222). The i18n file reads this page's source.
    • Every check:* gate the dispatch named exited 0, plus check-changeset-fixed, check-changeset-overwrite, check:unreferenced-sources and check-governed-queue-guard --test (NOT GOVERNED).
  • Eager closure. Built with vite build for base and head, on the same tree except MarketplacePage.tsx.
    • Base and head both measure 3170.2 KB gzipped across 290 of 2474 chunks. The delta is -1 byte, and MarketplacePage is in no eager chunk.
    • check:eager-closure and check:eager-locale-catalogues both exit 0.
  • Narrowed lint.
    • eslint over the 2 touched source files (counted from --format json): 0 errors and 2 warnings. Both warnings are pre-existing, and the base file shows the same two rules.
    • The population and the invariance come from eslint's own --print-config for both files: parserOptions is empty, so type-aware linting is off and this diff cannot change the verdict on any untouched file.
    • Repo-wide pnpm lint belongs to CI.

Acceptance notes

Session: https://claude.ai/code/session_01AswpQDLCKiZos2jCXknwKz (dispatched os-dev, domain:ui seat 1).


Generated by Claude Code

… before refreshing (objectui#12097)

On a cloud-managed environment, doOrgInstall pulsed emitMetadataRefresh the
moment installPackage answered, inside the runtime's stale-while-rebuild
window, so every mounted MetadataProvider re-read and persisted the
pre-install app list. The cloud branch now runs waitForServedApp
(objectui#12087) on the row's manifest_id and pulses only once the app is
served; the strip shows the package page's deploying, served and timeout
states under the package's name. The installLocal branch is unchanged.

Claude-Session: https://claude.ai/code/session_01AswpQDLCKiZos2jCXknwKz
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3170.2 KB 3204.6 KB
Main entry chunk (gzip) 74.1 KB 350 KB
Entry file index-dyqcxel4.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 19.75KB 7.29KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.55KB 68.24KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 14.32KB 5.17KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.66KB 15.64KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.59KB 46.00KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 120.09KB 30.25KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b32312a1ec335b54206c47af93f249ef45abcf5b
Local-runs: none

Inputs read: card #12097 (body and all four comments: triage 6099367789, unlock scan 6101786288, claim 6104559670, os-dev-report 6104795140), PR #12115 (body, its 3-file list, and the net diff against main at the head), and the 42 check-runs on the head. Read-only: nothing built, run or re-run.

① Derived judgments

Every accept-set or public-surface change the diff implies, each judged right or wrong:

  1. @object-ui/app-shell entry: no public-surface change. Right. No export is added or removed; MarketplacePage keeps its no-argument signature; OrgDeployState is a module-local type, not exported. The two hooks the page now calls, useAdapter from @object-ui/react and useObjectLabel from @object-ui/i18n, are the same two the package page (MarketplacePackagePage) already calls; appRouteSegment, filterActiveApps and resolveKeyedI18nLabel are existing exports of the app-shell utils barrel.
  2. Locale accept-set: no key added in any pack. Right. The page reads five keys that exist in the head's en pack under marketplace.install (deploying, deployed, openApp with {{name}}, deployTimeout, checkAgain), with the interpolation the package page uses. TranslationKeys does not widen. The claim's file surface anticipated marketplace.org.* keys; the diff shows that prediction was unnecessary, and sharing the package page's copy keeps the two pages from drifting.
  3. Input accept-set of doOrgInstall: no widening, no new fallback. Right. The cloud branch reads pkg.manifest_id and pkg.display_name, both already required string members of OrgPackageSummary; no || fallback is added. The installLocal branch's pre-existing pkg.manifest_id || pkg.id is byte-unchanged and not this PR's to answer for.
  4. The wait is the one waitForServedApp, called unchanged. Right. No intervalMs or capMs is passed, so the module's SERVED_APP_POLL_INTERVAL_MS and SERVED_APP_WAIT_CAP_MS govern; the test pins defaults: true on every wait the page starts. Triage's "no second copy of the wait" holds, waitForServedApp.ts is untouched, so the serial constraint with objectui#12098 is clear.
  5. emitMetadataRefresh() moves behind served on the cloud branch and is not sent on expiry. Right. That is the card's scope sentence. The readApps closure reads adapter.getClient().meta.getItems('app'), not the metadata cache, as the wait's contract requires. A null adapter makes readApps throw, which waitForServedApp swallows ("answers neither way") until the bound: an honest timeout, not a crash and not an unhandled rejection, exactly as on the package page.
  6. Cloud-branch success text: marketplace.org.installed is no longer shown before the app is served. Right. The status box carries the package's display name as its title and the package page's three states in the package page's words; both cloud cases pin the absence of the "Installed NAME" text.
  7. installLocal branch behaviour: identical. Right. setOrgMsg, await load() and the pulse move inside the branch in the order they ran before; the CONTROL case pins the immediate pulse, no wait, no status box.
  8. One status per strip; a late settle never overwrites. Right. settle writes only while the current status still names its manifestId; a superseded wait's pulse still goes out, which is correct because its app is served.
  9. Not awaited, not cancelled on unmount. Right, by precedent. void awaitServedOrgInstall(...) mirrors the package page's void awaitServedInstall(...); a state write after unmount is a no-op in React 18 and the pulse still reaches every mounted provider.
  10. Test shape matches triage's four items. Right. The real waitForServedApp at its real constants with only now and sleep injected; the real MetadataProvider and assistant bus mounted; the stale window (no pulse and no stale seed write across eleven intervals, one pulse at 60 s with the app served), expiry (clock at exactly the cap, no pulse, no success, no seed write, "Check again" finds the app on its first read), and CONTROL. The vi.mock of ../waitForServedApp wraps importOriginal; Inert vi.mock Specifier Check is green on the head.

② Semver level

  • Changeset @object-ui/app-shell: patch. Right. The diff publishes a behaviour fix in a released package and nothing else: no export, prop, type or locale key. patch is the level a fix takes; Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report and Changeset Claim Re-read are all green on the head. The changeset body states the before and after behaviour and names the reused keys; no migration is owed because nothing authorable moves.
  • The PR body's Clause-② line reads yes; the diff answers no. The claim (6104559670) declared yes on the prediction that new locale keys would widen TranslationKeys. The dev measured on the built closure and the diff confirms it: zero keys, zero exports, and no narrowing either. The direction is this review's to rule: the diff neither widens nor narrows a published accept set or public surface, so the declaration that describes it is no with no arm, and patch is consistent with that. The yes was conservative, cost exactly this review, and is not a false declaration. The dev copied the claim's line as the executor rules require and handed the direction to this record, which was the right move. Recommended, not required for landing: the owning seat appends a one-line correction to the PR body, since the line is the seat's own declaration.

③ Boundary flags

open_questions in the os-dev-report: none declared. The dev's seven deviations and two out-of-scope findings, each answered:

  • D1, no marketplace.org.* key; the package page's marketplace.install copy is reused with the package name as the title. Accepted; ① items 2 and 6.
  • D2, the pulse moved behind the wait rather than the suggested seed drop plus refreshMetadata(). Accepted. The card's scope sentence is "before the pulse, the org install waits"; the pulse already makes every mounted provider, this page's own included, re-read and re-persist, and the test asserts on the real provider's seed writes, not on a spy of the page's calls.
  • D3, virtual clock by injected now and sleep, not fake timers. Accepted: triage asked for a test on the virtual clock, and this one runs the real module at its real constants.
  • D4, objectstack-ai/cloud attached read-only and shallow-cloned outside the repo to read the org-packages producer; register_repo_root not called. A process note; nothing of it is in the diff or the repo tree. Noted, nothing to judge.
  • D5, eager-closure delta measured with vite build on a swapped page under a restore trap. Accepted; Bundle Analysis and Docs Route Eager Closure Check are green on the head and the measured delta is -1 byte.
  • D6, model-free commit trailers and the session-URL body footer. Accepted: that is the trailer and footer form both repos' rules ask for.
  • D7, a second commit (changeset wording only) with the full gate union re-run on it. Accepted; b32312a1 is the head this record reviews.
  • OOS-1, the cloud branch's installPackage({ environmentId: '' }) reaches the same-origin control-plane action when getCloudBase() is empty, which the cloud handler refuses with 400 environment_id is required; the dev marked it 承接者:无, noted, not filed. Pre-existing and untouched by this PR: the wait runs only after a successful answer, and on a tenant runtime (getCloudBase() non-empty, the shape the card measures) the install goes through /api/v1/cloud-connection/install, which resolves the environment by hostname. Not blocking. Escalated to the owning seat: the finding names contract text (the handler's refusal), which is what a card needs under the file-a-defect rule; file it, or record on the card why not. The dev's dedupe words stand.
  • OOS-2, the readApps closure repeats the package page's three lines; carrier objectui#12098. Accepted: a shared reader edits waitForServedApp.ts or the package page, both outside this claim, and the carrier is named in the PR's Acceptance notes.
  • Reviewer flag, outside the three contract faces, does not move the verdict: the new test file's header comment carries one non-English fragment (the word 打开 in corner brackets, in the sentence about the Open button). objectui's English-only codebase rule covers code comments; no gate reads it. A push now would re-owe this record for one word: fix it on the file's next touch, or in a patch round if one comes.

Check-runs on b32312a1 as read: 42 runs; 28 success; 3 skipped (two coverage matrix placeholders and dependabot); 11 still in_progress at read time: Lint, Type Check, Spec Main Shape Gate, and Test (shard 1/8) through Test (shard 8/8). No run has failed. Those eleven are not read as green here; the seat confirms every check green before the queue. ①②③ above are judged on the diff, the card and the completed runs.

Implemented-by: claude/issue-12097-org-install-wait-served
Reviewed-by: session_01AswpQDLCKiZos2jCXknwKz

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 11, 2026 03:12
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 11, 2026 03:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit a21ff9a Oct 11, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12097-org-install-wait-served branch October 11, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants