Skip to content

feat(auth,console): a new workspace takes its creator's browser timezone (objectui#11908) - #11953

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-11908-create-workspace-timezone
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-11908-create-workspace-timezone

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #11908

Clause-②: yes

A new workspace now takes its creator's browser timezone when it is created, so its first administrator is no longer asked for a zone the browser already knows. Existing workspaces are untouched (「存量不动」).

The wire shape is the one in triage's unlock on the card (comment 6055427516): POST /api/v1/auth/organization/create?timezone=IANA_ZONE, with the body staying { name, slug }. The zone rides the query, never the body.

What changed

  • @object-ui/auth: createOrganization takes an optional timezone.
    • The client (createAuthClient) puts it on the create route's query through better-auth's fetchOptions.query.
    • An absent or empty value sends no parameter at all.
    • AuthProvider passes its input through to the client unchanged.
  • @object-ui/app-shell: CreateWorkspaceDialog reads Intl.DateTimeFormat().resolvedOptions().timeZone once per submit, through a module-private browserTimeZone().
    • Every slug attempt (the generated slug and its collision retries) carries the same timezone.
    • A missing, empty or throwing read sends no timezone key.
  • @object-ui/console: SetupPage's create branch sends the same zone, read through the console's existing browserTimeZone() in pages/settings/workspaceTimezonePrompt.ts, so that module stays the console app's one reader.
  • Changeset: @object-ui/auth minor, @object-ui/app-shell patch, @object-ui/console patch.
  • @object-ui/i18n (test only): machineLocaleCensus-9909.test.ts gains one DECLARED entry for the dialog's host time-zone read (count: 1, verdict not applicable), the twin of the console prompt reader's entry. Nothing is published.

Published declarations that change (for the contract review)

Measured on the built declaration closure: @object-ui/auth built at BASE f3a0488 and at this branch, then diff -r over dist with *.js and *.map excluded. Exactly two .d.ts files differ, and each gains one optional member plus doc text. Nothing else in dist/*.d.ts moves.

Declaration Reached from dist/index.d.ts by data parameter before data parameter after
AuthClient.createOrganization (dist/types.d.ts) export type { AuthClient }; return type of createAuthClient() { name: string; slug: string; logo?: string } { name: string; slug: string; logo?: string; timezone?: string }
AuthContextValue.createOrganization (dist/AuthContext.d.ts) export type { AuthContextValue }; return type of useAuth() { name: string; slug: string; logo?: string } { name: string; slug: string; logo?: string; timezone?: string }
  • The return type is unchanged in both (a promise of AuthOrganization).
  • No export is added or removed, so check:readme-exports is not implicated.
  • The widening is additive:
    • an existing caller that passes no timezone compiles and sends exactly what it sent before;
    • an existing implementer whose function accepts the narrower input stays assignable, because its parameter still accepts the wider object.
  • pnpm --filter @object-ui/app-shell type-check and pnpm --filter @object-ui/console type-check both compile every test double in the tree that is typed as AuthClient, and both are green.

Measurements (dispatch Zone 2)

  1. The better-auth client can carry a query without touching the body. Confirmed on the installed better-auth 1.7.2: its client proxy splits fetchOptions and query off the argument before building the body.
    • createOrganization-timezone-11908.test.ts drives the real client through a stubbed fetch. It reads url.search === '?timezone=Asia%2FShanghai' and body keys exactly name, slug.
    • The control (zone omitted or empty) reads url.search === '' and the same two body keys.
    • The server side: better-auth's own /organization/create endpoint declares a body schema and no query schema. So a server without the cloud half ignores the parameter, which matches triage's "harmless before production" reading.
  2. The published reach is the table above: two declarations, so Clause-②: yes stands.
  3. The zone read is the same expression as the console's browserTimeZone(). packages/app-shell cannot import apps/console, so the dialog carries its own module-private copy, and no new export was added for it.
  4. The prompt. No prompt change was needed: a seeded zone has a non-default source, and findTimezoneOffer returns null for any non-default source.
    • Unit reading: WorkspaceTimezonePrompt.test.tsx passes "tenant-sourced timezone → no prompt, no write", along with its global and env variants.
    • ⚠ NOT MEASURED on a live server: this checkout cannot run the cloud half, and the scope at which the cloud half writes its seed (tenant was assumed) was not read. A real-browser reading is owed by whoever can run cloud staging.

Tests (all at HEAD 5bf108f)

New pins:

  • packages/auth/src/__tests__/createOrganization-timezone-11908.test.ts: the wire. Query ?timezone=Asia%2FShanghai, body keys exactly name, slug, a logo still in the body, and the controls for an omitted and an empty zone.
  • packages/app-shell/src/console/organizations/__tests__/CreateWorkspaceDialog.test.tsx: the zone is stubbed at Intl.DateTimeFormat.prototype.resolvedOptions, so the suite no longer depends on the machine's zone.
    • The create call is strictly { name, slug, timezone }, and the collision retry carries the same zone.
    • Controls: no zone, an empty zone and a throwing read each send no timezone key.
    • Two existing pins now include the zone in their expected argument.
  • apps/console/src/pages/auth/__tests__/setupCreateTimezone-11908.test.tsx: the wizard's create branch, with the same pins and controls. Fake setTimeout covers the poll's pauses.

Runs (repo root, through the shared verify lock, exit codes captured before any pipe):

Command Exit Reading
pnpm exec vitest run packages/auth/ 0 29 files, 289 tests passed
pnpm exec vitest run packages/app-shell/src/console/organizations/ packages/i18n/src/__tests__/residue-namespaces-3546.test.tsx 0 15 files, 178 tests passed
pnpm exec vitest run apps/console/src/pages/auth/ apps/console/src/pages/settings/ apps/console/src/components/SetupRoute.test.tsx 0 27 files, 195 tests passed
pnpm --filter @object-ui/auth type-check 0 script name echoed; --listFiles includes the new test
pnpm --filter @object-ui/app-shell type-check 0 script name echoed; --listFiles includes the dialog test
pnpm --filter @object-ui/console type-check 0 script name echoed; --listFiles includes SetupPage.tsx and the new test
pnpm exec eslint on the 9 touched .ts/.tsx files 0 0 errors; per-file problem counts identical to BASE (createAuthClient.ts 21 → 21, all pre-existing no-explicit-any warnings)
pnpm check:control-bytes, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check:new-line-citations, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:phantom-deps 0 each each printed its OK line; new-line-citations reads "0 new citation(s)"
node scripts/check-changeset-presence.mjs, node scripts/check-changeset-no-major.mjs 0 each "9 source file(s) of 3 released package(s) changed … declares 1 changeset(s)"; "No changeset declares a major bump."

Declared narrowings:

  • app-shell tests. The package has over a thousand test files, too many for one foreground run on the shared box. The run covers the changed file's folder plus every test file in the tree that names CreateWorkspaceDialog, enumerated by git grep -l CreateWorkspaceDialog -- '**/*.test.*'. app-shell's published surface is unchanged: the dialog's props are the same and the new helper is module-private. That enumeration is blind to tree-wide disk scanners such as machineLocaleCensus-9909, which read app-shell's source from packages/i18n; CI caught it, and round 2 declares the site.
  • console tests. Scoped to the touched area (pages/auth), the prompt module's folder (pages/settings) and SetupRoute, which mounts SetupPage.
  • eslint. The population is **/*.{ts,tsx} per eslint.config.js. 9 files were linted, a count read from --format json. Type-aware linting is not enabled (no parserOptions.project or projectService), so this diff cannot move a verdict on any untouched file.
  • The full farm is CI's.

Round 2 at HEAD 7f443ba: the census plus the three pins (4 files, 26 tests) pass, and pnpm --filter @object-ui/i18n type-check exits 0. Removing the new census entry through ablation-replace turns exactly the census's 'no call site passes nothing…' test red, with the dialog's line as the only received entry. The restore was proven: blob ccd30a3ae311 == HEAD.

Reverse verification

Each leg was run through ablation-replace from the committed state. Each mutation was proven on disk by its anchor count and a blob change, and each restore was proven by blob equal to HEAD with git diff HEAD empty.

Leg Mutation Pin result Restore
1 drop the client's fetchOptions.query wiring 2 failed, 2 passed. The zone case reads "expected '' to be '?timezone=Asia%2FShanghai'"; the omitted and empty controls stay green, as predicted blob 631e7ae6c6e7 == HEAD
2 the dialog passes undefined instead of browserTimeZone() 3 failed, 8 passed: the zone pin, the retry pin and the first-path pin blob f102cb5d504c == HEAD
3 SetupPage drops the timezone spread 1 failed, 3 passed: the zone pin red, the three controls green blob 18ccaaa34618 == HEAD

All three mutated files are read from src by their pins (relative imports; @object-ui/auth is mocked at the call sites), so no dist rebuild was involved.

Eager closure (the console's first-load budget)

Bundle Analysis is red on main already: the console's eager closure is over its 3307.0 KB ceiling there, tracked by the P0 card objectui#11937. This PR does not touch the budget script or any ceiling. This change's own delta, measured locally with a console vite build plus pnpm check:eager-closure on BASE f3a0488 and on this branch:

eagerGzipBytes gate line
BASE f3a0488 3,386,819 3307.4 KB across 336 chunks
this branch 3,386,918 3307.5 KB across 336 chunks
delta +99 bytes gzipped (+254 raw) eager chunk count unchanged

The delta lands mainly in index (+86) and infrastructure (+26). The other chunks move by 1 to 3 bytes, from hashed chunk-name references.

After main took the rollback (3c888c6, objectui#11956) and this branch merged it (9c77741d), the budget bot reads 3306.7 KB across 336 chunks against a 3312.0 KB budget: PASS (comment 6058417056). The first paragraph's 'red on main already' describes the base before the rollback.

Acceptance notes

  • SetupPage's usual path does not create a workspace, so it sends no zone. It renames the organization the server bootstrapped at sign-up (updateOrganization). Only when no bootstrap organization appears does it reach the create branch this PR changes. So on a fresh self-hosted deployment, the first owner's bootstrap organization keeps the server default, and the prompt still asks there. That organization is created server-side with no client request this card covers. Whether the ruling reaches it is left to the seat (report open_questions). ⛔ Not changed here.
  • The prompt module's doc says browserTimeZone() is "the console's ONE reader of the browser's zone". That stays true of apps/console: SetupPage reuses it. packages/app-shell's dialog now holds its own module-private reader, because it cannot import the console. The sentence is narrower than it may read. Carrier: none (the prompt module is not on this claim's surface). Noted, not carded.
  • A metadata action can also create organizations. ExpressionProvider mentions sys_organization.create_organization, a metadata action owned outside this repository. If that action creates organizations, it does not carry the zone. Not measured from here. Noted, not carded.
  • Real-browser reading not taken. The acceptance line (a zh-CN user on Asia/Shanghai creates a workspace, and the environment reads localization.timezone = Asia/Shanghai with no prompt) needs the cloud half's server, which is not installable here.

Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 (os-dev for domain:ui#3, dispatched under that session).


Generated by Claude Code

claude added 3 commits October 8, 2026 09:16
…eate query (objectui#11908)

The create route reads the creator's IANA zone from `?timezone=` and keeps
the body `{ name, slug }`. `createOrganization`'s input gains an optional
`timezone` on `AuthClient` and `AuthContextValue`; the client lifts it onto
the query through better-auth's `fetchOptions.query`. An absent or empty
zone sends no parameter.

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

`CreateWorkspaceDialog` (every slug attempt) and `SetupPage`'s create branch
pass the browser's `Intl.DateTimeFormat().resolvedOptions().timeZone` as
`createOrganization`'s `timezone`. A missing, empty or throwing read sends
no key at all.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…bjectui#11908)

`@object-ui/auth` minor (an additive optional member on two published
declarations); `@object-ui/app-shell` and `@object-ui/console` patch.

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

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3307.5 KB 3307.0 KB
Main entry chunk (gzip) 71.4 KB 350 KB
Entry file index-C15WkNQ0.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ✅ pass
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.82KB 6.58KB
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.28KB 141.14KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 266.92KB 67.47KB
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) 38.37KB 10.31KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
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.39KB 15.52KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.82KB 39.17KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 248.06KB 68.77KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 117.45KB 29.33KB
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.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
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

…chine-locale census (objectui#11908)

`CreateWorkspaceDialog` reads `Intl.DateTimeFormat().resolvedOptions().timeZone`
to seed a new workspace's zone. That is the host zone, the one member of
`resolvedOptions()` the locale does not decide, and nothing is formatted:
the same verdict the census already gives the console's prompt reader,
which `packages/app-shell` cannot import.

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

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3307.5 KB 3307.0 KB
Main entry chunk (gzip) 71.4 KB 350 KB
Entry file index-C15WkNQ0.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ✅ pass
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.82KB 6.58KB
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.28KB 141.14KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 266.92KB 67.47KB
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) 38.37KB 10.31KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
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.39KB 15.52KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.82KB 39.17KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 248.06KB 68.77KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 117.45KB 29.33KB
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.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
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: 7f443ba308ff9c7951ab1ad650cd7f81732cd5b5
Local-runs: none

Inputs: card objectui#11908 (body; comments 6055427516, 6056548803, 6057204057, 6057250297, 6057425342, 6057502960), PR objectui#11953 (body, 11-file list, net diff f3a0488..7f443ba) and the 43 check-runs on the head (one red, Bundle Analysis; three skipped, coverage x2 and dependabot; the rest green). Read-only: gh api reads and git diff / git show on fetched refs. The head has not moved since the brief, and its merge-base with main is the PR's base f3a0488.

① Derived judgments

Published surface, @object-ui/auth:

  • AuthClient.createOrganization (packages/auth/src/types.ts): the data parameter gains timezone?: string. Reached from the entry, packages/auth/src/index.ts:101 exports type AuthClient. Additive widening, return type unchanged. Right against the card's scope item 2 as amended by the unlock 6055427516 ("whatever optional input carries it to that query, not a body field").
  • AuthContextValue.createOrganization (packages/auth/src/AuthContext.ts): the same widening, reached via index.ts:118 exports type AuthContextValue. Right.
  • Existing implementers stay assignable: a function typed on the narrower input still accepts the wider object (the new member is optional), and the one non-test double in the tree, the throwing stub at useAuth.ts:71, takes no parameter at all. Type Check and Test (dist pins) are green on the head. No export is added or removed (README Export Check green), and packages/auth/README.md does not document createOrganization, so no doc drifts.

Wire, the accept-set of POST /organization/create:

  • createAuthClient.ts lifts timezone into better-auth's fetchOptions.query and leaves the body { name, slug, logo }. A missing or empty zone adds no fetchOptions at all. Right against the unlock's shape (?timezone=IANA, body unchanged, optional). Pinned through the real client in createOrganization-timezone-11908.test.ts: query ?timezone=Asia%2FShanghai, body keys exactly name, slug, a logo still in the body, and the omitted and empty controls send no parameter. Test shards 1 to 8 are green on the head.
  • AuthProvider.createOrganization passes data through unchanged. Right.

Call sites:

  • CreateWorkspaceDialog.tsx (app-shell): a module-private browserTimeZone() with the same expression and the same empty/throw handling as the console's reader, read once per submit; every slug attempt, the generated slug and its collision retries, carries the same timezone, and none carries the key without a zone. The dialog's props are unchanged, so app-shell publishes no declaration change. Right; pinned in CreateWorkspaceDialog.test.tsx with the reader stubbed at Intl.DateTimeFormat.prototype.resolvedOptions (strict { name, slug, timezone }, the retry carries the zone, controls for none, empty and throw).
  • SetupPage.tsx (console): the create branch reuses the console's browserTimeZone() from pages/settings/workspaceTimezonePrompt.ts; the usual rename branch (updateOrganization) is untouched. Right for the two call sites the card names; the rename path is the seat-answered open question in ③. Pinned in setupCreateTimezone-11908.test.tsx with the same controls.
  • machineLocaleCensus-9909.test.ts: one DECLARED entry for the dialog's read (count: 1, not applicable), the twin of the console reader's entry. Test-only, publishes nothing. Right; it is what turned shard 7 green between 5bf108f and 7f443ba.

Prompt, card scope item 3: no code change. The guard at workspaceTimezonePrompt.ts:204 returns null unless resolved.source === 'default', and the existing pin at WorkspaceTimezonePrompt.test.tsx:220 ("a chosen zone is never asked about", an it.each over the tenant, global and env sources) reads a tenant-sourced zone as no prompt and no write. Right as a unit reading; the live reading the card asks for is escalated in ③.

Bundle Analysis, red: judged not this diff's contract. The only failing half is the aggregate ceiling (3307.5 KB over 3307.0 KB, 336 chunks); per-chunk ceilings, membership, headroom and ceiling freshness against the base branch all pass. The PR's 11 files touch no budget script, workflow or ceiling. The import graph on the head shows the diff adds no module to the closure: App.tsx imports SetupRoute, which imports SetupPage, so that page was already eager, and AppContent.tsx imports WorkspaceTimezonePrompt.tsx, which imports workspaceTimezonePrompt.ts, so the module SetupPage newly imports was already eager too. The runtime additions are one six-line function, two conditional spreads and one local. The dev's two-sided build (PR body) reads +99 bytes gzipped over a base already at 3307.4 KB; this record has no independent base reading within its inputs, but a delta of that size cannot be what carried the closure 550 bytes over 3,386,368. The red is inherited from main (tracked on objectui#11937 per the card thread) and is escalated in ③, not cleared here.

② Semver level

Changeset .changeset/11908-create-workspace-timezone.md: @object-ui/auth minor, @object-ui/app-shell patch, @object-ui/console patch.

  • minor on @object-ui/auth matches what the diff publishes: two declarations widen additively and one optional wire parameter appears; nothing is removed or narrowed, so not major. Right.
  • app-shell and console publish no declaration change, so patch is acceptable, and under .changeset/config.json all three packages sit in the one fixed group, so the group publishes the minor whatever these two declare. Right. (Changeset Bump Policy on the head is the no-major rule only; Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check and Changeset Overwrite Report are all green.)
  • @object-ui/i18n has no entry: its only change is under src/__tests__, which ships nothing. Right.
  • Clause-②: line: the PR body carries Clause-②: yes at line start, matching the claim 6056548803 and the measured reach (exactly dist/types.d.ts and dist/AuthContext.d.ts). Right. The stale Clause-②: no in the dispatch's "Common terms" block was flagged by the dev and owned by the seat in 6057250297; the claim governs.

③ Boundary flags

  1. open_questions[0], the self-hosted bootstrap organization (SetupPage's rename path sends no zone, so a fresh self-hosted deployment's first owner still gets the one-time prompt). Answered by the seat in 6057250297: A for this card; the card names two client call sites and the unlock keeps both; option C (the server bootstrap reads the zone at sign-up) is the home if pull appears, through the objectstack lane. Consistent with the card's scope and the ruling as carried; stands.
  2. Dev flag "NOT MEASURED on a live server" (PR body, Measurements 4). Card scope item 3 says "Measure it; do not assume it", and acceptance line 1 walks a zh-CN user on Asia/Shanghai from sign-up to an environment reading Asia/Shanghai with no prompt on either surface. Not answered in the thread. Escalated to the seat: the diff is right without a prompt change and is harmless before production (a server that ignores the parameter leaves today's behaviour), so the PR can land; but the card's acceptance is not met by this PR alone. The live reading on cloud staging, both surfaces and the scope the cloud half writes its seed at, is owed before the card closes, and if a surface still asks, scope item 3's narrowing becomes a follow-up on this card.
  3. Dev flag, the prompt module's "ONE reader" sentence (workspaceTimezonePrompt.ts:104): still literally true of apps/console, since SetupPage reuses it; app-shell's copy is module-private and declared in the census. Answered: no action, noted and not carded, as the dev wrote.
  4. Dev flag, the sys_organization.create_organization metadata action: owned outside objectui; nothing in this diff can carry a zone through it, and a creation through it keeps the server default, which is today's behaviour. Answered: no regression and no carrier here; it stays an observation.
  5. Dev deviation, the declared test narrowings: the full farm ran on the head (shards 1 to 8, dist pins, Build & E2E and Live E2E all green), which closes the blind spot round 1 owned.
  6. Bundle Analysis red on the head: an inherited aggregate overage (①). Escalated to the seat: this record does not clear a red required check; whether the queue admits a head whose only red is the inherited aggregate is the maintainer's call under the tracking card objectui#11937, outside this record's inputs.
  7. The PR is still a draft; marking it ready is the seat's act, not a contract matter.

Implemented-by: claude/issue-11908-create-workspace-timezone
Reviewed-by: session_01CGZy1BGCjdN5cXqL9cnvB8

VERDICT: PASS

Takes the eager-closure rollback (objectui#11937, #11956) so Bundle Analysis
re-runs against the rolled-back base. No conflicts; no file of this branch
is touched by the merged range.

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

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3306.7 KB 3312.0 KB
Main entry chunk (gzip) 71.4 KB 350 KB
Entry file index-C63evaGV.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.82KB 6.58KB
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.28KB 141.14KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 266.92KB 67.47KB
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) 38.37KB 10.31KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
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.39KB 15.52KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.82KB 39.17KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 248.06KB 68.77KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 117.58KB 29.37KB
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.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
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: 9c77741d92887cc55434f1bfc4fc615f0804ef43
Local-runs: none

Inputs: card objectui#11908 (body; comments 6055427516, 6056548803, 6057204057, 6057250297, 6057425342, 6057502960, 6057960053, 6058437332), PR objectui#11953 (body, 11-file list, net diff against main at the merge-base 3c888c6e, which is the PR's base) and the 43 check-runs on the head: 40 success, 3 skipped (Test (coverage), its shard template, dependabot), 0 failure; the combined status (Vercel) is success. Read-only: gh api reads, one git fetch of the PR head into a review ref, and git diff / git show / git grep on fetched refs. The head has not moved since the brief.

Re-record context, verified rather than inherited: this head is 7f443ba3 plus one merge commit whose parents are 7f443ba3 and main 3c888c6e. The PR's 11 files and the 15 files main moved between f3a0488 and 3c888c6e are disjoint. git diff f3a0488..7f443ba3 is byte-identical to git diff 3c888c6e..9c77741d, and git diff f3a0488..3c888c6e is byte-identical to git diff 7f443ba3..9c77741d: the merge carries no edit of its own. So the diff judged below is the one record 6057929091 judged, now against a new base and with new gate readings; every judgment is re-made here, not copied.

① Derived judgments

Published surface, @object-ui/auth:

  • AuthClient.createOrganization (packages/auth/src/types.ts:395): the data parameter gains timezone?: string. Reached from the entry: packages/auth/src/index.ts:101 exports type AuthClient. Additive widening, return type unchanged. Right against the card's scope item 2 as amended by the unlock 6055427516 ("whatever optional input carries it to that query, not a body field").
  • AuthContextValue.createOrganization (packages/auth/src/AuthContext.ts:144): the same widening, reached via index.ts:118 exports type AuthContextValue. Right.
  • Implementers: AuthProvider.tsx:846 widens its parameter identically and passes data to the client unchanged; the only other non-test implementer, the throwing default at useAuth.ts:71, takes no parameter. A function accepting the narrower object stays assignable to the wider property type because the new member is optional. Type Check, Test (dist pins) and README Export Check are green on the head; no export is added or removed.

Wire, the accept-set of POST /organization/create:

  • createAuthClient.ts:831-836 destructures timezone and sends it only as fetchOptions: { query: { timezone } } when truthy; the body stays { name, slug, logo }. An absent or empty zone adds no fetchOptions, so no parameter. Right against the unlock's shape (?timezone=IANA, body unchanged, optional). Pinned through the real better-auth client (^1.7.2) over a stubbed fetch in createOrganization-timezone-11908.test.ts: url.search exactly ?timezone=Asia%2FShanghai, body keys exactly name, slug, a logo still in the body, and the omitted and empty controls send no parameter. Test shards 1 to 8 are green on the head.
  • The client does not judge the zone's validity; the unlock says the server records nothing for an invalid value and keeps UTC, and the type doc says so. Right: the server judges.

Call sites:

  • CreateWorkspaceDialog.tsx (app-shell): a module-private browserTimeZone() (try/catch; empty or non-string reads as undefined), read once per submit at :203 and passed into createWithGeneratedSlug, which spreads timezone into every attempt, the generated slug and its collision retries, and adds no key without a zone. The dialog's props are unchanged, so app-shell publishes no declaration change. Right; pinned in CreateWorkspaceDialog.test.tsx with the reader stubbed at Intl.DateTimeFormat.prototype.resolvedOptions (strict { name, slug, timezone }, the retry carries the zone, controls for none, empty and throw).
  • SetupPage.tsx (console): the create branch only, the else of the bootstrap rename, reads the console's existing browserTimeZone() from pages/settings/workspaceTimezonePrompt.ts and spreads it conditionally; the rename path (updateOrganization) is untouched. Right for the two call sites the card names; the rename path is the seat-answered open question in ③. Pinned in setupCreateTimezone-11908.test.tsx with the same controls.
  • machineLocaleCensus-9909.test.ts: one DECLARED entry for the dialog's read (count: 1, not applicable), the twin of the console reader's entry. Test-only, publishes nothing. Right; Test (shard 7/8) is green on this head.

Prompt, card scope item 3: no code change. The guard at workspaceTimezonePrompt.ts:204 returns null unless resolved.source === 'default' and not locked, and the existing pin in WorkspaceTimezonePrompt.test.tsx reads a tenant-sourced zone as no prompt and no write. Right as a unit reading; the live reading is flag 2 in ③.

Eager closure, Bundle Analysis: green on this head. The budget bot (PR comment 6058417056) reads 3306.7 KB across 336 chunks against a 3312.0 KB budget, PASS, where on 7f443ba3 it read 3307.5 KB against 3307.0 KB. The closure moved with the merged main range (3c888c6e carries the objectui#11937 rollback, .changeset/11937-eager-budget-rollback.md), and neither number moved with this diff: none of the PR's 11 files is a budget script, workflow or ceiling, so the budget the bot reads on this head is main's, and the module SetupPage newly imports was already eager (AppContent.tsx:17 imports WorkspaceTimezonePrompt.tsx, which imports workspaceTimezonePrompt.ts at :57), as was SetupPage itself (App.tsx:48 imports SetupRoute, SetupRoute.tsx:23 imports SetupPage). The dev's two-sided reading of +99 bytes gzipped stands as the dev's; this record's own reading is the gate's conclusion. Record 6057929091's escalation 6 (the inherited red) is closed by the gate itself.

② Semver level

Changeset .changeset/11908-create-workspace-timezone.md: @object-ui/auth minor, @object-ui/app-shell patch, @object-ui/console patch.

  • minor on @object-ui/auth matches what the diff publishes: two declarations widen additively and one optional wire parameter appears; nothing is removed or narrowed, so not major, and more than patch. Right.
  • app-shell and console publish no declaration change (the dialog's props are unchanged; SetupPage is an app page), so patch is acceptable; all three sit in the one fixed group of .changeset/config.json, so the group publishes the minor whatever these two declare. Right. Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check and Changeset Overwrite Report are all green on the head.
  • @object-ui/i18n has no entry: its only change is under src/__tests__, which ships nothing. Right. The merged range adds .changeset/11937-eager-budget-rollback.md (app-shell patch); that is main's own declaration, not this PR's, and it names none of this PR's files.
  • Clause-②: line: the PR body carries Clause-②: yes at line start, matching the claim 6056548803 and the dev's measured reach (exactly dist/types.d.ts and dist/AuthContext.d.ts). Right. The stale Clause-②: no in the dispatch's common-terms block was flagged by the dev and owned by the seat in 6057250297; the claim governs.

③ Boundary flags

  1. open_questions[0], the self-hosted bootstrap organization (repeated verbatim in the round-1, round-2 and round-3 reports): SetupPage's rename path sends no zone, so a fresh self-hosted deployment's first owner still gets the one-time prompt. Answered by the seat in 6057250297: A for this card (the card names two client call sites and the unlock keeps both); option C, the server bootstrap reading the zone at sign-up, is the home if pull appears, through the objectstack lane; not filed. Consistent with the card's scope; stands. The round-3 repetition adds no new fact.
  2. Dev flag "NOT MEASURED on a live server" (PR body, Measurements 4 and Acceptance notes). Escalated by record 6057929091; answered by the seat in the ACCEPT 6057960053: Fixes #11908 is kept, the live reading is recorded as NOT MEASURED, and a prompt on a seeded workspace after deploy is a new card. This record names the residual as a fact: the card's acceptance line 1 (a zh-CN user on Asia/Shanghai, no prompt on either surface, the environment reading Asia/Shanghai) and scope item 3's "measure it; do not assume it" are not measured by this PR, and merging closes the card through Fixes with that line unmeasured. That is the seat's call, made in writing, not a contract defect of this diff: the diff is right without a prompt change and is harmless against a server that ignores the parameter. Answered; the residual is recorded.
  3. Dev flag, the prompt module's "ONE reader" sentence (workspaceTimezonePrompt.ts:103): still true of apps/console, since SetupPage reuses it; app-shell's copy is module-private and declared in the census. Answered: no action, as the dev and the seat wrote.
  4. Dev flag, the sys_organization.create_organization metadata action: owned outside objectui; nothing in this diff can carry a zone through it, and a creation through it keeps the server default, which is today's behaviour. Answered: no regression and no carrier here.
  5. Round-3 deviation, re-runs without the verify lock (two locked attempts timed out; the locked readings are NOT MEASURED): the full farm on this head is green (Test (shard 1/8) to (8/8), Test (dist pins), Build & E2E, Live E2E, Type Check, Lint), and those are the readings that count. Answered.
  6. Round-3 deviation, the merge commit: verified above as a pure merge of 3c888c6e onto 7f443ba3 (first parent 7f443ba3, so no rebase and no force-push; no edit of its own; file overlap with this PR empty). Answered.
  7. Record 6057929091's escalation 6, Bundle Analysis red: closed; the check is green on this head (①). Nothing is escalated from this record.
  8. The PR is still a draft; marking it ready is the seat's act, not a contract matter.

Implemented-by: claude/issue-11908-create-workspace-timezone
Reviewed-by: session_01CGZy1BGCjdN5cXqL9cnvB8

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 11:34
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 11:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit dfa15c8 Oct 8, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11908-create-workspace-timezone branch October 8, 2026 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants