Skip to content

fix(plugin-grid): the import preview checks a time column and the user import email the way the server does (objectui#11913) - #11973

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-11913-import-time-identity-email
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-11913-import-time-identity-email

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #11913

Clause-②: yes

The Import Wizard's preview marks a cell exactly where the server's import refuses it, because the import button counts the rows nobody marked (objectui#11814, objectui#11889). This PR extends the arms objectui#11889 left in checkImportCell, without forking them, to cover the two columns the card measured unchecked.

Implemented by the os-dev session dispatched by the domain:ui seat 3 PM, session https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8.

What changes

1. A time column is checked (invalid_time).

  • checkImportCell gains a time arm right after the date / datetime arm, the order the server's coerceFieldValue runs them in. The arm asks isImportableTimeCell, a new mirror in importCoercionContract.ts beside isImportableDateCell.
  • The mirror reads a cell the way core's parseDateCell(cell, 'time') reads it in the published 17.7.0. It asks readTimeOfDayCell first, then readYearFirstCell alone, never readIsoTemporalCell. readTimeOfDayCell asks core's comparand rule, which takes two forms:
    • a wall clock that the spec's ClockTimeValueSchema takes;
    • an ISO 8601 day or date-time on a real calendar day whose instant has a four-digit UTC year.
  • Two sources. The wall-clock grammar is imported from @objectstack/spec/data (ClockTimeValueSchema), not restated, because the server reads that same schema. The ISO and year-first readings reuse the file's existing private grammar: ISO_TEMPORAL_CELL, YEAR_FIRST_CELL, namesRealCalendarDay, isWallClock and zonedCellUtcYear.
  • The sentence. A refused cell reads grid.import.invalidTime (en: "{{value}}" is not a valid time). The same sentence renders an invalid_time row error from the server's dry run (formatDryRunError).
  • The write door agrees. objectql's record-validator.ts at 17.7.0 judges a written time by the same core rule (isUninterpretableTemporalComparand). So a time the coercion stores is never refused after it.

2. The user import's email column is checked by the user import endpoint's rule (INVALID_EMAIL).

  • identityImportFields types the column email, where it was text, and flags it emailRule: 'identity'.
  • checkImportCell's email arm asks isIdentityEmail for a flagged field and isRecordEmail for any other. isIdentityEmail mirrors plugin-auth's resolveRowIdentity at the @objectstack/plugin-auth@17.7.0 tag, which refuses in two cases:
    • isLikelyEmail refuses the cell. It takes at most 254 characters of printable ASCII with no whitespace, one @ that is neither first nor last, and a domain whose last dot is neither its first nor its last character.
    • isPlaceholderEmail takes it: an address on placeholder.invalid, in any case.
  • Replaced, not added. The identity rule replaces the record rule on that column, because neither rule is stricter. The endpoint takes a@b..c, which the record validator refuses. It refuses 735431496@柴仟.com, which the record validator takes. A plain email field keeps the record rule exactly as before.

3. The count. The button counts unmarked rows (objectui#11814), so both new marks move it. Each leg is pinned through the real wizard: Import 1 Row on five time rows, and on three identity email rows.

Widenings (Clause-②)

  • @object-ui/plugin-grid (minor). ImportWizardProps['fields'][number] gains emailRule?: 'identity', which is optional and a single literal. Left out, an email column keeps the record validator's rule.
    • This is the only change reachable from the package's built entry. The built index.d.ts re-exports ImportWizard, and the types ImportWizardProps and ImportResult. The new mirrors (isImportableTimeCell, isIdentityEmail) and the widened CellRefusal code union are module-private, and the package exports map only . and ./style.css.
    • Measured on the rebuilt dist: emailRule?: 'identity' appears in dist/ImportWizard.d.ts, and dist/index.d.ts names neither new mirror.
    • Why one literal and not 'record' | 'identity': an explicit 'record' would be a second spelling of leaving the key out.
  • @object-ui/i18n (minor). A new key, grid.import.invalidTime, with a {{value}} placeholder, is added beside grid.import.invalidDate in all ten packs: en, zh, ja, ko, de, es, fr, pt, ru, ar. It is mirrored in the wizard's IMPORT_DEFAULT_TRANSLATIONS, which ImportWizard.i18n-parity.test.ts pins to the en pack.
  • @object-ui/app-shell (patch). No reachable change: identityImportFields is not re-exported from the package entry.

Changeset: .changeset/11913-import-time-identity-email.md.

File surface

  • Sources. Claim 6059931513, plus amendment 6060072703, which adds importCoercionContract.ts and its test.
  • importCoercionContract.ts. It changes in the regions the amendment names: the time reader beside isImportableDateCell, isIdentityEmail beside isRecordEmail, and the doc comments (isRecordEmail's, which said the user import's column "reaches the wizard typed text", and the module header). ⚠ One more region, named here so the review can rule on it: the year-first tail of the private importDateCellYear moved into a new private yearFirstCellYear. The date and time readers now share one year-first reading instead of keeping two copies. The behaviour is unchanged: the existing isImportableDateCell pins pass unmodified.
  • packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts. This is a test beside the packs. Its de census pins (correctly paired spans, and the „ / “ counts) move from 75 to 76, because the new de row adds one matched pair. The history comment is extended in the file's own style.
  • identityImportFields' return type stays the narrow shape. The email entry is typed at its literal against ImportWizardProps['fields'][number], so emailRule is still checked where it is written.
    • Why: ObjectView passes identityImportFields(...) or importTargetFields(...) into fields through one conditional. That expression compiles only because subtype reduction collapses it to the identity shape. importTargetFields' ImportTargetField.options?: unknown is not assignable to the wizard's options.
    • Measured: typing the return as ImportWizardProps['fields'], or as a Pick that names emailRule, turns pnpm --filter @object-ui/app-shell type-check red at that conditional (TS2322). Both files are outside the surface, so this is recorded under Acceptance notes.

Verification, at HEAD 551e5ae8 (after merging main 0bbb67b9)

  • Mirror against the server (one-off probe, not committed).
    • Time: isImportableTimeCell was compared with the installed @objectstack/core 17.7.0's exported parseDateCell(cell, 'time') over 1592 cells. These are the card's cells, 7 days × 11 clocks × 10 zones × 2 separators, bare days, the year-first forms and assorted prose. The server takes 281 of them, and the two disagree on 0.
    • Identity email: isIdentityEmail was compared with the two 17.7.0 functions, restated verbatim from the tag because plugin-auth is not installed here. 19 cells, 0 disagreements.
  • pnpm exec vitest run packages/plugin-grid/ packages/i18n/: Test Files 277 passed (277), Tests 3153 passed | 13 skipped (3166).
  • pnpm exec vitest run on the four app-shell import files (identityImportFields.emailRule-11913, identityImport, identityImportSavedMapping, importTargetFields) plus ObjectHooksPanel.newHookTarget-11820 (the test objectui#11986 fixed on main): Test Files 5 passed (5), Tests 31 passed (31).
    • ⚠ Declared narrowing: app-shell has 1172 test files, past the foreground cap. These four are the files that import identityImportFields or the identity wrapper, or derive the generic target set. CI runs the whole suite.
  • After turbo run build --filter='@object-ui/app-shell^...' (28/28): pnpm --filter @object-ui/plugin-grid type-check, pnpm --filter @object-ui/i18n type-check and pnpm --filter @object-ui/app-shell type-check all exit 0. Each script name is echoed, and each runs both tsc --noEmit and tsc -p tsconfig.test.json. The test configs include src/**/*.test.tsx, so the new test files are compiled too.
  • pnpm exec eslint on the 17 touched TypeScript files: 0 errors and 11 warnings, all on pre-existing lines this diff does not add.
  • Gates. These exit 0: check:control-bytes, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check:i18n-keys, check:i18n-drift (1 key(s) added against merge-base 0bbb67b9), check:i18n-dead-keys, check:i18n-designer-parity, check:new-line-citations, check:spec-symbols, check:installed-pin-claims and check:phantom-deps. scripts/check-changeset-presence.mjs and scripts/check-changeset-no-major.mjs also exit 0.
  • NOT MEASURED (prerequisite, not a verdict):
    • check:spec-floors and check:readme-exports exit 1 with no-artifact findings only, on packages this branch did not build (app-shell in the floors gate, plugin-gantt, plugin-timeline, plugin-tree, cli). Neither names plugin-grid, whose built dist imports ClockTimeValueSchema from @objectstack/spec/data. That symbol exists at its ^17.5.0 floor.
    • check:eager-locale-catalogues and the eager-closure budget need a console build, so they are left to CI's Bundle Analysis.

Reverse checks

These ran at 34ade8bd; the mutated files and the tests they ran are byte-identical at 551e5ae8. Each one went through scripts/ablation-replace.mjs in wrap mode: the anchor hit 1 → 0, the blob changed, and the restore was proven with blob equal to HEAD and an empty git diff HEAD.

  1. The time arm deleted from checkImportCell. ImportWizard.cellCheck-11913.test.tsx goes red: 2 failed, 7 passed. Red: "marks what the server refuses with invalid_time" and "marks the time cells the server refuses and counts only the clean row". The 10:00 pin stays green, as expected with no arm.
  2. The identity selection replaced by isRecordEmail(value). The plugin-grid and app-shell 11913 files go red: 4 failed, 7 passed. Red: the endpoint-rule marks, "not both", and the wizard count in both packages. The first attempt was refused by the tool before it ran anything, because the replacement text was a substring of the anchor. It was re-run with a unique replacement.
  3. The emailRule: 'identity' flag dropped from identityImportFields. The app-shell 11913 file goes red: 2 failed. Red: the flag pin and the wizard count.

The tree was clean (git status empty) after each one.

Acceptance notes

Not filed, and not fixed here:

  • The latent type mismatch. ImportTargetField.options?: unknown (importTargetFields.ts) is not assignable to ImportWizardProps['fields'][number]['options']. ObjectView's import fields conditional compiles only through subtype reduction to the narrower identity shape. It is type-level only: nothing renders differently. The carrier is the next PR that touches importTargetFields.ts or ObjectView's import wiring; there is no named one today.
  • The identity dry run's codes have no localized sentence. A dry-run row error from the user import endpoint carries upper-case codes (INVALID_EMAIL, INVALID_PHONE, NO_IDENTITY). formatDryRunError has no case for them, so the endpoint's English text is shown. This is a read, not measured through the public door. Carrier: none.
  • The ^17.5.0 floor. @object-ui/plugin-grid declares @objectstack/spec ^17.5.0, and 17.5.x's ClockTimeValueSchema still took a zone suffix. 17.6.0 dropped it. A host resolving spec 17.5.x therefore gets 17.5's clock grammar in the preview. This repository's lockfile installs 17.7.0. Carrier: none.

Generated by Claude Code

claude added 5 commits October 8, 2026 12:45
…r import's email as the server does

The preview marks a cell exactly where the server's import refuses it, and
the import button counts the rows nobody marked (objectui#11814, #11889).
Two columns still went unchecked:

- A `time` cell: `checkImportCell` had no `time` arm. It now mirrors
  core's `parseDateCell(cell, 'time')` (`readTimeOfDayCell`, then the
  year-first form) and refuses with `invalid_time`, in a new localized
  sentence `grid.import.invalidTime` in every pack.
- The user import's email: `identityImportFields` typed it `text`. It is
  now typed `email` and flagged `emailRule: 'identity'`, and the email arm
  asks the user import endpoint's rule (`isLikelyEmail` and
  `isPlaceholderEmail`) instead of the record validator's.

Refs objectui#11913.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
The de pack's new `grid.import.invalidTime` row quotes the cell with one
matched pair, so the pinned span and pair counts move from 75 to 76.

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

`ObjectView` passes `identityImportFields` or `importTargetFields` into the
wizard's `fields` through one conditional, which compiles only while this
return type is a supertype of `ImportTargetField`. The email entry is typed
at its literal against `ImportWizardProps['fields'][number]`, so
`emailRule` is still checked where it is written.

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, 337 chunks) 3311.3 KB 3312.0 KB
Main entry chunk (gzip) 71.4 KB 350 KB
Entry file index-B0V8S9z1.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) 18.33KB 6.73KB
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) 587.49KB 141.58KB
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) 268.74KB 68.04KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 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.43KB 15.54KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.20KB 38.95KB
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) 177.11KB 45.88KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.56KB 69.16KB
plugin-kanban (index.js) 52.22KB 16.38KB
plugin-list (index.js) 117.68KB 29.40KB
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.09KB 22.94KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.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

Standing down on Test (shard 6/8) — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T14:59Z.

  • The failing check: Test (shard 6/8) on head 603ad4e2, at packages/app-shell/src/views/studio-design/ObjectHooksPanel.newHookTarget-11820.test.tsx:87. The error is Unable to find … checkbox … "Invoice (invoice)"; the only checkbox present is named invoice.
  • Why it is not this PR's:
    • This PR's 18 files touch nothing on the test's path. ObjectHooksPanel, HookDefaultInspector, useObjectOptions and the metadata-admin i18n table are untouched.
    • The race reproduces on main. The test awaits the "This package" group, then reads the checkbox label synchronously, but the label arrives from a second fetch (the object roster). A throwaway probe copy that delays the roster by 120 ms fails at the same line on both d7e9e9ab and this head.
    • Run alone, the file failed 2 of 17 runs on this head and 0 of 17 on d7e9e9ab. That sample does not separate the two heads (one-sided Fisher p ≈ 0.24).
    • The same test took objectui#11972 out of the merge queue on main 989b1908.
  • Fix: none exists yet. It is carried by objectui#11978 (domain:spec), whose done-when is the fix shape: await the labelled checkbox with findByRole instead of reading it synchronously.
  • Re-run: the seat has no means to re-run a job, so none was requested. No empty commit will be pushed.
  • Next: objectui#11913 moves to pm:blocked on objectui#11978. When that closes, this branch merges main, which then carries the fix. CI then runs on the new head, and the contract review is recorded on that head.

Generated by Claude Code

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 337 chunks) 3311.5 KB 3312.0 KB
Main entry chunk (gzip) 71.4 KB 350 KB
Entry file index-D7hrW4R_.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) 18.33KB 6.73KB
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) 587.07KB 141.28KB
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) 268.74KB 68.04KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 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.43KB 15.54KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.20KB 38.95KB
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) 177.11KB 45.88KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.56KB 69.16KB
plugin-kanban (index.js) 52.22KB 16.38KB
plugin-list (index.js) 117.72KB 29.41KB
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.09KB 22.94KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.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: 551e5ae8508aa29249a117e72e0c5725f728149a
Local-runs: none

Rendered 2026-10-08T18:05Z by the at-tier reviewer of domain:ui seat 3, from the brief's inputs and nothing else: card #11913 (body and its seven comments: triage 6055545691, claim 6059931513, amendment 6060072703, dev report 6061659448, block and unlock 6062787896 / 6065020993 / 6065298494), PR #11973 (body, 18 files, net diff against main 0bbb67b9, its three comments), and the 43 check-runs on the head. The head is a pure merge of main 0bbb67b9 into 603ad4e2 (parents 603ad4e2 + 0bbb67b9; the merge's diff against 603ad4e2 equals main's own advance from d7e9e9ab line for line, and the branch's net diff is byte-identical at both heads). The server references the mirrors restate were read at their published tags in the objectstack checkout with git show only: @objectstack/core@17.7.0, @objectstack/plugin-auth@17.7.0, @objectstack/spec@17.5.0 / 17.6.0 / 17.7.0.

