Skip to content

fix(app-shell): the exported default auth pages follow the audience posture (objectui#11705) - #11711

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11705-default-auth-pages-posture
Oct 6, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11705-default-auth-pages-posture

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11705
Clause-②: yes

What

DefaultLoginPage and DefaultRegisterPage, the auth pages @object-ui/app-shell exports (examples/console-starter mounts them at /login and /register), read emailPassword.disableSignUp alone. Under the default invite_only audience posture they offered "Sign up" and the full form to every visitor, and the server refused the finished form with 403 SELF_REGISTRATION_CLOSED. They now call decideSignUpOffer, the decision the console's own pages have used since objectui#11691 (PR objectui#11703, landed as daa7caf).

The move: one rule, no second copy

  • apps/console/src/pages/auth/signUpOffer.ts moves to packages/app-shell/src/console/auth/signUpOffer.ts. With comments and imports stripped, the code compares equal to BASE.
  • The bootstrap probe it reads moves too: useBootstrapStatus and BootstrapStatus, out of apps/console/src/components/setupEntry.ts, into packages/app-shell/src/console/auth/bootstrapStatus.ts. The function body also compares equal to BASE, and so does its base-URL expression.
  • Both are exported from the app-shell entry. The console's LoginPage, RegisterPage and setupEntry.ts (decideSetupEntry / useSetupEntryMode stay 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 about AuthPublicConfig. It also reads the invitation route /accept-invitation/ID that DefaultAcceptInvitationPage (app-shell) bounces from, and the hasOwner probe, 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 the VITE_SERVER_URL + /api/v1/... convention the probe uses. @object-ui/auth owns 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 grep for bootstrap-status / hasOwner / BootstrapStatus over packages/ returns zero hits. The same query over apps/console/src hits setupEntry.ts, SetupPage.tsx and their tests, which is the control.

Zone 2 #1 confirmed. At BASE the app-shell entry exports LoginPage as DefaultLoginPage and RegisterPage as DefaultRegisterPage from console/auth/, and examples/console-starter/src/App.tsx routes those two at /login and /register. Both pages read cfg?.emailPassword?.disableSignUp === true and nothing else.

The default pages now

  • DefaultLoginPage reads ?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 for form, and the link carries ?redirect=, which is how /register knows 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.
  • DefaultRegisterPage renders nothing until the config is read, as before. closed bounces to /login and keeps ?redirect=. pending renders nothing. by-invitation explains before any form, with the console's copy key auth.register.errors.selfRegistrationClosed. form renders the form, and its sign-in link carries ?redirect=.
  • The README gains a "Default auth pages and the sign-up offer" section: a table of what each page offers per case, and a compiled example of the exported decision.

Pins

  • objectui#11691's 16 pins, none weakened. The 5 decision cases moved with the decision into packages/app-shell/src/console/auth/__tests__/signUpOffer-11691.test.ts. Their describe block and the configFor helper are byte-identical to BASE. That includes the parity case against the spec's AUDIENCE_POSTURES / audiencePermitsSelfRegistration; app-shell already depends on @objectstack/spec. The 11 rendered console-page pins stay in signUpFollowsPosture-11691.test.tsx, and that block is byte-identical to BASE too. Only the header and the imports changed.
  • 11 new rendered pins in defaultPagesFollowPosture-11705.test.tsx. They render the exported pages with real @object-ui/auth forms, a real AuthProvider over a real createAuthClient, and a real I18nProvider; only fetch is a stub.
    • Under invite_only with an owner, there is no "Sign up".
    • An invitation redirect still reaches registration: the link carries the redirect, the form submits, and the sign-up request is recorded.
    • With no owner yet, the link and the form stay.
    • open, email_domain and no posture are unchanged and make no probe.
    • disableSignUp: true hides everything, and /register bounces to /login keeping the redirect.

Verification (head 54cb036; the type-checks, the builds and the verbose 16-case run at f133f3c, which differs from 54cb036 only by packages/app-shell/README.md)

  • app-shell, auth directory: the two new files are 16 of 16 green (verbose run), and with the three existing default-page pins it is 5 files and 30 tests green.
  • app-shell, narrowed suite: the 17 test files that read the barrel or name a touched module (derived by 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.
  • Console, apps/console/src/pages/auth/ + SetupRoute.test.tsx: 12 files and 82 tests green. That covers the 11 kept posture pins and the 15 SetupRoute cases for setupEntry.ts.
  • type-check: app-shell exit 0 (its tsconfig.test.json lists both new test files, and the registerRefusalCodes-11030 positive 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.
  • Builds: @object-ui/auth and @object-ui/app-shell built. The built dist/index.d.ts carries the new entry exports listed in the changeset's Clause-② paragraph. audienceAdmitsUninvitedSignUp is in dist/ but not on the entry.
  • Root gates, all exit 0: 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, and check-changeset-no-major / -presence / -fixed, check-type-check-coverage, check-vi-mock-override-shape, check-lint-coverage. check:readme-exports judged 571 self-imports where it judged 566 before, so the README's five new names are read and real. check:doc-snippets covers 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: decideSignUpOffer 4/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 under invite_only with an owner, with an invitation, and with no owner; register's notice, the invitation flow, the no-owner case, and the disableSignUp bounce that keeps the redirect. The 4 controls stayed green: login under open/email_domain, with no posture, and with disableSignUp, plus register under open. Restore: both files are back to their HEAD blob hashes, and git diff HEAD is 0 bytes. The tests import the pages by relative path, so the run read src/ and no rebuild leg was needed.

Eager closure (Zone 2 #5)

The console was built twice with vite build in 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-closure reads 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

  • File surface. The claim's surface does not include packages/auth/src/types.ts. Its audiencePosture doc comment pointed at pages/auth/signUpOffer.ts, which this move deletes, so the comment, which ships in dist/types.d.ts, now names app-shell's decideSignUpOffer instead. The change is comment-only. Auth was built, type-checked and linted after it.
  • Observation, not filed. The default pages navigate to / after a successful sign-in or sign-up and never honour ?redirect= afterwards. An invitee bounced by DefaultAcceptInvitationPage therefore registers but lands on /, not back on the invitation. Nothing in this repo mounts both (examples/console-starter has no /accept-invitation route), and nothing here changes it. Carrier: none.
  • Observation, not filed. The console's SetupPage.tsx still runs its own bootstrap-status fetch beside useBootstrapStatus. That is pre-existing and untouched. Carrier: none.

Generated by Claude Code

claude added 2 commits October 6, 2026 09:43
…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>
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3323.3 KB 3330.4 KB
Main entry chunk (gzip) 153.6 KB 350 KB
Entry file index-CDPgrLon.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) 17.60KB 6.51KB
app-shell (runtime-config.js) 22.52KB 7.86KB
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) 40.70KB 10.94KB
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) 574.72KB 137.94KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 233.72KB 64.83KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
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) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.18KB 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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.81KB 47.14KB
plugin-dashboard (index.js) 143.75KB 38.87KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 247.23KB 65.04KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.09KB 45.89KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 238.48KB 65.51KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.83KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
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.08KB 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.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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: 54cb036b9634402f444d083afa66ac80a67be544
Local-runs: none

Inputs, read at 2026-10-06T11:06Z: card objectui#11705 (body and its six comments: triage's first grade 6011978580, the unlock 6013024141, the seat's unblock 6013031064, the claim 6013426205, the os-dev-report 6014566552, the seat's ACCEPT 6014641503); PR objectui#11711 (body, the 14-file list, the net diff against main at da354537525d386cb265a6cd89199900f9d654c9, +739/−184); the 43 check-runs on the head. Files at the head and at base were read with git show on a pinned ref; nothing was built, run or re-run.

Check-runs on the head: 43 concluded — 40 success, 3 skipped (Test (coverage), Test (coverage shard), dependabot: matrix and skip-by-design), 0 failed, 0 in progress. Every gate family the dev declared to CI has answered: Test (shard 1/8) through (shard 8/8) (the full 1029-file app-shell suite the dev did not run locally), Test (dist pins), Type Check, Lint, Build & E2E, Changeset Declaration, Changeset Bump Policy, Changeset Claim Re-read, Changeset Fixed Group Check, README Export Check, Doc Snippet Type Check, Docs Route Eager Closure Check, Bundle Analysis, Line Citation Gate, Control Byte Scan, Governed Surface Queue Guard. Nothing is outstanding for the seat's landing check.

① Derived judgments

Each accept-set and public-surface change the diff implies, named right or wrong:

  1. One decision, no second copy — right. apps/console/src/pages/auth/signUpOffer.ts is a git rename to packages/app-shell/src/console/auth/signUpOffer.ts. With comments and imports stripped the two files compare equal (my own read of base and head: True); the four functions, INVITATION_ROUTE_PREFIX, SignUpOffer and SignUpOfferContext are unchanged. The console copy is deleted with no shim, and a grep of the head tree finds no importer of the deleted path: the console's LoginPage, RegisterPage and setupEntry.ts import from @object-ui/app-shell, and both default pages import ./signUpOffer.js. All four pages call the one decideSignUpOffer. This is triage's direction ("move it rather than copy it, ⛔ no second copy") as graded and re-stated at unlock. The posture predicate audienceAdmitsUninvitedSignUp restating the spec's audiencePermitsSelfRegistration predates this PR and keeps its parity pin.
  2. The probe moved unchanged — right. useBootstrapStatus and BootstrapStatus left apps/console/src/components/setupEntry.ts for packages/app-shell/src/console/auth/bootstrapStatus.ts: hook body equal with comments stripped (True), the AUTH_BASE expression and the three-value union byte-equal. import.meta.env.VITE_SERVER_URL is already the same-origin REST convention of a dozen app-shell modules (ConditionalAuthWrapper, marketplaceApi, useConsoleActionRuntime, useInboxBell, …), so the move introduces no new build-time dependency. setupEntry.ts keeps decideSetupEntry and useSetupEntryMode; SetupRoute.test.tsx imports only those two and stubs the probe over global fetch, which the moved hook still uses.
  3. @object-ui/app-shell, not @object-ui/auth — right. The decision reads the route DefaultAcceptInvitationPage bounces from (AcceptInvitationPage.tsx: navigate('/login?redirect=' + encodeURIComponent(location.pathname + location.search))), and that page, the default auth pages, both refusal-copy maps and the REST-probe convention are app-shell's. @object-ui/auth owns the client and the config type and knows no routes.
  4. The entry widening is exactly seven names — right. packages/app-shell/src/index.ts gains decideSignUpOffer, needsBootstrapProbe, isInvitationRedirect, useBootstrapStatus and the types SignUpOffer, SignUpOfferContext, BootstrapStatus; no export is removed and no existing type changes. The types those signatures reach (AuthPublicConfig from @object-ui/auth, BootstrapStatus) are public already or added here. audienceAdmitsUninvitedSignUp is off the entry, and app-shell's package.json exports map is . and ./styles.css only, so dist/console/auth/signUpOffer.js is not a published subpath: the "not published" sentence holds by the exports map, not only by the entry file.
  5. DefaultLoginPage — right against the card's pins. Under invite_only with an owner no "Sign up" link (pinned with the probe answered and settled); an invitation redirect keeps the link, carrying ?redirect=, with no probe; a deployment with no owner keeps the link (objectui#11691 ruling A, the first-owner window, honoured by bootstrap === 'fresh' answering form); open, email_domain and a server that sends no posture are unchanged and make no probe; disableSignUp: true hides the link even from an invitee. Before the config is read, null answers form exactly as base's undefined did; LoginForm renders its fields only after its own read of the same cached /config promise, which settles after the page's .then, so there is no link flicker in the common case.
  6. DefaultRegisterPage — right against the card's pins. Renders nothing before the config is read (as at base); closed bounces to /login and now keeps ?redirect= (base dropped it); by-invitation renders auth.register.errors.selfRegistrationClosed under AuthFormHeader with a sign-in link, before any form (the key is in the en locale pack, and check:i18n-keys is green); form is the real RegisterForm whose loginUrl carries the redirect. The invitation leg is pinned on the wire: from /login?redirect=/accept-invitation/inv_1 through the link to a recorded /sign-up/email body, with zero probes.
  7. The console's own pages after the re-point — right. The diff to signUpFollowsPosture-11691.test.tsx removes only the five decision cases and the imports they needed; the eleven rendered pins and configFor (with its DEV_SEED) are untouched. The five cases reappear in signUpOffer-11691.test.ts with the same configFor and DEV_SEED, so objectui#11691's sixteen pins hold with no assertion weakened. The console pages' gating (hasBootstrapped && !user && needsBootstrapProbe(…), the spinner while user is set, the post-sign-up orchestration) is unchanged.
  8. packages/auth/src/types.ts — right. The hunk is three comment lines on audiencePosture: the path it cited is the file this PR deletes, and it now names app-shell's decideSignUpOffer. No type or value changes. The published-text rule (text a round makes false is fixed in that round) is the correct call, and the seat added the path to the claim's surface.
  9. Eager closure — right. Docs Route Eager Closure Check is green and the budget comment reads 3323.3 KB of 3330.4 KB gzipped across 331 chunks: the decision and the probe were already in the console's closure through the console's own modules, so the move adds only the re-export (+77 B gzip per the dev's A/B build).
  10. One residue, named: a signed-in visitor on the default /register under invite_only without an invitation. The default page gates the probe on !user (as the console page does), so for that visitor bootstrap stays unknown, decideSignUpOffer answers pending, and the page renders the empty AuthPageLayout for as long as it is mounted. At base that visitor got the form (one the server refuses). The README table this PR adds says /register shows "registration is by invitation, before any form" for "invite_only, anyone else", which that visitor does not see. Reach: a hand-typed URL by a signed-in user — examples/console-starter mounts /register unguarded, and no flow in this PR sends a signed-in user there (an invitee who signed up from the invitation leg keeps form, and a fresh answer is kept once received). The console page covers the same visitor with a spinner and its post-sign-up redirect; the default page has no signed-in branch at base or at head. Judged: right in direction for the visitor set the card's pins define (no form is offered where the server would refuse one), incomplete in rendering for the signed-in visitor, who should see the notice or be sent on. The seat recorded it as an Acceptance note; it is escalated under ③ as a follow-up card, and it does not block this head.

② Semver level

  • .changeset/11705-default-auth-pages-posture.md declares '@object-ui/app-shell': minor. Matches what the diff publishes: the entry gains seven names with no removal and no type change (a widening), and two published components' rendered behaviour changes under the default posture, which the changeset spells out in its "Behaviour change" paragraph (no link / notice instead of form under invite_only with an owner; invitation redirect and no-owner deployment keep the offer; only that case makes the extra request; open, email_domain and no posture unchanged; disableSignUp: true hides everything; ?redirect= carried between the two pages; success still navigates to /). Each sentence was checked against the two page files and needsBootstrapProbe and holds. The Clause-② paragraph lists exactly the seven names index.ts exports; the "Not published" sentence matches the exports map. objectui declares no major; Changeset Bump Policy and Changeset Fixed Group Check are green.
  • Not named in the changeset: @object-ui/auth (a doc comment in dist/types.d.ts, no type or value change) and @object-ui/console (source re-pointed, rendered behaviour unchanged, pins byte-identical). Both sit in the one fixed group with @object-ui/app-shell (40 packages, one group), so they version with it, and Changeset Declaration counts the one added file for every guarded source in the diff. Judged: no second entry is owed; the single minor is the right level.
  • The PR body carries Clause-②: yes at the start of its second line; the claim said Clause-②: yes; the changeset says yes (widening). The three agree: a widening, with the behaviour change spelled out.

Clause-②: yes (widening)

③ Boundary flags

Dev deviations (six), each answered:

  1. packages/auth/src/types.ts outside the claim's file surface — answered: the edit is comment-only (judgment ① 8), the seat amended the surface in ACCEPT 6014641503. Closed.
  2. Full 1029-file app-shell suite not run locally — answered by the head's check-runs: Test (shard 1/8) … (shard 8/8), Test (dist pins) and Build & E2E are all success. Closed.
  3. First console type-check and first check:readme-exports were precondition misses, re-run green — answered: Type Check and README Export Check are success on the head. Closed.
  4. PR-body "Verification" heading named one head for readings taken at two — answered: the seat amended it; the body now reads "head 54cb036; the type-checks, the builds and the verbose 16-case run at f133f3c, which differs from 54cb036 only by packages/app-shell/README.md", which the commit list confirms (54cb036 is the README-only commit on top of f133f3c). Closed.
  5. Attribution trailers follow objectui AGENTS.md, not the harness reminder — answered: both commits end with the model-free pair (Claude-Session: and Co-authored-by: Claude), and no model identifier appears in the PR body, the changeset, the README section or any code comment, which is the repo's rule. Closed.
  6. Default pages still navigate('/') after success and ignore ?redirect= — answered: unchanged from base and outside the card's done-when; carried as out-of-scope finding 1 below. Closed for this head.

open_questions: none declared, none found.

Out-of-scope findings (two), and the seat's own observation, answered or escalated:

  • Finding 1 — ?redirect= is dropped after a successful sign-in or sign-up on the default pages. Escalated: this PR's README now documents the invitation path through the default pages, and DefaultAcceptInvitationPage is an exported page a host can mount beside them, so an invitee who registers from the documented leg lands on / instead of back on the invitation. That is a user-visible gap on a published, documented path even though no in-repo producer mounts all three routes. The seat should file it as a card (location packages/app-shell/src/console/auth/LoginPage.tsx and RegisterPage.tsx, producer examples/console-starter/src/App.tsx plus the README's invitation row) rather than hold it as a note. Not blocking this head: the card's done-when is the offer, not the landing.
  • Finding 2 — apps/console/src/pages/auth/SetupPage.tsx keeps its own bootstrap-status fetch beside useBootstrapStatus. Answered: pre-existing, untouched by this diff, a static read. A cleanup card at the seat's discretion; not blocking.
  • Seat's observation — the signed-in visitor on the default /register under invite_only (judgment ① 10). Escalated: file it as a follow-up card against packages/app-shell/src/console/auth/RegisterPage.tsx with producer examples/console-starter/src/App.tsx; the fix is one branch (render the by-invitation notice or bounce when user is set, or let the probe run regardless of user on the default page, where enabled carries none of the /setup policy's load). Not blocking: the card's pins are for a signed-out visitor, and the pre-PR rendering for the signed-in one was a form the server refuses.

Implemented-by: claude/issue-11705-default-auth-pages-posture
Reviewed-by: session_01FngvPpdrnhHMdHHq6vwwju

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 6, 2026 11:08
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 6, 2026 11:08
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit 7e2d5b0 Oct 6, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11705-default-auth-pages-posture branch October 6, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants