Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(types): one authority for `ActionSchema` and the Breadcrumb pair by os-sam · Pull Request #6936 · objectstack-ai/objectui · GitHub
Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(types): one authority for `ActionSchema` and the Breadcrumb pair by os-sam · Pull Request #6936 · objectstack-ai/objectui · GitHub
Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(types): one authority for `ActionSchema` and the Breadcrumb pair by os-sam · Pull Request #6936 · objectstack-ai/objectui · GitHub
Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(types): one authority for `ActionSchema` and the Breadcrumb pair by os-sam · Pull Request #6936 · objectstack-ai/objectui · GitHub
Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(types): one authority for `ActionSchema` and the Breadcrumb pair by os-sam · Pull Request #6936 · objectstack-ai/objectui · GitHub
Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(types): one authority for `ActionSchema` and the Breadcrumb pair by os-sam · Pull Request #6936 · objectstack-ai/objectui · GitHub
Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(types): one authority for `ActionSchema` and the Breadcrumb pair by os-sam · Pull Request #6936 · objectstack-ai/objectui · GitHub
Skip to content

fix(types): one authority for ActionSchema and the Breadcrumb pair - #6936

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1
Aug 31, 2026
Merged

fix(types): one authority for ActionSchema and the Breadcrumb pair#6936
os-sam merged 1 commit into
mainfrom
claude/issue-6349-types-internal-collisions-batch-1

Conversation

@os-sam

@os-samos-sam commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #6349first batch only: the three collisions that live entirely inside packages/types, where a barrel consumer cannot disambiguate them. 43 of the 46 census names remain and #6349 stays their owner, so this is Refs, not Fixes.

Verified on c7bc6155b. ⛔ Do not mark ready / enqueue / auto-merge — draft by dispatch order.

The batch, and the delta on the merge-base

The census table was measured on 79ebf30d1. Re-derived on my merge-base 40c479af2 with the gate's own matcher (scripts/js-comment-mask.mjs + the same three regexes, same population bound): 43 colliding names, not 46FormFieldSpec / FormSectionSpec / FormViewSpec left the tree with the spec-bridge retirement (#6366) and KNOWN_COLLISIONS already records that in a comment. All three of my names still collided, at the same sites the table names, so none had to be dropped:

ActionSchema packages/types/src/crud.ts:67 packages/types/src/ui-action.ts:374
BreadcrumbItem packages/types/src/data-display.ts:1649 packages/types/src/navigation.ts:179
BreadcrumbSchema packages/types/src/data-display.ts:1663 packages/types/src/navigation.ts:205

After: 40 collisions, 40 baseline entries. Population floors intact — 38 published packages, 1,384 files, 2,201 authority sites (was 2,204: three declarations removed, one barrel alias became a plain re-export).

Shapes compared structurally before choosing — the method

Not by reading. A TypeScript-AST probe over both declarations of each name, comparing heritage clause, member set, and each shared member's declared type and optionality:

nameABonly in Aonly in Bshared keys typed differentlyverdict
ActionSchemacrud.ts (28 members, extends BaseSchema)ui-action.ts (28 members, no heritage)19193 — type: 'action' vs ActionType; variant; methodunrelated types
BreadcrumbItemdata-display.ts (2)navigation.ts (5)03 — icon, onClick, siblings0strict subset copy
BreadcrumbSchemadata-display.ts (3, extends BaseSchema)navigation.ts (4, same)01 — maxItems0strict subset copy

That split is what picks the remedy per name, and the two remedies are different.

ActionSchema → rename, because the two are different things

9 shared keys out of 28 each; one is a UI node in a component tree (type: 'action'), the other a spec-v2 action definition (name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type — exactly the harm the ruling is about. #5044 is the precedent for choosing the surviving name, and here the tiebreak is already settled by the barrel: src/index.ts publishes crud.ts's as ActionSchema and ui-action.ts's as UIActionSchema (export type { ActionSchema as UIActionSchema }, line 1037). So ui-action.ts's declaration is renamed to the name the package has always published it under, and the alias becomes a plain re-export. packages/components' own CHANGELOG already calls that type UIActionSchema in prose; only the declaration was out of step.

Consumers. Nothing outside ui-action.ts imported the old spelling: exports in packages/types/package.json has no ./ui-action subpath, and the only three files importing that module by path take ActionParam and the value exports, never ActionSchema. Every UIActionSchema user in the repo goes through the barrel and is untouched.

BreadcrumbItem / BreadcrumbSchema → re-point, because one copy was stale

Not a second dialect — a subset with nothing declared differently. Everything that actually reads a breadcrumb was already on navigation.ts: registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems all present), the ui:breadcrumb renderer consumes it and says so in a comment, and content/docs/components/data-display/breadcrumb.mdx documents icon and maxItems. data-display.ts now re-exports the one authority.

Consumers, and one that imported the losing file directly. Yes — packages/types/src/__tests__/zod-mirror-parity.test.ts:138 imports BreadcrumbItem / BreadcrumbSchema from ../data-display and pairs them against the navigation.zod.ts mirrors (lines 610–611). It was checking the navigation Zod schema against the data-display TS interface: a live instance of the defect, silent because the mirror is the wider side. After the re-point those pairs compare the mirror against the declaration it actually mirrors; the file is green either way, but it is honest now. No ledger entry (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) names either pair, so nothing went stale.

Emitted .d.ts — what changed shape, and for whom

Built @object-ui/types on both sides (a second worktree at 40c479af2, since removed) and diffed the emitted declarations:

emitted fileresult
dist/index.d.tsCHANGED, but only the spelling of one re-export clauseActionSchema as UIActionSchemaUIActionSchema. No exported name added, removed or reshaped.
dist/crud.d.ts, dist/navigation.d.ts, dist/registry.d.tsbyte-identical
dist/ui-action.d.tsinterface identifier ActionSchemaUIActionSchema; members unchanged. Not reachable — no ./ui-action subpath.
dist/data-display.d.tsthe two interface Breadcrumb* declarations replaced by export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'
dist/zod/*.zod.d.ts7 files (data-display, form, index, layout, navigation, objectql, views)byte-differs, shape-identical. Ordering jitter of inferred-type properties: the sorted line-multisets are identical for all seven, with dist/data-display.d.ts as a hot control that FAILS that same test, as a real change must; and two clean builds of the same head commit are byte-identical, so tsc is deterministic here and the reordering is attributable to this PR's module-graph change. The ./zod subpath's declared types are unchanged in meaning.

The first four rows are the complete shape statement; the zod row is what makes it complete as a byte statement too — my first enumeration listed only the shape changes and was incomplete on bytes.

So exactly one audience sees a member change shape: a consumer of the published @object-ui/types/data-display subpath, or of the DataDisplaySchema union. Their BreadcrumbItem gains icon / onClick / siblings and their BreadcrumbSchema gains maxItems. All four gained members are optional and nothing narrows, so every value that type-checked before still does, in both directions — and what the subpath declares is now what the renderer already honoured and the docs already described. Graded minor per the version-alignment rule (direct precedent: #6574 added bind to BaseSchema, the same class, graded minor).

⚠️One caveat, and it is stated because an earlier draft of this sentence claimed the absolute (“everything that type-checked before still does”) and a probe refutes it. Adding members moves keyof, so a consumer that EXHAUSTS it does break. Measured, direction predicted first: Record<keyof BreadcrumbItem, string> written with the two old keys compiles on base and fails on head — TS2739: ... missing the following properties ...: icon, onClick, siblings. In-repo consumers of that shape: zero (controlled grep for keyof over both names across packages/ and apps/), and there is no in-repo, examples/ or skills/ importer of the @object-ui/types/data-display subpath at all. So this is a documented caveat for downstream, not a blocker — but it is a real class, and the landing record should not deny it. The changeset already carries the value-scoped wording.

The baseline gate shown non-vacuous

The instrument is the two-way failure itself, so it is demonstrated rather than supplemented. Direction predicted in writing first: deleting a KNOWN_COLLISIONS line while the name still collides leaves the observed collision with no baseline entry, so reconcile puts it in fresh with the text a NEW colliding name — the fresh assertion fails, stale stays green.

Run on the clean merge-base tree, mutating the fact (the baseline line for BreadcrumbItem), never the assertion, under trap … EXIT INT TERM with absolute paths from git rev-parse --show-toplevel:

HEAD_BLOB=542a445903d56fb0e57fc176eda916bef7646e07
DISK_PRE =542a445903d56fb0e57fc176eda916bef7646e07 (equal — tree was at HEAD before mutating)
ANCHOR_COUNT_BEFORE=1 → ANCHOR_COUNT_AFTER=0
DISK_MUT =7139a100bc8afe870b427d69b0246fd0175e7ff4 (≠ HEAD blob — mutation reached disk)
GATE_EXIT_ON_MUTATED_TREE=1
BreadcrumbItem — a NEW colliding name
Test Files 1 failed (1) · Tests 1 failed | 10 passed (11)

Observed direction matched the prediction. Restore proved both ways with git checkout HEAD -- <abs path> (never bare): git diff HEAD empty and disk hash back to 542a4459…, git status clean.

Gates

Exit codes captured before any pipe; every verdict below is the line the gate printed for itself.

gateresult
pnpm --filter @object-ui/types type-check (hyphenated; tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, so test files are covered)exit 0, before and after
root-form vitest, same file set before and afterbefore: 82 files / 1021 tests passed. after: 82 / 1021 passed. Identical counts — no file or test silently dropped. Re-run on c7bc6155b: 82 / 1021.
pnpm exec eslint . (plain form, no --no-inline-config)exit 0 — full repo, not narrowed: 4,022 files linted per --format json, 0 errors, 11,516 pre-existing warnings. All seven changed files are in the population; every warning on them is @typescript-eslint/no-explicit-any on lines outside my hunks.
node scripts/check-changeset-presence.mjsexit 0 — 3 released-source files changed, 1 changeset declared
check:action-forward-parityexit 0 — 5 surfaces checked against 39 runtime-read keys from 4 consumers
check:spec-symbolsexit 0 — 16 declared dialects, 0 untriaged collisions in 0 packages
check:control-bytesexit 0 — 5,782 tracked text files
check:doc-types, check:designer-field-key-parity, check:icon-record-names, check:self-import, check:esm-specifiersexit 0 (derived from the paths this diff actually touches)
check:readme-exportsNOT MEASURED locally — the gate prints the population COLLAPSED -- this run proves nothing without a full workspace build (388 complaints, all dist/index.d.ts is not on disk, across packages this diff never touches). readme-exports.yml builds first; this one is CI's.

Two touches outside packages/types, both declared

Neither is drive-by; both are the same defect and both are named here because the divide-line scan belongs in the PR body.

  1. scripts/check-action-forward-parity.mjs extracts interface ActionSchema from packages/types/src/ui-action.ts by name, and its own failure message reads "The renderer view moved or was renamed; re-point this gate at it." — so it is re-pointed at UIActionSchema, together with the three fixtures in scripts/__tests__/check-action-forward-parity.test.ts that spell the old name (fixture triage: they merely used it; the negative control interface Other still proves a renamed view is red). That suite is owed because the gate script itself is edited — and it earned its keep: it went red first, 32 failures, before the fixtures were corrected.
  2. scripts/check-spec-symbol-derivation.mjs's ALLOW["@object-ui/types:ActionSchema"] reason said "Two deliberate objectui-side shapes". After the rename only crud.ts carries the name, so the prose is corrected; that file's own header says such a comment is load-bearing rather than decorative. The entry stays (it is keyed package:name and crud.ts still matches), so the shrink-only ratchet does not fire.

Scope

Exactly the three names the PM sized. Nothing else from the 46 — not while in the file. KanbanCard / KanbanColumn (#6155), KanbanSchema / MarkdownSchema (#6172) and ComponentConfig (#6298) are untouched. The #6298 fence held: git diff --name-only -- packages/types/src/base.ts is empty; the eight changed files are disjoint from base.ts.

Amendment record

This body was amended once after the contract review (#6349 (comment), verdict ACCEPT-WITH-CONDITIONS): the compatibility sentence is now value-scoped with the keyof caveat named, and the .d.ts table gained the seven zod/*.zod.d.ts byte-differences the reviewer measured as order-only. Body only — no commit; the head is still c7bc6155b, the tree the gates above were run on. Authored by Claude Code, session https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB.

Generated by Claude Code

The first batch of objectui#6349: the three collisions that live entirely
inside `packages/types`, where a barrel consumer cannot disambiguate them.
`ActionSchema` was declared in `crud.ts` and in `ui-action.ts`. Measured
member-by-member the two share 9 keys out of 28 each, `crud.ts` extends
BaseSchema and pins `type: 'action'` while `ui-action.ts` extends nothing and
types `type` as `ActionType` — unrelated types, so this took the rename branch
(objectui#5044 precedent). `ui-action.ts`'s declaration is now spelled
`UIActionSchema`, the name `src/index.ts` has always published it under; the
barrel alias becomes a plain re-export and the public surface is unchanged.
`scripts/check-action-forward-parity.mjs` extracts that interface by name and
its own error message asks to be re-pointed when it moves, so it is, together
with its fixtures; `check-spec-symbol-derivation.mjs`'s ALLOW reason described
both shapes and now describes the one that is left.
`BreadcrumbItem` / `BreadcrumbSchema` were declared in `data-display.ts` and in
`navigation.ts`. The data-display pair was a strict SUBSET copy — no key
declared differently, missing `icon` / `onClick` / `siblings` and `maxItems` —
while `registry.ts`, the barrel, the zod mirror, the renderer and the docs page
were all already on the navigation declaration. `data-display.ts` re-points at
it, so the published `@object-ui/types/data-display` subpath and the
`DataDisplaySchema` union finally declare what the renderer honours.
The three `KNOWN_COLLISIONS` lines come down in the same change; that baseline
fails in both directions, so converging without deleting them is red too.
43 entries -> 40.
Refs #6349
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3179.6 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-RnBefW7y.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.09KB116.42KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.26KB63.17KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 02:59
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit b84dc18Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-6349-types-internal-collisions-batch-1 branch August 31, 2026 03:12
os-sam pushed a commit that referenced this pull request Aug 31, 2026
…inor
Two fixes to the review of #6318's PR.
1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red:
the two schemas this branch added to `@object-ui/types` collided by name with
the pre-existing declarations in their owning plugins.
The two shapes were compared STRUCTURALLY before choosing, not by reading:
a tsc probe asserting mutual assignability in both directions, per pair, plus
a per-member probe for `language` — the one member the two spelled
differently (`'javascript' | … | string`, which TypeScript collapses to
exactly `string`). Every relation held, and a deliberate counter-probe
against an unrelated shape failed as required, so the green was a
measurement rather than a vacuous assertion. Same heritage, same member set,
same per-member types and optionality ⇒ these are re-points, not renames.
Direction is forced: `@object-ui/types` is the lower layer and cannot import
from a plugin without a cycle, so the plugins re-point at types'.
`export type { X } from '@object-ui/types'` is a re-export, which the gate
does not count as a second declaration.
⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own
test file — #6936 and #6937 both have unlanded edits to it.
The published import paths are unchanged: `check:readme-exports` still
resolves both README self-imports through the re-export (386 judged, 0
wrong-path, 0 fabricated), and `objectui check` reports a byte-identical
file list, so the accept set of the validator did not move.
2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract
review: the package's own precedent (the `visible` widen, and
`ViewNavigationConfig`) grades a change of this size `minor` on the stated
grounds that it is more than patch describes. Purely additive, so no
migration note is owed beyond the regrade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Sign up for freeto 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.

2 participants

@os-sam@claude