Check-runs on the head: 43 completed, 40 success, 3 skipped (Test (coverage), its shard, dependabot: skip-by-design on a PR head), 0 failures. Test (shard 6/8) and the Test rollup, which failed at 603ad4e2 on the main race objectui#11978 names, are success here: the fix (fff07fbb, objectui#11986) rode in with the merge.

① Derived judgments

Every accept-set and public-surface change the diff implies, each judged against the server it mirrors:

  1. A time column is now checked: checkImportCell gains a time arm asking isImportableTimeCell. Right. Before, time fell through to refusedOptionValue, which answers undefined for a field with no options, so every time cell passed. Core 17.7.0's parseDateCell(s, 'time') is readTimeOfDayCell (refuse a {token}; otherwise the comparand rule, readsAsWallClock || (readsAsInstant && keepsTimeOfDay)), then readYearFirstCell alone, never readIsoTemporalCell. The mirror is the same three readings: (a) ClockTimeValueSchema.safeParse, the very schema core's readsAsWallClock asks, imported from @objectstack/spec/data rather than restated; (b) isoCellKeepsTimeOfDay: the T form with or without a zone and the space form without one (ISO_DATETIME_WRITE_FORM's shape, with sep === ' ' && zone refused), a real calendar day by the same arithmetic, and a UTC year in 0..9999 computed with the UTC setters, which is keepsTimeOfDay's four-digit-year toISOString condition; (c) yearFirstCellYear: core's YEAR_FIRST_CELL regex is character-for-character the same, hour at most 23, minute and second at most 59. A {placeholder} (^\$?\{[^{}]+\}$) matches none of the three, as core steps around it. The arm sits after date / datetime, the order coerceFieldValue runs them in. The card's pins (25:00, abc, 10:00Z, 9am marked; 10:00 taken) and the Date.parse cross-check over 3 days × 7 clocks × 6 zones are the tripwire, and both are green on this head.
  2. invalid_time is a new refusal code with its own sentence in the preview (cellRefusalMessage) and in the dry run (formatDryRunError). Right. Core's coerceFieldValue emits invalid_time for a refused time cell (import_invalid_time), so the dry-run case renders the server's own code with the same sentence.
  3. The user import's email column is checked by the identity endpoint's rule, replacing the record rule on that column only. Right. identityImportFields types the column email and flags emailRule: 'identity'; the email arm asks isIdentityEmail when flagged and isRecordEmail otherwise. isIdentityEmail is plugin-auth 17.7.0's isLikelyEmail body (at most 254 characters, printable ASCII, no whitespace, one @ neither first nor last, the domain's last dot neither first nor last) and !isPlaceholderEmail (@placeholder.invalid, any case), which is exactly what resolveRowIdentity refuses with INVALID_EMAIL on the trimmed cell, dry run included. Replace rather than add is right: the two rules are incomparable (a@b..c and 735431496@柴仟.com are each taken by one and refused by the other), and the sys_user import never meets the record validator. A plain email field keeps the record rule unchanged, pinned.
  4. Public surface. Right, and as the amendment foresaw. ImportWizardProps['fields'][number] gains emailRule?: 'identity', and it is reachable: index.tsx re-exports ImportWizard and the types ImportWizardProps and ImportResult, and exports maps only . and ./style.css. The new mirrors and the widened private CellRefusal union are not reachable (importCoercionContract.ts and __testables are not re-exported from the entry). identityImportFields is not re-exported from app-shell's entry, and IdentityImportPanels.tsx is no published subpath. The type-only import of ImportWizardProps into app-shell comes from a declared peer (@object-ui/plugin-grid: workspace:^) and adds no runtime edge across ObjectView's lazy boundary.
  5. i18n. Right. grid.import.invalidTime with {{value}} lands in all ten packs beside invalidDate, each in its pack's own quote convention, and in IMPORT_DEFAULT_TRANSLATIONS, which ImportWizard.i18n-parity.test.ts pins to en.grid.import key for key and text for text.
  6. The count. Right. The button counts unmarked rows (objectui#11814), so both new marks move it; pinned through the real wizard in both packages (Import 1 Row on five time rows and on three identity email rows), with the three reverse checks the PR body records going red as predicted.

Nothing in the 18 files lies outside the claim plus its amendment, except the one region the dev named, ruled on in ③.

② Semver level

.changeset/11913-import-time-identity-email.md declares @object-ui/plugin-grid minor, @object-ui/i18n minor, @object-ui/app-shell patch. Each matches what the diff publishes:

  • plugin-grid: an optional member on an exported type, additive, so minor. Right.
  • i18n: a new key in every published pack (./locales/* is an export of the package), so minor. Right.
  • app-shell: a behaviour change in a module its entry does not re-export, so patch. Right.
  • No major, and the changeset names the two surface additions in words: Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check and Changeset Overwrite Report are all success on the head.

The PR body carries Clause-②: yes at line start, as the claim (6059931513) and the amendment (6060072703) said it would and for the reasons they gave: the published i18n key, and the one reachable plugin-grid widening. Right; this at-tier record is the review that line obliges.

③ Boundary flags

open_questions: none declared. Dev deviations (report 6061659448), each answered:

  1. yearFirstCellYear extracted from the tail of importDateCellYear: accepted. Same file, same card, mechanical: the body moved verbatim and is called at the same point, so the date and time readers share one year-first reading instead of two copies, the opposite of the fork triage ruled out. The existing isImportableDateCell pins are unmodified and green.
  2. de-quote-pairing-3876.test.ts census 75 to 76: accepted. A pin beside the packs that must move with them; the de row adds exactly one matched „ “ pair.
  3. The app-shell suite narrowed to four files locally: answered by the head's check-runs. All eight test shards, Test (dist pins) and the Test rollup are success.
  4. Model-free commit trailers and the session-URL PR footer: confirmed on the six commits and the body; no model identifier anywhere.
  5. One lock wait killed and rerun: a process note with no artifact; nothing to rule on.
  6. The PR body not re-PATCHed after creation: moot. The body under review names this head and its verification, and the record judges the head.

Acceptance notes and out-of-scope findings, each answered:

  • The latent ImportTargetField.options?: unknown mismatch against the wizard's options: pre-existing, type-level, outside the surface; keeping identityImportFields' narrow return type is the right choice here (app-shell Type Check success). Not blocking. Escalated to the seat as a filing candidate; its carrier is the next PR on importTargetFields.ts or ObjectView's import wiring.
  • The identity dry run's codes (INVALID_EMAIL, INVALID_PHONE, NO_IDENTITY) have no localized sentence: pre-existing, and outside the card's done-when (the preview mark and the count, which now hold before Validate is clicked). Not blocking. Escalated to the seat as a filing candidate.
  • The @objectstack/spec ^17.5.0 floor: verified at the tags: 17.5.0's ClockTimeValueSchema takes a zone suffix, 17.6.0 and 17.7.0 do not. A host resolving 17.5.x would leave 10:00Z-shaped cells unmarked, an under-mark exactly as before this PR, and would never mark a cell the server takes, because 17.5's grammar is a strict superset. So the objectui#3017 invariant (never flagged for a value the server would take) holds at every version the floor admits, and this repository's lockfile installs 17.7.0. Not blocking. Escalated to the seat: a floor bump is a dependency change outside this claim's surface and a Clause-② question of its own.

One observation of my own, not a flag: better-auth revalidates an address at real-import time beyond isLikelyEmail's pre-filter. That lies outside the endpoint's dry-run rule the card names, which the preview now mirrors exactly.

Implemented-by: claude/issue-11913-import-time-identity-email
Reviewed-by: session_01CGZy1BGCjdN5cXqL9cnvB8

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 18:08
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 18:08
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit fbad078 Oct 8, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11913-import-time-identity-email branch October 8, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

import preview: a time column is not checked, and the user import checks no email — the import button still overcounts both

2 participants