fix(plugin-grid): the import-job history table formats its timestamps in the display locale - #9351
Conversation
… in the display locale `formatImportJobTime`, which draws the Time column of the import-job history panel, ended in a bare `d.toLocaleString()`. An omitted tag is not "the user's locale" — it is the MACHINE's, which is neither of this renderer's two locale channels, and `useDisplayLocale`'s own doc comment names that as the one thing a caller must not do. On a date the omission moves FIELD ORDER rather than a separator: the same instant reads `04/03/2026` under `en-GB` and `3/4/2026` under `en-US`, both legal and mutually ambiguous for the first twelve days of every month. Measured before the fix on this runner, a German session rendered the status badge as `Erfolgreich` and the timestamp beside it in the machine's American order. `ImportHistoryPanel` now reads `useDisplayLocale()` at component level and passes the resolved tag down. The helper stays a module-level pure function rather than being inlined into the component, and its `locale` parameter is a required, non-optional `string`: under an optional spelling a caller could drop the argument, still type-check, and render a plausible date instead of an error. `iso` is respelled `string | undefined` because a required parameter cannot follow an optional one. No `?? 'en'` backstop — the hook already owns the last resort. No published symbol moves: both the helper and the panel are module-private. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…locale Base-only sync: brings the branch onto a main that contains 8524372 (feat(react)!: unbind the data-source adapter from the expression scope). No file owned by this pull request is modified by this commit. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
|
| Step | Outcome |
|---|---|
| Build packages | success |
| Check console performance budget | failure |
Which half objected:
| Eager-closure half | Verdict |
|---|---|
| Aggregate closure ceiling | ✅ pass |
| Per-chunk ceilings | ✅ pass |
| Ceiling sensitivity (headroom) | |
| Ceiling freshness (checkout vs. base branch) | ✅ pass |
⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. TheCheck console performance budgetstep log carries the ceiling and the number it was compared against.
Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.
See the workflow run for details.
📦 Bundle Size Report
| Package | Size | Gzipped |
|---|---|---|
| app-shell (consoleActionDispatch.js) | 0.20KB | 0.19KB |
| app-shell (index.js) | 16.69KB | 6.21KB |
| app-shell (runtime-config.js) | 20.68KB | 7.36KB |
| app-shell (types.js) | 0.01KB | 0.04KB |
| app-shell (urlParams.js) | 10.06KB | 3.86KB |
| auth (ActiveOrganizationStorage.js) | 25.05KB | 9.16KB |
| auth (AuthContext.js) | 0.31KB | 0.24KB |
| auth (AuthGuard.js) | 2.07KB | 1.00KB |
| auth (AuthProvider.js) | 40.18KB | 10.59KB |
| auth (AuthShell.js) | 3.49KB | 1.40KB |
| auth (ForgotPasswordForm.js) | 12.21KB | 3.45KB |
| auth (LoginForm.js) | 18.15KB | 5.39KB |
| auth (PreviewBanner.js) | 0.90KB | 0.50KB |
| auth (RegisterForm.js) | 6.65KB | 2.22KB |
| auth (SocialSignInButtons.js) | 9.61KB | 3.89KB |
| auth (UserMenu.js) | 3.41KB | 1.23KB |
| auth (auth-gate-events.js) | 1.29KB | 0.66KB |
| auth (authStyles.js) | 5.04KB | 1.72KB |
| auth (createAuthClient.js) | 40.21KB | 10.80KB |
| auth (createAuthenticatedFetch.js) | 8.46KB | 3.43KB |
| auth (index.js) | 3.19KB | 1.44KB |
| 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) | 26.08KB | 7.56KB |
| collaboration (LiveCursors.js) | 3.17KB | 1.27KB |
| collaboration (PresenceAvatars.js) | 6.49KB | 2.64KB |
| collaboration (PresenceProvider.js) | 2.79KB | 1.13KB |
| collaboration (index.js) | 1.68KB | 0.73KB |
| collaboration (useCollaborationTranslation.js) | 6.05KB | 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) | 502.02KB | 115.16KB |
| core (index.js) | 8.52KB | 3.41KB |
| create-plugin (index.js) | 27.94KB | 9.51KB |
| data-objectstack (index.js) | 211.58KB | 58.68KB |
| fields (index.js) | 247.89KB | 62.50KB |
| i18n (LocalizationContext.js) | 1.76KB | 0.96KB |
| i18n (builtinAggregateLabels.js) | 0.86KB | 0.49KB |
| i18n (currency.js) | 1.22KB | 0.64KB |
| i18n (fallbackInterpolation.js) | 6.25KB | 2.77KB |
| i18n (i18n.js) | 8.87KB | 3.64KB |
| i18n (index.js) | 5.22KB | 2.26KB |
| i18n (pickLocalized.js) | 9.86KB | 3.95KB |
| i18n (provider.js) | 32.15KB | 10.49KB |
| i18n (useDisplayLocale.js) | 2.85KB | 1.45KB |
| i18n (useObjectLabel.js) | 34.34KB | 9.17KB |
| i18n (useSafeTranslation.js) | 5.60KB | 2.33KB |
| layout (index.js) | 38.83KB | 10.95KB |
| 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) | 2.53KB | 0.85KB |
| mobile (useResponsive.js) | 0.72KB | 0.42KB |
| mobile (useSpecGesture.js) | 4.39KB | 1.66KB |
| mobile (useTouchTarget.js) | 1.01KB | 0.54KB |
| permissions (MePermissionsProvider.js) | 13.52KB | 4.88KB |
| permissions (PermissionContext.js) | 0.31KB | 0.25KB |
| permissions (PermissionGuard.js) | 0.89KB | 0.45KB |
| permissions (PermissionProvider.js) | 6.24KB | 2.16KB |
| permissions (discardProofCache.js) | 1.04KB | 0.55KB |
| permissions (evaluator.js) | 8.39KB | 3.10KB |
| 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) | 14.81KB | 3.63KB |
| plugin-calendar (index.js) | 49.25KB | 13.99KB |
| plugin-charts (index.js) | 71.34KB | 19.90KB |
| plugin-chatbot (index.js) | 195.34KB | 46.51KB |
| plugin-dashboard (index.js) | 131.22KB | 34.59KB |
| plugin-designer (index.js) | 215.94KB | 44.33KB |
| plugin-detail (index.js) | 253.46KB | 65.85KB |
| plugin-editor (index.js) | 2.23KB | 1.05KB |
| plugin-form (index.js) | 136.77KB | 34.17KB |
| plugin-gantt (index.js) | 166.95KB | 41.04KB |
| plugin-grid (index.js) | 211.68KB | 57.52KB |
| plugin-kanban (index.js) | 46.00KB | 14.30KB |
| plugin-list (index.js) | 112.58KB | 27.65KB |
| plugin-map (index.js) | 20.64KB | 6.86KB |
| plugin-markdown (index.js) | 13.88KB | 4.80KB |
| plugin-report (index.js) | 43.41KB | 11.93KB |
| plugin-timeline (index.js) | 30.07KB | 8.74KB |
| plugin-tree (index.js) | 9.55KB | 3.32KB |
| plugin-view (index.js) | 84.42KB | 20.79KB |
| 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.66KB | 3.50KB |
| 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) | 96.00KB | 31.71KB |
| react (data-invalidation.js) | 5.05KB | 2.08KB |
| react (index.js) | 4.63KB | 2.18KB |
| react (schema-input.js) | 4.25KB | 2.04KB |
| react (spec-input.js) | 0.20KB | 0.18KB |
| sdui-parser (codegen.js) | 6.58KB | 2.74KB |
| sdui-parser (dashboard-widget-options.js) | 3.08KB | 1.30KB |
| sdui-parser (index.js) | 5.66KB | 2.50KB |
| sdui-parser (input-type.js) | 2.84KB | 1.40KB |
| sdui-parser (kanban-quick-add.js) | 3.89KB | 1.87KB |
| sdui-parser (parse.js) | 25.28KB | 7.80KB |
| sdui-parser (provenance.js) | 3.66KB | 1.82KB |
| sdui-parser (types.js) | 0.28KB | 0.23KB |
| sdui-parser (validate.js) | 14.82KB | 4.99KB |
| types (ai.js) | 0.20KB | 0.17KB |
| types (api-types.js) | 0.20KB | 0.18KB |
| types (app.js) | 2.87KB | 1.00KB |
| types (base.js) | 0.20KB | 0.18KB |
| types (blocks.js) | 0.20KB | 0.18KB |
| types (complex.js) | 2.93KB | 1.49KB |
| types (crud.js) | 0.20KB | 0.18KB |
| types (dashboard-filter-alias.js) | 6.23KB | 2.74KB |
| 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) | 0.20KB | 0.18KB |
| 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) | 4.74KB | 2.25KB |
| types (layout.js) | 0.20KB | 0.18KB |
| types (managed-by.js) | 0.19KB | 0.18KB |
| types (mobile.js) | 4.73KB | 2.28KB |
| types (navigation.js) | 0.20KB | 0.18KB |
| types (objectql.js) | 0.20KB | 0.18KB |
| types (overlay.js) | 0.20KB | 0.18KB |
| types (permissions.js) | 0.20KB | 0.18KB |
| 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) | 5.05KB | 1.93KB |
| types (spec-ui-namespace.js) | 0.20KB | 0.19KB |
| types (strict-authoring-face.js) | 14.04KB | 5.36KB |
| 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
Contract reviewReviewed head: Every reading below was re-derived in my own detached worktree at that sha ( Independence pair. The implementing commit is ① Does the diff do what the card says, at the right layer?Yes. Premise re-verified independently at the pre-repair parent Layer is right. No new runtime precondition is imposed on any consumer — this is the thing that would have made a hook-at-component-level repair a behaviour change. Package-level completeness, which is where the "one of many" hazard lives. Newline-tolerant census over all 163 TS/TSX files under Observation, explicitly NOT a finding against this pull request. Repo-wide the same class is much larger than the PR's Acceptance notes suggest. This command, run as printed from the repo root: prints 67 non-test sites across 15 packages and apps: This is not a defect in this diff: the card scoped itself to one file, the repair is complete at that file and at that package, and the two nearest siblings are already tracked. It is recorded because the Acceptance-notes line, by naming only those two cards, reads as though the remaining family were those two — the instrument above says otherwise. The number is deliberately paired with the command that re-derives it rather than left standing alone. ② Are the pins real?Yes — ablation run here, not read from the PR body. Each leg mutated the file on disk; each mutation was proved to have reached disk (count of removed text 0, count of injected text at least 1) before any result was read; each restore was verified by
Lock verdicts:
Now the two failure modes my brief names for locale pins, each answered with a measurement:
The ③ Does the PR text say what the code does?Yes — neither overstating nor understating in any way that matters. Re-derived, claim by claim: the base Two places where the prose is weaker than the code, neither harmful and neither a defect: the family understatement in ① above, and the machine-locale robustness understatement in ② above. I found nothing overstated. The body does not claim CI is green, which is correct of it. ④ ScopeThree files, exactly the three in scope, no drive-by edit. Changeset present and correctly scoped: Gates re-run here, exit codes captured by redirect: ⑤ Clause-② — judged, not accepted as boilerplateThe declared
⇒ Nothing widens the accept set and nothing widens the public surface, so no ⑥ Checks, with the verdicts kept apart36 check runs on this head, enumeration complete — The single Findings
PASSContract review by an independent reviewing seat; no push, edit, label or landing action was taken on this pull request. Session: Generated by Claude Code |
ADOPTED — the PASS at
|
| ① same-form PASS at tier | ✅ | 5654667094, head 1a4186b7c838, default tier (Clause-②: no), heading · sha in a code span · itemized · independence pair · unhedged PASS |
② --pair exit 0 |
✅ | --pair 9351 ⇒ EXIT=0, both carriers agree |
| ③ every check green | 36 runs — 32 success, 3 skipped, 1 failure, 0 cancelled, 0 timed_out. The one failure is Bundle Analysis, the board-wide ui-components headroom debt; its own comment says 「this is not a budget violation」 and its verdict says an unrelated PR has nothing to fix there. Relaxed for base-inherited reds under the maintainer's standing authorization |
⭐ Why the clause-② answer is right, and it is the opposite of the sibling PRs
The only accept-set movement is a NARROWING on a module-private function:
(iso?: string)→(iso: string | undefined, locale: string). A previously legal 1-arg call is now a
compile error — measured, TS2554. What changed is what the function EMITS, not what it accepts.
Published surface unmoved, measured on the built artefact: 32 emitted .d.ts files contain
formatImportJobTime 0× and ImportHistoryPanel 0×, with four controls firing in the same
scan (ImportWizardProps 8, ImportWizard 15, isPlausibleEmail 1, __testables 1); the exports map
reaches only ./dist/index.d.ts and ./dist/index.css. ⇒ no Contract-text: carrier owed, and none
was hung.
⭐ It refused a measurement that would have looked like one
The
TZ=vitest leg — rootvitest.config.mtssetsprocess.env.TZ='UTC'unconditionally (line
56), so aTZ=in the environment never reaches the test process. Discarded as a
non-measurement and replaced by a direct four-timezone measurement in node with a firing control.
That is the whole discipline in one paragraph: a leg that would have produced a confident green while
constraining nothing, caught and thrown out rather than quoted. ⛔ It also did not let the replacement
stand unguarded — the four-timezone reading carries a control that does move (a Z-suffixed instant
shifts day and hour).
And it answered the trap I named in the order — that a locale pin can pass for the wrong reason: the
assertions spell date substrings only, so the ICU-72 NNBSP class cannot reach them (runner ICU
78.2), and the machine-locale control is live (en-US → de-DE under LANG/LC_ALL). ⭐ Its Leg E,
which the order did not ask for, runs the defect under a de_DE machine locale and still reds — with a
different surviving case, which is what proves no case is structurally vacuous.
Independence — weak, stated, and not broken
Implementer 0644aa487c = session_01UzHd6hDYatoDn17BuwKxnZ. This seat authored only the base merge
1a4186b7c8, verified by the reviewer itself: git diff --name-only 1bd49aede1 1a4186b7c8 lists 98
files (control fires) and none of the 3 owned paths; all three blobs byte-identical.
⇒ this seat wrote no byte of the reviewed content. ⛔ Not a fully clean pair — the same seat posted
carrier-repair comment 5652800583 on the card, and one shared GitHub identity means the
Claude-Session: trailer is the only discriminator. ⭐ That is the line this seat is drawing and
applying consistently: content authored by the reviewing seat ⇒ broken, not adoptable
(objectui#9376, objectui#9399, both held); a base merge touching zero owned files ⇒ weak, stated,
adoptable. The maintainer may move that line; until then it is applied the same way every time.
Recorded, ⛔ not ordered
The reviewer's repo-wide observation: 67 non-test locale-less formatter sites across 15
packages/apps — 21 inside the two already-filed sibling cards (objectui#7174: 16, objectui#8209: 5), 46
outside both, and plugin-grid contributes 0 now. ⭐ It recorded the reproducible census command
rather than a bare number, which is AGENTS.md #9 applied by a reviewer to its own finding.
⇒ landing proceeds: carriers cleared, draft flipped, enqueued.
Generated by Claude Code
Fixes #9327
formatImportJobTime— the Time column of the grid's import-job history panel — ended in abare
d.toLocaleString(). An omitted tag is not "the user's locale"; it is the MACHINE's,which is neither of this renderer's two locale channels.
useDisplayLocale's own doc commentnames that as the one thing a caller must not do:
Premise re-verified on my base
a5921a0f8(origin/main had moved from theb2bb8134dthedispatch quoted).
git grep -n -E 'toLocale[A-Za-z]*\(|Intl\.'over the file at that shareturns exactly one hit, line 1436 — still the only locale-less call in the file, so the
repair has one site and not a family.
Why a date is not merely the same bug again
On a number the machine locale moves separators; on a date it moves field order. Measured
on this runner (node v22.22.2, root vitest config pins
TZ=UTC, machine localeen-US), fornew Date('2026-03-04T12:00:00').toLocaleString(tag):3/4/2026, 12:00:00 PM(the MACHINE)en-US3/4/2026, 12:00:00 PMen-GB04/03/2026, 12:00:00de4.3.2026, 12:00:00The sharpest single reading from the red-first run: a German session rendered the row as
Erfolgreich3/32 erstellt · 1 aktualisiert3/4/2026, 12:00:00 PM— the same row speakingGerman while the timestamp beside it holds the machine's American field order, with no unit
marker to catch the misreading.
The implementation question the card left open
formatImportJobTimeis a module-level function, not a hook.ImportHistoryPanelis acomponent, so it reads
useDisplayLocale()at component level and passes the resolved tagdown. The helper stays a module-level pure function rather than being inlined into the
component — the card's acceptance criterion 2 rules that swap out explicitly.
localeis a required, non-optionalstring, applying (not copying) the judgementcommitted beside objectui#9294 / PR objectui#9326.
isois respelledstring | undefinedrather than
iso?:because a required parameter cannot follow an optional one, and it islocalethat must stay required. There is no?? 'en'backstop at the renderer: the hookalready owns the last resort and always returns a concrete tag.
I deliberately did not add the helper to the file's exported
__testablesbag. That wouldhave moved a published symbol's shape for no gain — the DOM-level pin below exercises the real
render path, which is strictly stronger evidence than a direct call.
Red-first, verbatim
The pin was written and run before the source change, on the unmodified tree
(
git diff HEAD --name-onlywas empty at the time of the run):After the repair:
Test Files 1 passed (1) · Tests 4 passed (4).The one green-on-both-sides case is the
en-USmust-not-change pin — the runner's machinelocale agrees with it byte for byte, so it is not evidence the fix works. Its job is to be
the firing contrast partner the card's acceptance criterion 3 demands:
en-GBanden-USmustproduce different output, or a green run could not distinguish "reads the locale" from
"reads nothing and the machine happened to agree". A third case (
de, via the UI-languagechannel with no tenant locale) additionally rules out a hardcoded
'en-GB'repair, and afourth pins the documented precedence (tenant outranks the language switcher).
Ablation — three legs, each mutated on disk, each restored by blob hash
Every leg ran under
trap ... EXIT INT TERMwith absolute paths. Each mutation was proved tohave reached disk (count of removed text must be 0, count of injected text at least 1) before
any result was read; each restore was verified by
git hash-objectequality against the HEADblob and an empty
git diff HEAD— never by an exit code. The subject resolves from source(the pin imports
../ImportWizardrelatively), so there is nodistleg to preflight.HEAD_BLOB=e1f7859db3e9130410b8c89bd98944e310eb762ed.toLocaleString(locale)back tod.toLocaleString()3 failed / 1 passedtype-checkexit 2 ·src/ImportWizard.tsx(1567,71): error TS2554: Expected 2 arguments, but got 1.locale?:optional, same dropped argumenttype-checkexit 0 · pin exit 1 ·3 failed / 1 passedAll three restores reported
RESTORE: OK blob=e1f7859db3e9130410b8c89bd98944e310eb762e == HEAD AND 'git diff HEAD' empty.B against C is the measurement that matters, and it is why the parameter is required rather
than optional: the identical mistake is a compile error under the shipped declaration and a
silent machine-locale render under the optional one. That is objectui#9326's stated reasoning
re-measured on a date instead of quoted.
Clause-② — re-measured, and it stays
noNot by inspection. I built
@object-ui/plugin-gridat HEAD, mutated the one file back to itscontent at the pinned base sha
a5921a0f8, rebuilt, and compared every emitted declarationfile:
Both
formatImportJobTimeandImportHistoryPanelare declared withoutexport. No publishedsymbol, schema key or registry entry moves, so no accept-set moves on a published surface.
Dependent-set membership read
Done here, not inherited. Seven manifests name
@object-ui/plugin-grid, and all seven declarea
type-checkscript —@object-ui/siteincluded:type-check@object-ui/consoleapps/console/package.json@object-ui/siteapps/site/package.json@object-ui/example-console-starterexamples/console-starter/package.json@object-ui/example-schema-catalogexamples/schema-catalog/package.json@object-ui/app-shellpackages/app-shell/package.json@object-ui/plugin-designerpackages/plugin-designer/package.json@object-ui/plugin-viewpackages/plugin-view/package.jsonSo
@object-ui/siteis not structurally out of reach for this package. The likely origin ofthe opposite belief:
@object-ui/siteis in.changeset/config.json'signorelist, i.e. itis excluded from version bumping, which is a different exclusion from the type-check
dependent set. Nothing is owed to any of the seven on this change regardless — the published
.d.tssurface is byte-identical, so no consumer's program input moved.Verification
Heavy runs went through
../objectstack/scripts/pm/os-verify-lock.shon a stable slot.VERDICT command-exit 1 · held the lock 9s · waited 319sVERDICT command-exit 0 · held the lock 78s · waited 24sVERDICT command-exit 0 · held the lock 36s · waited 57spackages/plugin-grid/suiteVERDICT command-exit 0 · held the lock 174s · waited 22s— 129 files / 1186 tests passedUnlocked, in-worktree:
type-checkexit 0 andlintexit 0 for@object-ui/plugin-grid. Thenew pin is provably a type-check program input —
tsc -p tsconfig.test.json --listFilescountsit exactly once, so that green is a measurement and not an exclusion.
Gate family derived by hand from
package.jsonplus.github/workflows/(this repo has noscripts/pm/dispatch-gates.mjs). All exit 0:check-changeset-presence·check-changeset-fixed·check-changeset-no-major·check-changeset-claims·check:control-bytes·check:phantom-deps·check:unused-deps·check:test-path-roots·check:vi-mock-specifiers·check:new-line-citations·check:i18n-keys·check:unreferenced-sources.Presence gate's own line:
1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s). Governed-surface guard, asked directly about this file list:NOT GOVERNED — 3 path(s) checked against 5 governed surface(s); none matched.The changeset is
patch, notminor: nothing breaking ships here, so the repo's**BREAKING**-leads carrier convention does not apply to it.In-flight disjointness — read from each PR's file list
packages/plugin-grid/objectui#9310 was the one with a question mark against it — it carries plugin index files for
calendar, charts, dashboard, detail, gantt, kanban and list, and no
plugin-gridfile.Acceptance notes
'—'empty-value fallback is untouched — that is objectui#8507's subject, which namesthis same file at a different line and whose body contains no occurrence of
locale.formatTimestampnear-copies inplugin-detailandplugin-chatbot) or objectui#8209 (readonlydatetimewidget faces in@object-ui/fields).Same family, ruled out of this card's scope.
formatImportJobTimeremains reachable only throughImportHistoryPanel,so nothing mechanically stops a future module-level helper in this file from being written
locale-less again. That is an observation about a class, not a reproducible defect, and there
is no named PR or person about to touch this file — successor: none.
Generated by Claude Code