Skip to content

fix(console,app-shell,auth): one request per resource on console open — entitlements, get-session, runtime/config (objectui#12808) - #12840

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12808-one-request-per-resource
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12808-one-request-per-resource

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12808
Clause-②: yes

Each of the three resources the card measured is now fetched once per console open.

What changed, per resource

Environment entitlements (packages/app-shell/src/environment/useEnvironmentEntitlements.ts). Every reader of useEnvironmentEntitlements now shares one resource per control-plane origin and organisation. The resource holds the in-flight resolution and the settled answer (the summary, with the row-derived fallback when the summary is unavailable). The readers are the three cloud:plan-status cards, cloud:onboarding-next, cloud:workspace-timezone-notice and the environment list. CloudOnboardingNext drops its own fetch and reads the shared resource; only a summary answer resolves it, so a failed or row-derived answer still reads as unknown, as before. The resource is refetched when the organisation changes, and when a reader's refreshKey moves: the environment list after a create explicitly re-resolves the shared answer, and every reader gets the new one. Nothing is time-based. The answer lives only while at least one reader is mounted. The drop waits for the end of the current task, so an unmount and a mount committed together hand the answer over. A later mount asks the server again, as every mount did before.

Session (apps/console/src/lib/auth-preflight.ts, packages/auth/src/createAuthClient.ts, packages/auth/src/AuthProvider.tsx). When the preflight finds the stored Bearer valid, it leaves its get-session answer in a page-load slot on globalThis (Symbol.for('objectui.authPreflightSession')) along with the auth URL and the token it asked with. AuthProvider takes the answer once, in a useState initializer, as its first state, and its mount effect does not ask again. That includes the StrictMode effect replay. The handoff only applies when the URL matches, the stored token is still the one the preflight validated, and the provider builds its own client. The body is parsed with better-auth's parseJSON, the same parser getSession() uses, so dates are revived the same way. A stale token leaves nothing in the slot. Sign-in, sign-out, organisation switch, rotation and refreshSession all read from the server as before.

Runtime config (apps/console/index.html, packages/app-shell/src/runtime-config.ts). The early-branding script used to publish its request to the in-flight registry and drop it on settle. It now keeps its parsed-body promise for the page's lifetime in a slot (Symbol.for('objectui.earlyRuntimeConfig'), with the URL it asked). The first initRuntimeConfig() call takes it when the URL matches: it joins the request if it is still in flight and reads the body if it has settled. If the early fetch failed, initRuntimeConfig() fetches as before. Every later call re-fetches, so the repeat-call contract is unchanged.

Before and after: one console open, live

hotcrm objectstack dev --fresh backend, console via Vite dev, Chromium through Playwright. The browser signs in once (cookie plus Bearer in localStorage), opens the console once to warm Vite, then the measured open is recorded. Two runs on each side gave identical counts.

request main 6e4f144 this branch
GET /api/v1/auth/get-session 3 1
GET /api/v1/runtime/config 2 1
GET /api/v1/cloud/environment-entitlements 0 (cloud-only route, see below) 0
all /api requests of the open 30 28
all requests of the open (Vite dev module graph included) 3375 3373
  • get-session reads 3 on main in dev because React StrictMode replays AuthProvider's mount effect. A production build shows the card's 2. With this branch it is 1 in dev too, because the replay reuses the handed-off answer.
  • The entitlements route belongs to the cloud control plane, and the local backend does not mount the cloud widgets. Its count comes from the test below instead: the three plan cards, the onboarding step and the timezone notice mounted together issue 1 GET. With the shared resource ablated they issue 5.
  • These numbers were measured on head 745e4b2. The later head cef7052 changes only a type annotation in packages/auth.

The CONTROL that the console still renders: console home after the change, light, https://github.com/objectstack-ai/objectui/blob/73c020afbaf65fed269ff19dc5b52e169450db4f/docs/design/findings/12808/console-home-after-12808.png?raw=true (on the evidence branch claude/issue-12808-evidence, which is not merged here).

Tests: one per resource, counting the requests a mount issues

  • useEnvironmentEntitlements.oneRequest-12808.test.tsx (app-shell): five readers mounted together issue one GET. CONTROLs: an organisation change issues exactly one more; a refreshKey change re-resolves once for every reader; a later mount after all readers unmounted asks again.
  • sessionBootDedup.test.tsx (console): the real preflight plus the real AuthProvider in StrictMode issue one get-session, and dates are revived. CONTROLs: sign-out then sign-in then refreshSession reads the session again; a second provider asks the server, so the answer is used once; a stale token hands nothing on.
  • runtimeConfigBootDedup.test.ts (console, runs the shipped script text): new pin that the early answer is read even after it settled. CONTROLs: a failed early fetch still lets app-shell fetch once; a failure on both keeps the defaults. The old pin, which expected an early failure to suppress app-shell's fetch, is replaced as the card's ruling asks.

Ablation, one per new pin. Each mutation landed (ablation-replace.mjs anchor 1 to 0), turned the pin red, and was restored with blob equal to HEAD and git diff HEAD empty:

  • entitlements: always start a resolution in acquire gives 5 GETs instead of 1; all 4 tests red.
  • session: remove the boot-answer early return in AuthProvider's mount effect gives 3 get-session instead of 1; 2 tests red.
  • runtime config: never take the early answer gives 2 fetches instead of 1; 6 tests red.

Evidence, pinned to cef7052

  • pnpm --filter @object-ui/app-shell test: exit 0. 1510 files passed and 1 skipped (1511); 14117 tests passed and 9 skipped. Run on 745e4b2; cef7052 does not touch app-shell.
  • pnpm --filter @object-ui/auth test: exit 0, 33 files, 311 tests (on cef7052).
  • Targeted on cef7052: the three pin files, faviconAfterNavigation.test.tsx and anonSeedScope-5746.enumeration.test.tsx, 5 files, 40 tests, exit 0.
  • type-check for auth, app-shell and console: exit 0. build of the console's dependency closure (includes app-shell and auth): exit 0. pnpm --filter @object-ui/console build: exit 0.
  • eslint on the touched files: exit 0 (0 errors). pnpm check:i18n-keys 0, pnpm check:i18n-drift 0, node scripts/check-changeset-presence.mjs 0, pnpm check:eager-closure 0 (within budget), pnpm check:new-line-citations 0 (no new citations).

Deviations

  • Session handoff: I used a module-level slot, not a new AuthProviderProps member. AuthProvider is mounted in apps/console/src/App.tsx, which is outside the claimed file surface. The slot mirrors the index.html to runtime-config.ts handoff, so both handoffs work the same way. takePreflightSession is exported from the createAuthClient.ts module but not from the package entry: the package's only export is ., whose index.ts names createAuthClient and TokenStorage. So @object-ui/auth's published surface gains no member.
  • Entitlements: the settled answer is held while readers are mounted, not for the whole page. Holding it for the page would serve a stale hasProductionEnv after an environment is deleted outside the list, for example from a record page, and the list then remounts at refreshKey 0. The card's no-stale rule forbids that.
  • CloudOnboardingNext: on a failed summary it now waits for the shared resource's row fallback before showing unknown. This only happens on the failure path. The Welcome page's timezone notice already issued that rows read.
  • apps/console/src/main.tsx is in the claimed surface but needed no change.

Acceptance notes

  • Out of scope and unchanged: objectui#12809 (the splash's request waves) remains open and lands after this; the server-side query counts and the cloud pricing page's metadata are not touched.

Session: https://claude.ai/code/session_01H8duocHdwfqNvZ3vLSn47m


Generated by Claude Code

- Entitlements: every reader of useEnvironmentEntitlements shares one
  organisation-keyed resource (in-flight resolution + settled answer, held
  while a reader is mounted). CloudOnboardingNext moves onto it. A
  refreshKey change re-resolves the shared answer for every reader.
- Session: the pre-React preflight leaves its valid get-session answer in a
  page-load slot; AuthProvider takes it once for its first state instead of
  asking again. No exported member is added.
- Runtime config: index.html keeps its early runtime/config answer for the
  page's lifetime; the first initRuntimeConfig() call reads it, and a failed
  early fetch still fetches as before.

Claude-Session: https://claude.ai/code/session_01H8duocHdwfqNvZ3vLSn47m
Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#12808)

The preflight reads the get-session body with res.json() as before and
hands it on as JSON text, so callers with a json-only response double keep
working. The entitlements hook updates its resolve inputs in an effect, and
the session pin reads useAuth through a layout-effect sink.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3132.4 KB 3744.1 KB
Main entry chunk (gzip) 75.7 KB 350 KB
Entry file index-C0Xmvr3c.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) 21.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
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) 42.48KB 11.25KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 606.28KB 146.84KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.36KB 74.46KB
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.79KB 11.90KB
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) 15.35KB 5.51KB
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) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.70KB 69.73KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.28KB 46.60KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 271.78KB 75.39KB
plugin-kanban (index.js) 62.17KB 19.22KB
plugin-list (index.js) 138.78KB 35.68KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 94.68KB 24.36KB
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.23KB 39.46KB
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.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
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-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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

席位核对 — ACCEPT · objectui#12808 · head cef7052 · 2026-10-17T01:20Z

Seat domain:ui#4, session session_01SJn1GEkrbbBQkhGC26wEDw. Checked against GitHub and the diff, not against the report (relayed on the card, 6227831649). The seat opened this PR from the dev's drafts (degraded channel). The body is the dev's draft verbatim; the title's scope names the three packages the change touches.

Checklist

  • Form:
    • Draft → main, assignee os-sales.
    • The first line is Fixes #12808, and the second is Clause-②: yes, as the claim declares (6226275331).
    • Three commits. Every message carries only the model-free trailer pair.
    • git merge-tree against current main is clean. 15 files, +735 / −191, no PNG.
  • Rulings, as bound:
    • Entitlements: one resource per control-plane origin and organisation, holding the in-flight resolution and the settled answer, shared by every reader of useEnvironmentEntitlements: the three plan cards, the timezone notice, the environment list, and CloudOnboardingNext, which drops its own fetch. It refetches on an organisation change and on the environment list's refreshKey, the explicit invalidation that existed. Nothing is time-based.
    • Session: the preflight's get-session answer reaches AuthProvider through a page-load slot and is taken once, in a useState initializer, so the StrictMode replay reuses it. It is taken only when the auth URL matches, the stored token is still the one the preflight validated, and the provider builds its own client. Sign-in, sign-out, organisation switch, rotation and refreshSession read from the server as before.
    • Runtime config: index.html keeps its parsed-body promise for the page's lifetime, and the first initRuntimeConfig() takes it on a URL match. If the early fetch failed, it fetches as before. Later calls refetch.
  • Mechanism assumptions: all four held. A fifth entitlements reader was found (the environment list), and it carries the refreshKey invalidation.
  • Live readings (hotcrm objectstack dev --fresh, Chromium, one open, two runs per side, identical):
    • get-session: 3 → 1 in dev (StrictMode replays the mount effect; 2 → 1 in a production build).
    • runtime/config: 2 → 1.
    • environment-entitlements: a cloud-only route, absent locally. From the tests: five readers mounted together, 5 → 1.
    • All /api requests of the open: 30 → 28.
  • Screenshot, read by the seat (never-merged claude/issue-12808-evidence at 73c020a): the console home after the change, light. The greeting, Your apps, My work, Recently Accessed and the Activity feed all render: the CONTROL holds. Sent to the maintainer at 2026-10-17T01:20Z.
  • Tests:
    • Three pin files, one per resource. Entitlements: 4 tests (one GET; CONTROLs for an organisation change, a refreshKey move and a later mount). Session: 4 tests through the real preflight and the real AuthProvider in StrictMode (one get-session; CONTROLs for sign-out then sign-in, a second provider, a stale token). Runtime config: 12 tests, 3 new or replaced (the early answer read after it settled; a failed early fetch still lets app-shell fetch once).
    • Three ablations, each red (5 GETs, 3 get-session, 2 fetches instead of 1), each restored to an empty git diff HEAD.
    • Full suites: app-shell 1,510 files / 14,117 tests at 745e4b2 (cef7052 retypes one local in packages/auth only), auth 33 files / 311 tests at cef7052; both exit 0.
    • type-check for auth, app-shell and console, the console's dependency build and its own build, eslint on touched files (0 errors), the i18n checks, changeset presence, eager-closure (3,130.7 KB of 3,744.1 KB) and line citations: all exit 0.
  • Changesets: @object-ui/console, @object-ui/app-shell and @object-ui/auth, each patch; user-facing prose.
  • Contract faces: reviewed in the TIER_DEFAULT record that follows.
  • CI: running at this record. Landing waits for green.

Rulings

  1. How long the settled entitlements answer lives: A, as built. It is held while at least one reader is mounted, handed over across a same-commit replace, and asked again by a later mount. The card's rulings ask for in-flight dedupe plus a settled answer keyed by what it answers, and forbid anything that can serve a stale answer. A page-lifetime answer (B) would serve a stale hasProductionEnv after an environment change outside the list's refreshKey. A removes every measured duplicate (pricing ×3, Welcome ×2). This is the seat's call under the existing rulings, not a maintainer decision.
  2. The session handoff as a module-level slot, not an AuthProviderProps member: accepted. The ruling allowed either. AuthProvider is mounted in App.tsx, outside the claimed surface, and the slot mirrors the runtime-config handoff.
  3. The replaced runtime-config pin: accepted. The old pin expected an early failure to suppress app-shell's fetch; the ruling's CONTROL is the opposite.
  4. CloudOnboardingNext waiting for the row fallback on a failed summary: accepted (failure path only; the timezone notice already issued that read).
  5. main.tsx unchanged: fine; it is in the claimed surface but needed nothing.
  6. The contract conflict: as dispatched. The relay is refused from the dev's container, so the seat opened the PR and relayed the report.

Implemented-by: cloud session session_01H8duocHdwfqNvZ3vLSn47m, branch claude/issue-12808-one-request-per-resource


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: cef7052bfbaacfc2122eaf210e0ed7bb7336bda9
Local-runs: none

Face: the Clause-②: yes declaration of the claim 6226275331 on objectui#12808 («拿不准 ⇒ 按 yes»: the claim expected one optional AuthProviderProps member). The PR's file list carries no packages/spec/src/** path and no governed path. Inputs read: the card, the claim, the diff at the head above, the three changesets, the two package entries and the PR body. Nothing was built or run locally.

① Derived judgments

  • @object-ui/auth's published surface: the package exports only ., and src/index.ts names createAuthClient and TokenStorage. The new takePreflightSession is exported from the createAuthClient.ts module but not from the entry, and AuthProviderProps is unchanged. No published member moves.
  • @object-ui/app-shell's published surface: src/index.ts re-exports the same names from runtime-config.ts as before, and no export line in runtime-config.ts, useEnvironmentEntitlements.ts or the three home cards changes. The new ResolveOptions and EntitlementsResource are module-internal. No published member moves.
  • The two page-load slots (Symbol.for('objectui.authPreflightSession'), Symbol.for('objectui.earlyRuntimeConfig')) are a handoff between the console's own boot code and two packages. They are not on a package entry, and each package works without them: an absent or mismatched slot means "fetch as before", pinned by the CONTROLs. They follow the in-flight registry index.html already used. Acceptable as module-internal; not a contract face.
  • Behaviour: no authored metadata key and no schema the spec owns changes. Nothing is cached by time, so no answer can outlive what it answers. Correct.

② Semver level

Three patch changesets (@object-ui/console, @object-ui/app-shell, @object-ui/auth) in pre mode on tag next. No published member is added or removed, and the behaviour change is fewer duplicate requests. Consistent.

③ Boundary flags

  • None to the maintainer. The declaration Clause-②: yes was conservative; this review finds no exported face moved.

Implemented-by: session_01H8duocHdwfqNvZ3vLSn47m
Reviewed-by: session_01SJn1GEkrbbBQkhGC26wEDw

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI red on cef7052 · read 2026-10-17T01:23Z

  • Failing check: Type Check, step pnpm check:spec-symbols (job 117269623123). Every other check that finished is green.
  • Cause: this PR's own code. The new module-internal type ResolveOptions (packages/app-shell/src/environment/useEnvironmentEntitlements.ts:65) has the name of a type @objectstack/spec/system exports, and the gate refuses a local declaration under a spec export's name.
  • Fix: rename the local type to a name the spec does not own; no ALLOW entry. Sent to the dev's session session_01H8duocHdwfqNvZ3vLSn47m; the push follows on this branch. The ACCEPT (6227886097) and the contract review (6227891729) stand: the rename touches no published face.

Generated by Claude Code

…s name (objectui#12808)

`ResolveOptions` is a symbol `@objectstack/spec/system` owns; the
module-internal type is a different shape, so it takes its own name.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3132.5 KB 3744.1 KB
Main entry chunk (gzip) 75.7 KB 350 KB
Entry file index-BhhCjbbR.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) 21.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
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) 42.48KB 11.25KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 606.28KB 146.84KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.36KB 74.46KB
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.79KB 11.90KB
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) 15.35KB 5.51KB
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) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.70KB 69.73KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.22KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 271.78KB 75.39KB
plugin-kanban (index.js) 62.17KB 19.22KB
plugin-list (index.js) 138.78KB 35.68KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 94.68KB 24.36KB
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.23KB 39.46KB
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.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
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-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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

Landing · head cb91adb · 2026-10-17T01:49Z

  • Head since the ACCEPT: cef7052 → cb91adb, two commits. 8ea26f8 merges main, without conflicts. cb91adb renames the module-internal type ResolveOptions to EntitlementsResolveOptions in packages/app-shell/src/environment/useEnvironmentEntitlements.ts (5 occurrences, one file). That fixed the one red, check:spec-symbols (note 6227918917). No behaviour, export or test changed, so the ACCEPT 6227886097 and the TIER_DEFAULT contract review 6227891729 (written at cef7052) hold for this head. The dev's re-run on cb91adb: check:spec-symbols (red before the rename, green after), the entitlements pin (4 tests), app-shell type-check and eslint on the file, all exit 0.
  • Checks on cb91adb: 36 runs, 33 green and 3 expected skips, none red. That includes Type Check, Repo Gates, Build & E2E, all test shards, Lint, Changeset Bump Policy and Bundle Analysis at 3,132.5 KB against the 3,744.1 KB ceiling.
  • Merge state: clean against main ac59073.
  • Commit messages: every commit carries only the model-free trailer pair, so the squash adds no model name to main.
  • Action: mark it ready for review and enable auto-merge through the merge queue. Fixes #12808 closes the card on merge, and objectui#12809 (the splash's request waves, same boot path) becomes dispatchable after it.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 17, 2026 01:50
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 5d69b0e Oct 17, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12808-one-request-per-resource branch October 17, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants