Repository navigation
fix(app-shell): the exported default auth pages follow the audience posture (objectui#11705) - #11711
Conversation
…osture (objectui#11705) DefaultLoginPage and DefaultRegisterPage read emailPassword.disableSignUp alone, so under the default invite_only posture they offered a generic sign-up the server refuses with SELF_REGISTRATION_CLOSED. They now call decideSignUpOffer, the decision the console's own pages have used since objectui#11691. The decision and the bootstrap-status probe it reads move out of the private apps/console into @object-ui/app-shell, unchanged, and the console's pages and /setup entry import them from there; the console's copy is deleted, not shimmed. objectui#11691's 16 pins stay green: the 5 decision cases move beside the decision, byte-identical, and the 11 rendered console-page pins stay where they were. 11 new rendered pins cover the default pages. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…ctui#11705) A README section for DefaultLoginPage / DefaultRegisterPage: what each page offers per disableSignUp and audience posture, and the exported decision a host building its own pages can call. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju 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, read at 2026-10-06T11:06Z: card objectui#11705 (body and its six comments: triage's first grade Check-runs on the head: 43 concluded — 40 success, 3 skipped ( ① Derived judgmentsEach accept-set and public-surface change the diff implies, named right or wrong:
② Semver level
Clause-②: yes (widening) ③ Boundary flagsDev deviations (six), each answered:
Out-of-scope findings (two), and the seat's own observation, answered or escalated:
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #11705
Clause-②: yes
What
DefaultLoginPageandDefaultRegisterPage, the auth pages@object-ui/app-shellexports (examples/console-startermounts them at/loginand/register), reademailPassword.disableSignUpalone. Under the defaultinvite_onlyaudience posture they offered "Sign up" and the full form to every visitor, and the server refused the finished form with403 SELF_REGISTRATION_CLOSED. They now calldecideSignUpOffer, the decision the console's own pages have used since objectui#11691 (PR objectui#11703, landed asdaa7caf).The move: one rule, no second copy
apps/console/src/pages/auth/signUpOffer.tsmoves topackages/app-shell/src/console/auth/signUpOffer.ts. With comments and imports stripped, the code compares equal to BASE.useBootstrapStatusandBootstrapStatus, out ofapps/console/src/components/setupEntry.ts, intopackages/app-shell/src/console/auth/bootstrapStatus.ts. The function body also compares equal to BASE, and so does its base-URL expression.LoginPage,RegisterPageandsetupEntry.ts(decideSetupEntry/useSetupEntryModestay there) import them from@object-ui/app-shell. The console's copy is deleted, not left behind as a re-export shim. At BASE its only importers were those pages and the pin file, so no shim is needed.Why
@object-ui/app-shell, not@object-ui/auth(order, Zone 2 #3). The decision is not only aboutAuthPublicConfig. It also reads the invitation route/accept-invitation/IDthatDefaultAcceptInvitationPage(app-shell) bounces from, and thehasOwnerprobe, a REST call. app-shell already owns all of the related pieces: the default pages, the refusal-copy maps the console already imports (signInRefusalMessages,signUpRefusalMessages), the accept-invitation page, and theVITE_SERVER_URL+/api/v1/...convention the probe uses.@object-ui/authowns the client and the config type, and neither of those knows about routes or the probe.The probe (Zone 2 #2), measured before moving it. app-shell had no equivalent. At BASE,
git grepforbootstrap-status/hasOwner/BootstrapStatusoverpackages/returns zero hits. The same query overapps/console/srchitssetupEntry.ts,SetupPage.tsxand their tests, which is the control.Zone 2 #1 confirmed. At BASE the app-shell entry exports
LoginPage as DefaultLoginPageandRegisterPage as DefaultRegisterPagefromconsole/auth/, andexamples/console-starter/src/App.tsxroutes those two at/loginand/register. Both pages readcfg?.emailPassword?.disableSignUp === trueand nothing else.The default pages now
DefaultLoginPagereads?redirect=, the config, and the probe. The probe runs only when the posture is closed and the visitor is not an invitee. The page offers the link only forform, and the link carries?redirect=, which is how/registerknows the visitor is an invitee. Before the config is read it behaves as before the posture existed, which is what the console login page does too.DefaultRegisterPagerenders nothing until the config is read, as before.closedbounces to/loginand keeps?redirect=.pendingrenders nothing.by-invitationexplains before any form, with the console's copy keyauth.register.errors.selfRegistrationClosed.formrenders the form, and its sign-in link carries?redirect=.Pins
packages/app-shell/src/console/auth/__tests__/signUpOffer-11691.test.ts. Theirdescribeblock and theconfigForhelper are byte-identical to BASE. That includes the parity case against the spec'sAUDIENCE_POSTURES/audiencePermitsSelfRegistration; app-shell already depends on@objectstack/spec. The 11 rendered console-page pins stay insignUpFollowsPosture-11691.test.tsx, and that block is byte-identical to BASE too. Only the header and the imports changed.defaultPagesFollowPosture-11705.test.tsx. They render the exported pages with real@object-ui/authforms, a realAuthProviderover a realcreateAuthClient, and a realI18nProvider; onlyfetchis a stub.invite_onlywith an owner, there is no "Sign up".open,email_domainand no posture are unchanged and make no probe.disableSignUp: truehides everything, and/registerbounces to/loginkeeping the redirect.Verification (head
54cb036; the type-checks, the builds and the verbose 16-case run atf133f3c, which differs from54cb036only bypackages/app-shell/README.md)git grep) are 17 files and 91 tests green. The full 1029-file app-shell suite is declared to CI: it was stopped locally after 30 minutes holding the shared lock with two agents queued, so it is NOT MEASURED here.apps/console/src/pages/auth/+SetupRoute.test.tsx: 12 files and 82 tests green. That covers the 11 kept posture pins and the 15SetupRoutecases forsetupEntry.ts.type-check: app-shell exit 0 (itstsconfig.test.jsonlists both new test files, and theregisterRefusalCodes-11030positive control is listed too), auth exit 0, and console exit 0. The console was checked after building its own closure: the first run's 23 errors were all five unbuilt plugins, so that run measured nothing.lint: app-shell, console and auth exit 0 with 0 errors. None of their warnings is on a line this PR adds.@object-ui/authand@object-ui/app-shellbuilt. The builtdist/index.d.tscarries the new entry exports listed in the changeset's Clause-② paragraph.audienceAdmitsUninvitedSignUpis indist/but not on the entry.check:i18n-keys,check:phantom-deps,check:self-import,check:unreferenced-sources,check:side-effects-array,check:esm-specifiers,check:spec-symbols,check:published-tsconfig-exclude,check:readme-exports,check:new-line-citations,check:control-bytes,check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:doc-snippets, andcheck-changeset-no-major/-presence/-fixed,check-type-check-coverage,check-vi-mock-override-shape,check-lint-coverage.check:readme-exportsjudged 571 self-imports where it judged 566 before, so the README's five new names are read and real.check:doc-snippetscovers the new README block: a deliberately broken copy of it on disk went red with TS2353 on that block, and was restored by hash.Reverse verification
The fix was committed first (
f133f3c). The mutation wrote BASE's two default pages into the tree, which the marker counts confirm:decideSignUpOffer4/3 at HEAD, 0/0 when mutated. The new pin file then went 7 red, 4 green, exactly the registered prediction. The red ones are: login underinvite_onlywith an owner, with an invitation, and with no owner; register's notice, the invitation flow, the no-owner case, and thedisableSignUpbounce that keeps the redirect. The 4 controls stayed green: login underopen/email_domain, with no posture, and withdisableSignUp, plus register underopen. Restore: both files are back to their HEAD blob hashes, andgit diff HEADis 0 bytes. The tests import the pages by relative path, so the run readsrc/and no rebuild leg was needed.Eager closure (Zone 2 #5)
The console was built twice with
vite buildin this tree, at HEAD and with BASE's sources written back (trap-restored, verified by hash). Both builds have 331 eager chunks. Raw size grew by +4 bytes and gzip size by +77 bytes.check:eager-closurereads 3323.3 KB of 3330.4 KB at HEAD (headroom 7.0 KB), against headroom 7.1 KB at BASE. The move does not grow the closure.Acceptance notes
packages/auth/src/types.ts. ItsaudiencePosturedoc comment pointed atpages/auth/signUpOffer.ts, which this move deletes, so the comment, which ships indist/types.d.ts, now names app-shell'sdecideSignUpOfferinstead. The change is comment-only. Auth was built, type-checked and linted after it./after a successful sign-in or sign-up and never honour?redirect=afterwards. An invitee bounced byDefaultAcceptInvitationPagetherefore registers but lands on/, not back on the invitation. Nothing in this repo mounts both (examples/console-starterhas no/accept-invitationroute), and nothing here changes it. Carrier: none.SetupPage.tsxstill runs its ownbootstrap-statusfetch besideuseBootstrapStatus. That is pre-existing and untouched. Carrier: none.Generated by Claude Code