Skip to content

fix(app-shell): a create draft shows the spec seed's structural defaults before the first save (objectui#12866) - #12886

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12866-create-draft-seed
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12866-create-draft-seed

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12866
Clause-②: no

What changed

A new dataset's designer read Measures 0 before the first save, while the save wrote the spec create seed's count measure. The create page now puts the seed's STRUCTURAL defaults into its draft, so the designer shows before the first save what the save writes.

  • createBody.ts: new exported seedStructuralDefaults(seed), the seed minus its placeholder identity (seedPlaceholderKeys, objectui#12810). buildCreateModeBody now calls it for its base instead of repeating the copy-and-delete inline. The save body is byte-for-byte what it was: same keys removed, same { ...base, ...draft } spread, same createDefaults fallback when the server sends no seed.
  • ResourceEditPage.tsx: one effect in create mode. When the /meta/types entry carries a createSeed, it adds the seed's structural keys to the draft, once, and only for keys the draft does not hold yet. A type with createBuildBody (view) is not seeded. The values are copied (structuredClone), so the designer never patches the registry entry's own arrays.

Where the seed becomes available (mechanism assumption A1, measured)

Async. useMetadataTypes starts with entries: [] and fills them from client.listTypes() inside a useEffect, so the draft's useState initializer always runs before the entry exists. The seeding is therefore on arrival, not at init. The effect also covers an entry that is already present at mount, which is how the existing page tests serve it.

Why "keys the draft does not hold" is the rule: buildCreateModeBody spreads the draft over the seed, so a key the draft holds is saved from the draft, and an absent key is exactly the one the save fills from the seed. Adding only absent keys keeps the before-save view equal to the save body, and never overwrites anything the author (or createDefaults) set first.

Tests

New: ResourceEditPage.createDraftSeed-12866.test.tsx, through the real MetadataResourceEditPage with the real dataset designer, over a real MetadataClient on an in-memory server. The type-registry hook can hold the entry back and release it, the way the live one arrives.

  • (a) entry served at mount: Measures reads 1 and a measure named count is shown before save; after naming the dataset and picking a base object, the saved body's measures equals the seed's.
  • (a) entry arriving after the first render: Measures reads 0, then 1 once the registry answers; the saved body carries the seed's measures.
  • (b) CONTROL: the Label box is empty, the seed's label is not displayed anywhere, and the seed's placeholder object appears nowhere on the page.
  • (c) a measure the author added before the registry answered is still the only measure after it does (no count overwrite).

Two existing page-level pins (ResourceEditPage.seedPlaceholder-12810.test.tsx, inspectors/DatasetDefaultInspector.firstPickKeepsCount-12848.test.tsx) queried the single textbox named Name. The seeded count row now has a Name textbox of its own, so both now read the first one, which is the dataset's. Nothing they assert changed.

Ablation, run with the fix committed, through ablation-replace.mjs (anchor must hit, restore proven by blob hash):

  • Seeding dropped (the effect's guard replaced by a bare return): blob 8f81c0af5620 to 7ef7355f8dc8, both (a) tests and (b) red, (c) green (it cannot fail when nothing is seeded). Restored: blob equals HEAD 8f81c0af5620, git diff HEAD empty.
  • Seeding mutated to overwrite keys the draft holds: (c) red, the other three green. Restored the same way.

Gates (at 7e7a0b67fb)

  • Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build: exit 0.
  • pnpm --filter @object-ui/app-shell type-check (tsc --noEmit and tsc -p tsconfig.test.json): exit 0. --listFilesOnly confirms the new test file is in the test project.
  • vitest, the packages/app-shell/src/views/metadata-admin/ directory: 544 files, 6130 passed, 1 skipped, exit 0. A whole-package packages/app-shell/ run was stopped after about 29 minutes with no output under --maxWorkers=1. It is declared to CI, not measured here.
  • eslint --no-inline-config on the five changed files: ResourceEditPage.tsx has 3 errors and 57 warnings, the same count as that file at the base commit (react-hooks/static-components, on lines this PR does not touch). The other four files have 0 errors and 0 warnings. The eslint config enables no type-aware linting, so this diff cannot change the verdict on any untouched file.
  • Pre-tsc battery: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs and check:new-line-citations all exit 0.
  • check:spec-floors: exit 1, NOT MEASURED. Its five findings are all no-artifact (packages with no build output in this worktree, app-shell among them), not a floor verdict.

Acceptance notes

  • The card's screenshot (acceptance 2) is the maintainer's. None is attached here.
  • Every type with a spec seed and no createBuildBody now shows its structural seed keys in the create draft. In each case the save already wrote these values. Examples: object pluralLabel: New Objects (the label-to-plural derivation still replaces it as the author types a label), agent role and instructions, email template subject and bodyHtml, tool description, mapping's sample fieldMapping row, action type and body. Whether some of these are placeholders rather than structure is a question about seedPlaceholderKeys (objectui#12810), which this PR shares and does not change.
  • Edge case: a key the author set and then removed before the registry answered is absent from the draft, so it is filled from the seed. That matches what the save does for an absent key.
  • The header of ResourceEditPage.seedPlaceholder-12810.test.tsx still says the object picker resets measures on the first change. That has been out of date since objectui#12848 and is left as it is.

The session URL is https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.


Generated by Claude Code

…aults (objectui#12866)

A new dataset's designer read Measures 0 before the first save while the
save wrote the seed's `count` measure. The create page now folds the
seed's structural defaults into its draft once the `/meta/types` entry
arrives, only for keys the draft does not hold; the placeholder identity
stays out through the same `seedStructuralDefaults` the save body uses.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…has one too (objectui#12866)

The create draft now carries the seed's `count` measure from the start,
so a new dataset's designer renders a measure row with its own Name
textbox; the two page-level pins read the first one, the dataset's.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3141.8 KB 3744.1 KB
Main entry chunk (gzip) 80.3 KB 350 KB
Entry file index-mtQZp39s.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 606.32KB 146.86KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.19KB 74.38KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.33KB 11.96KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 210.60KB 49.94KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 265.18KB 70.24KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.22KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 272.78KB 75.66KB
plugin-kanban (index.js) 62.48KB 19.34KB
plugin-list (index.js) 139.20KB 35.82KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.23KB 39.46KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants