Repository navigation
fix(console): the sign-in page renders from the built-in packs, and the application's translations load after sign-in (objectui#12034) - #12042
Merged
objectstack-fleet[bot] merged 4 commits intoOct 9, 2026
Conversation
…s credentials (objectui#12034) The two loaders main.tsx hands to I18nProvider ran on the provider's first commit, before AuthProvider had answered, with a bare fetch: the sign-in page read /api/v1/i18n anonymously, and a signed-in boot carried the session only when a same-origin cookie rode along. They now wait for this page load's session answer (fed by AuthProvider's onAuthStateChange in App.tsx). Signed out, they request nothing and the sign-in page renders from the built-in packs. Signed in, they read through createAuthenticatedFetch(), the data adapter's own request path. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…cated /i18n load after sign-in (objectui#12034) Three pins through the real App against a framework that refuses an anonymous /i18n read: no /i18n request on the sign-in page (through the sign-in itself), the application label re-rendered from the authenticated read on the mounted heading, and no raw key on any frame of the sign-in to app path, with a control showing the recorder catches a raw key. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…objectui#12034) Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
… adapter's own fetch does (objectui#12034) The adapter's request path is withSettleSignal(createAuthenticatedFetch()); the two /i18n reads now take the whole of it, so an automated driver's idle predicate waits for the application's translations too. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ 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
|
objectstack-fleet
Bot
deleted the
claude/issue-12034-console-i18n-after-signin
branch
October 9, 2026 08:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12034
Clause-②: no
The console's sign-in page now renders from the built-in language packs and makes no
/api/v1/i18nrequest. Once the page load is signed in, the application's translations and locale list are read with the session's credentials, through the data adapter's own request path. This is the objectui half of the ruling on objectstack-ai/objectstack#22146 (6074960686, batch 300, item 1, letter A).Order. This card lands first. objectstack-ai/objectstack#22432 (the framework's anonymous refusal on
/i18n) isBlocked-by:objectui#12034 and moves the framework's.objectui-shapin past this merge in its own PR. Nothing here touches the framework or its pin.What changed
apps/console/src/i18nSession.ts(new): the session answer the two loaders wait for. It is fed by the consoleAuthProvider's existingonAuthStateChangeprop, so the session authority is the provider itself. Nothing reads the session a second time, and nothing lifts a token out of storage. It also exportsi18nFetch = withSettleSignal(createAuthenticatedFetch()), the same request pathAdapterProviderbuilds for the data adapter.apps/console/src/loadLanguage.ts,apps/console/src/loadLocales.ts: each loader first awaits the session answer. Signed out, it sends no request and answers{}or[]. Signed in, it reads throughi18nFetch(bearer,X-Tenant-ID,Accept-Language, session-rotation adoption, settle-signal count). Not yet answered (isLoading), it waits, and the wait ends when the session read ends. It never rejects.apps/console/src/App.tsx:onAuthStateChange={publishAuthState}on the console'sAuthProvider. This is the session state the post-sign-in load hangs on, named here as the claim asks.apps/console/src/main.tsx: a comment only. TheI18nProvidermount and its two props are unchanged.apps/console/src/__tests__/i18nAfterSignIn-12034.test.tsx..changeset/12034-console-i18n-after-signin.md: apatchfor@object-ui/console.No new prop, export or key on
I18nProviderorpackages/i18n. The provider is used through its existingloadLanguageandloadLocalesprops. Docs: no page incontent/docsdescribes the console's translation loading.git grepfor the loaders and the/i18nroutes finds none, and the only mention is the sentence inguide/console.mdandguide/deployment.mdthat the i18n endpoints hang offVITE_SERVER_URL, which is still true. So no docs page is edited.The hypotheses, measured
The readings below come from a fake framework whose
/i18nanswers401 UNAUTHENTICATEDto any caller without the session's bearer (the head of objectstack-ai/objectstack#22432). It knows the bearer only. That models a deployment where no session cookie rides along, such as a console built with an absoluteVITE_SERVER_URL. The probe ran the realAppand the realLoginPageonmain's blobs of the loaders,App.tsxandmain.tsx.main, the sign-in page's page load made 2 reads (translations/zhandlocales). Neither carriedAuthorization, and both got 401. The "never re-fetches after sign-in" half does not hold. Every console sign-in exits through a full-page navigation (window.location.assigninLoginPage,RegisterPageandSetupPage;authExitBasename.test.tsxpins it). So the loaders run again on the page load after sign-in: 2 more reads, still with noAuthorization, both 401. The fetches use the defaultcredentials: 'same-origin', so on a same-origin cookie deployment the session cookie does ride along, and that post-sign-in read would have been served. The bearer was never sent.get-sessionround trip, the same answerAuthGuardwaits for before it renders anything signed in. The reads then carryBearerand get 200. "The first render already has the application's labels" holds on neithermainnor head:I18nProvidernever holds the first render for its loader. Pin 2 shows the actual sequence on one mounted heading: the authored literalLeadfirst, then线索once the held response is released.addResourceBundlewithbindI18nStore: 'added') re-renders every reader. No key remount (AGENTS.md commandment 8 refuses one), and nochangeLanguageto the same language: that call runs through the provider'slanguageChangedchoke point and would persist the language as an explicit user choice. A session that begins in place, with no page load, is not re-run (see Acceptance notes). The provider has no re-run hook for that:loadedAppLangsandaskedForLocalesguard it.MutationObserverrecorder over every frame of the sign-in to app path records zero raw keys, and a control proves the recorder catches a key drawn raw. The card's raw-key premise did not reproduce onmaineither: against the head of runtime: the/i18ndispatcher domain answers an anonymous caller —handleI18nRequestmakes noshouldDenyAnonymouscall, unlike every other dispatcher domain (ADR-0056 D2); with an objectui companion for the Console sign-in page objectstack#22432,mainshowed the signed-in userLead(the untranslated authored literal) and zero raw keys. The application-bundle readers (useObjectLabel,useSettingsLabel) fall back to the authored literal. The spec'sI18nLabelrefuses a keyed{ key, defaultValue }form (key?: never), so a spec-legal label has no form that could render as its key. Key coverage of built-int()call sites ischeck:i18n-keys's job, and it is green.eagerGzipBytes: base049012bf03,238,404 B, head27caa7d0b3,238,537 B, a difference of +133 B gzip. The ceiling is 3,281,467 B.pnpm check:eager-closureprints: Console eager closure is 3162.6 KB gzipped across 289 of 2474 chunks (budget: 3204.6 KB, headroom: 41.9 KB). Both builds usedCI=true pnpm exec vite buildinapps/console, under the verify lock.Pins and the reverse leg
The pin file renders the REAL
App. That means the realAuthProviderprops (so the realonAuthStateChangewire), the real route table, the realLoginPageand the realProtectedRoute. Only the auth network client is the test's. TheI18nProvidermount ismain.tsx's, transcribed: that module boots the page when it is imported./login: the sign-in itself goes through the real form, and no/i18nrequest is made across that whole page load. The labels are the built-inzhpack's.Bearerand get 200. The mounted heading re-renders fromLeadto线索(objectui#10382: the assertion is on rendered text, not on a request count)./i18nread gets a status other than 200.console.noSuchKey12034drawn raw.Reverse leg at
27caa7d0b. All four ofloadLanguage.ts,loadLocales.ts,App.tsxandmain.tsxwere swapped formain's blobs, with the hash of each checked equal to049012bf0's. Result: 7 red (pins 1, 2, 3 and the four new loader session-rule tests), 13 green (the control and the pre-existing loader tests). Restored withgit checkout HEAD: each blob hash matched HEAD's andgit diff HEADwas empty. A second ablation removed only theonAuthStateChange={publishAuthState}wire fromApp.tsx: pins 2 and 3 went red, and pin 1 and the control stayed green, as expected, because without the wire the loaders never ask at all. Restore proven the same way.Gates (head
27caa7d0b)pnpm exec vitest runover 40 files: the three touched or new test files; everyapps/consolesuite that mountsI18nProvider,LoginPageorApp(31 files); the suites that readmain.tsxorApp.tsxsource (bootSplash,insecure-origin-crypto.placement,faviconAfterNavigation,tabTitleAfterNavigation,consoleToasterAnchor.ratchet-7482,auth-namespace-3546,runtimeConfigBootDedup);column-identity.ratchetandone-authority-per-exported-name-6273. Result:Test Files 40 passed (40),Tests 290 passed (290).pnpm --filter @object-ui/console type-check(the dependency closure built first with turbo): exit 0. Its program includes the three test files, counted with--listFiles.pnpm exec eslinton the 8 touched source and test files: 0 errors, 0 warnings.node scripts/check-changeset-presence.mjs: ✅ 8 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).node scripts/check-changeset-no-major.mjs: ✅.pnpm check:new-line-citations: 0 new citation(s).check:control-bytes,check:i18n-keys,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:changeset-claims,check:pending-changeset-literals,check:unreferenced-sources,check:phantom-deps,check:eager-closure: all exit 0.pnpm lintand the fullpnpm test.docs:check-linksandcheck:spec-symbolsdo not apply, becausecontent/docsis not touched.Changeset rule: AGENTS.md §9 Housekeeping.
apps/consoleis in thefixedrelease group, andsrc/changed, so it takes a changeset: apatch. The claim'sClause-②: nostill stands, because no export, prop or contract changes. But@object-ui/consoleis a published package (publishConfig.access: public, shipsdist), not only an application.examples/console-starter, measured and not editedThe starter hands
I18nProvideronlyloadLanguage, which is a barefetch, and it signs in IN PLACE (DefaultLoginPagecallsnavigate('/'), with no page load). A probe with the starter's real loader and the 401 framework read like this. Before sign-in: 1 read with noAuthorization, 401. After an in-place sign-in: 0 reads. The heading stayedLeadfor the session. So once objectstack-ai/objectstack#22432 lands, a starter user who signs in sees untranslated application labels until they reload. That is true on every deployment, cookie or not, because the read before sign-in is anonymous everywhere and the provider never asks again. After a reload, a same-origin cookie deployment is served and a bearer-only one gets 401 again. The console's gate is not a drop-in fix there: with an in-place sign-in, the page-load call would have to wait for a sign-in, not for the first answer. The starter has noloadLocales.Acceptance notes
get-sessionround trip later than before. It still runs in parallel with the adapter connect and the metadata reads, which start at that same answer.main, and the bundle is per deployment, not per user.window.__objectui.pendingRequests, as the data adapter's reads do. An automated driver's idle predicate waits for the translations.Generated by Claude Code