Skip to content

feat(app-shell): Studio uses shared picklists — select-field picker, served options left out of object saves, read-only picklist page (objectui#10202) - #11669

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10202-studio-use-picklist
Oct 6, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10202-studio-use-picklist

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #10202

Clause-②: no

Studio now reads shared picklists, the objectui half of objectstack#18164 phase 2, scoped by ruling 6006062645 (A′) items (a)–(c). Item (d), the studio-authoring checklist item, lives in objectstack; its ready-to-file text is in the os-dev-report comment on objectui#10202, for the seat to file.

Dispatched work: claim 6007167258, session https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL.

What changes

(a) "Use picklist" in the select-field editor (inspectors/ObjectFieldInspector.tsx, shared by the metadata-admin object designer and the Studio data page).

  • For a select, radio, multiselect or checkboxes field, an Options from picker (the existing InspectorSelectField, a closed list) offers "This field's own options" and every picklist GET /meta/picklist serves, by name. The roster loads through usePickerLoad, with its loaders held in state once per mount (3114a9f), so a failed read is its own state: the picker says so and still offers the field's own options. A stored name the served list no longer carries is drawn flagged "(not found)".
  • Choosing a picklist sets picklist and removes options outright. Choosing the field's own options removes picklist and the served copy of the options.
  • A bound field refuses inline options: the inline options editor is not offered. In its place the field's values are listed read-only, with the reason. The values are the served field's resolved copy when the draft carries one, else the list's own options from the roster, with a note that values other packages add are offered too (GET /meta/picklist does not merge extensions). The default-value picker offers the same values.
  • A retype to a type the spec lets name no picklist drops the binding. The test is the installed spec's SINGLE_OPTION_TYPES ∪ MULTI_OPTION_TYPES, not a re-listed set.

(b) Served options left out of the object PUT (metadata-admin/picklist-binding.ts, dropServedPicklistOptions).

  • Every field whose picklist is set leaves without options, not only the edited field. Nothing else is touched: a field without picklist keeps its inline options byte-identical, and so does every other key. The function is pure and returns the same reference when there is nothing to drop.
  • Applied where a designer that hosts the select-field editor builds its PUT. The metadata-admin object designer gets it as the object resource's fromDraft in services/builtinComponents.tsx, the page resource's pageSaveBody precedent. The Studio data page applies it at its two object PUTs that re-send the served buffer: the draft autosave and the column reorder.
  • The metadata-admin live Zod pass now judges fromDraft(draft) in edit mode, the body doSave sends. Before, it showed "picklist and options cannot both be declared" on load for every object with a served bound field, measured below. fromDraft is read from the registry by type, so no memoised identity keys the effect.
  • ⛔ Not applied at the transport. MetadataClient.save cannot tell a served copy from an author who wrote both keys, and the second has to stay a loud refusal. The writers this PR does not touch are listed under Acceptance notes.

(c) The picklist page is read-only (previews/PicklistPreview.tsx, registered in previews/index.ts; reader in previews/picklist-extensions.ts).

  • The kind's /meta/types row declares allowRuntimeCreate: false and allowOrgOverride: false, and the generic list and edit pages already honour it. So this adds no read-only mechanism of its own: it registers the detail view.
  • The detail view shows the label, name, description and owning package (_packageId), each option with its label and value, and each extension under the package that declares it. It offers no create, edit or delete.
  • Extensions are read from GET /meta/package, where each manifest carries its picklistExtensions as authored. The list's own read does not serve them. A failed package read says the extensions are unknown, never that there are none.

Live measurement

objectstack main faf8dce4 in its own worktree, showcase booted --fresh. The console was served from this branch. The stock showcase declares no picklist, so a local, uncommitted fixture was added to that worktree only: picklist showcase_priority_tier (Gold, Silver, Bronze), one picklistExtensions entry (Platinum), and one bound field on showcase_account. Writable objects acme_account (field tier bound) and acme_contact (field segment inline) were created in a runtime package com.acme.crm.

reading result
GET /meta/picklist, GET /meta/picklist/NAME name, label, description, own options, _packageId; no extension
GET /meta/object/acme_account/layers effective.fields.tier picklist + 4 resolved options (Platinum included)
before: that served body PUT back with an edited description 422 INVALID_METADATA at fields.tier.options
control: the same body with only tier.options removed 200
today's tree, metadata-admin designer on a served bound field live issue "Fields → priority_tier → options: picklist and options cannot both be declared…" on load
after, metadata-admin designer: rename an unrelated field's label (autosave) PUT 200; tier sent as {type, label, picklist}; inline status options intact; no live issue on load
after, acme_contact.segment: Options from → Priority Tier picker listed "This field's own options" + "Priority Tier (showcase_priority_tier)"; PUT 200 with picklist and no options; "Add value" 1 → 0
after, Studio data page on acme_account: add a field (autosave) PUT 200, tier without options
after publish, new-record forms of both objects each bound select offers Gold, Silver, Bronze, Platinum
after, picklist page owning package, options, "Added by ObjectStack Showcase (com.example.showcase)" → Platinum; every control disabled or read-only, zero write requests (same on today's tree: no create on the list, disabled form)

Tests

New pins, each read off the wire or the real component:

  • metadata-admin/picklist-binding.test.ts
  • inspectors/ObjectFieldInspector.usePicklist-10202.test.tsx
  • metadata-admin/ResourceEditPage.picklistServedOptions-10202.test.tsx, real edit page, real MetadataClient, a transport that refuses the served pair as the door does
  • studio-design/DataPillar.picklistServedOptions-10202.test.tsx, both Studio PUTs
  • previews/PicklistPreview.test.tsx, fed the measured package-row shape
  • metadata-admin/ResourceEditPage.picklistReadOnly-10202.test.tsx, the generic route offers no create, edit or delete

Ablations through objectstack's scripts/ablation-replace.mjs (WRAP mode, run from the committed trees 3019cff/1d8c6c2, and A9 on 3114a9f). Each one: landing proven by anchor count and blob change; restore proven by blob == HEAD and an empty git diff HEAD.

id mutation red
A1 drop the object fromDraft the PUT pin (expected 422 to be 200 at fields.tier.options and fields.regions.options), the advisory pin, the registration pin
A2 live pass judges the raw draft the advisory pin only (1 of 3)
A3a Studio autosave sends objDraft the autosave pin only (422)
A3b Studio reorder sends body the reorder pin only (422)
A4 binding keeps options the bind and unbind pins
A5 inline editor shown on a bound field the two bound-view pins
A6 retype keeps picklist the retype pin
A7 extensions not filtered by extend the extension-filter and reader pins
A8 the strip drops options from every field 6 pins across the helper, edit-page and Studio files
A9 the roster loader back to a client-keyed useMemo the usePicklist pins and the existing ObjectFieldInspector.optionLabel suite hang (killed by timeout 120, exit 124)

The first A3 attempt was a no-op: its replacement text was a substring of the anchor, so the tool refused before running anything. It was re-run with a distinct marker.

Gates

Read on 3114a9f, the final head, unless a line says otherwise (corrected by the seat at landing; this section was first written at 46320ed, while the locked runs were still queued). Each exit code was written to a file before any pipe.

  • The six new test files, run from the repo root (pnpm exec vitest run --maxWorkers=2 FILE…): 30 passed (30), exit 0.
  • Root check:* gates, exit 0 each: i18n-designer-parity, i18n-keys, i18n-drift, designer-field-key-parity, metadata-write-doors (17 doors, 3 object-capable, all guarded), new-line-citations (0 new), control-bytes, test-path-roots, vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, changeset-claims, pending-changeset-literals, unreferenced-sources, spec-symbols, installed-pin-claims, phantom-deps. scripts/check-changeset-presence.mjs and check-changeset-no-major.mjs also exit 0.
  • Under the shared verify lock, on 3114a9f:
    • @object-ui/app-shell type-check, lint and build: exit 0 each.
    • The full pnpm exec vitest run packages/app-shell/, as two --shard runs: 1/2 Test Files 509 passed | 1 skipped (510), Tests 4662 passed | 9 skipped (4671); 2/2 Test Files 510 passed (510), Tests 5423 passed (5423). Together 1019 of 1020 files passed and 1 skipped.
    • The @object-ui/app-shell^... closure build ran on the pre-merge tree: exit 0. The merge brought no declaration change.
  • Clause-② fence, on the 3114a9f build:
    • Walked every .d.ts reachable from packages/app-shell/dist/index.d.ts through relative specifiers: 150 files.
    • None names dropServedPicklistOptions, PicklistPreview, extensionsOf, shownOptions, PicklistExtensionRow, ShownOption, usePicklistRoster, fetchPicklistRoster, PicklistSourceField, BoundPicklistOptions, takesPicklist or PicklistRosterEntry.
    • Positive controls: registerMetadataResource and SupportedLocale are both found.
    • i18n.d.ts is reachable, and it carries none of the new copy keys. Control: an existing key is absent from it too; the new key is present in the emitted i18n.js.
    • No exported type, prop or member changed.
  • An earlier full run had no verdict. The first, unsharded attempt was killed by the cgroup OOM killer after 28 min, with vitest at 7.8 GB anon RSS. The cause was this PR's own render loop: the picklist roster loader was a useMemo keyed on the client, and usePickerLoad re-entered loading on each new loader. 3114a9f holds the loaders in state once per mount and pins it (A9); the two shards above ran after that fix.
  • Not run locally: check:eager-closure. It weighs a built console bundle; CI's performance-budget job runs it. This PR adds the picklist preview to the eager metadata-admin registrations.

Acceptance notes

  • Remaining object writers re-send the served options (family, for the seat). Measured: Studio → Access → OWD overview (?surface=owd:overview), change acme_account's sharing model and Save. The PUT came back 422 at fields.tier.options, with the server's prescription on screen. By reading, the same shape exists in views/runtime-metadata-persistence.ts (two object saves), plugin-designer MetadataFieldsPage / MetadataObjectsPage, and MetadataService.saveObject / saveFields / saveMetadataItem. None of them is touched by this PR, so none is changed here. The fix shape is the same dropServedPicklistOptions at each writer that seeds from a served read.
  • The designer canvas draws a just-bound select with no options: the canvas reads the field's own options. Runtime forms show the list's options (measured above).
  • The picklist page's inspector pane is the generic engine's read-only form: every control is disabled (measured), the same rendering every locked kind gets.

Generated by Claude Code

claude added 4 commits October 6, 2026 02:01
…served options left out of object saves, read-only picklist page (objectui#10202)

- ObjectFieldInspector: an "Options from" picker over the picklists the
  runtime serves (GET /meta/picklist). Choosing one sets `picklist` and
  removes `options`; a bound field shows the list's values read-only and
  offers no inline options editor; a retype to a non-option type (spec
  SINGLE_OPTION_TYPES / MULTI_OPTION_TYPES) drops the binding; the default
  value picker offers the list's values.
- dropServedPicklistOptions: the served -> authored conversion. Every
  picklist-bound field leaves without its resolved `options`; nothing else is
  touched. Applied as the `object` resource's `fromDraft` (metadata-admin
  designer) and at the Studio data page's two object PUTs (autosave, column
  reorder). The metadata-admin live Zod pass judges the `fromDraft` body.
- PicklistPreview: the read-only detail view of the package-owned kind —
  label, name, description, owning package, options, and the
  `picklistExtensions` other packages declare (read from GET /meta/package).

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…e picklist's own only (objectui#10202)

`GET /meta/picklist` serves a list's own options, not the ones other
packages add with `picklistExtensions`. After a save the designer's draft
carries no served copy, so the bound view lists the roster's own values;
it now says the extensions' values are offered too. Measured live: after
an edit of an object bound to a list with one extension, the view listed
three of the field's four runtime values with nothing saying so.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
… of its own (objectui#10202)

PicklistPreview.tsx exported `extensionsOf` beside the component, which react-refresh/only-export-components flags; and its pin imported an unused `waitFor` (object-ui/no-unused-imports, an error). The reader moves unchanged to previews/picklist-extensions.ts.

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

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3321.5 KB 3330.4 KB
Main entry chunk (gzip) 152.3 KB 350 KB
Entry file index-DRTnSwQt.js —
Status PASS —

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 574.72KB 137.94KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
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) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.18KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.39KB 47.02KB
plugin-dashboard (index.js) 143.75KB 38.87KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 247.23KB 65.04KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.09KB 45.89KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 238.48KB 65.51KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

…ce per mount, not keyed on a memoised client (objectui#10202)

usePickerLoad re-runs its request whenever the loader's identity changes and re-enters `loading` as it does. The roster loader was a useMemo keyed on `client`, so a host whose client is not referentially stable re-minted it on every render: a render loop. Measured: ObjectFieldInspector.optionLabel.test.tsx, whose mock returns a fresh client per call, held a worker at 100% CPU (killed at 120 s); it passes with this change. Both loaders now live in useState, whose identity React guarantees (AGENTS.md #10). Pinned by a fresh-client mount that counts one roster read.

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

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3322.5 KB 3330.4 KB
Main entry chunk (gzip) 152.7 KB 350 KB
Entry file index-C59rtGpI.js —
Status PASS —

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 574.72KB 137.94KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
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) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.18KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.81KB 47.14KB
plugin-dashboard (index.js) 143.75KB 38.87KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 247.23KB 65.04KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.09KB 45.89KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 238.48KB 65.51KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT: PR objectui#11669, head 3114a9f. It lands when every check on this head is green

domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL (os-steve) · 2026-10-06T06:29Z. Reviewed against GitHub and origin/main, not against the report's prose (report 6010658114 on objectui#10202).

  • Shape.
    • The PR is a draft against main, and its assignee is os-steve. Its first line is Fixes #10202, the only line with a closing keyword next to an issue number. Clause-②: no is on its own line.
    • The diff is 16 files, +1743/−7, all in app-shell. Six are new pin files.
    • objectui check-governed-queue-guard.mjs --test answers NOT GOVERNED over all 16 paths.
  • The ruling as written (6006062645, A′).
    • (a) The select-field editor (ObjectFieldInspector, hosted by both the metadata-admin designer and the Studio data page) gets an "Options from" picker over GET /meta/picklist. Choosing a list sets picklist and removes options. A bound field offers no inline options editor and shows its values read-only. A retype to a non-option type, per the spec's SINGLE_OPTION_TYPES ∪ MULTI_OPTION_TYPES, drops the binding.
    • (b) dropServedPicklistOptions removes options from every field whose picklist is set and touches nothing else. It returns the same reference when there is nothing to drop. It runs as the object resource's fromDraft and at the Studio data page's two object PUTs. ⛔ It is not at the transport, so an authored picklist + options pair still gets the door's loud refusal.
    • (c) The picklist page reuses the generic route's declared read-only mechanism (the kind's allowRuntimeCreate/allowOrgOverride are false) and registers a read-only detail view. It reads extensions from GET /meta/package, because GET /meta/picklist does not serve them, and a failed package read says "unknown", never "none". No create, edit or delete is offered: pinned, and 0 write requests measured live.
    • (d), the studio-authoring checklist item, is filed in objectstack as platform-checklist: add studio-authoring.picklist-bound-select, the acceptance item of objectui#10202 (a package picklist bound from the designer: forms offer its options, inline options are refused, an object with a bound field saves) objectstack#21957, from the dev's measured entry.
  • Surface, accepted.
    • (b) lands in services/builtinComponents.tsx and studio-design/StudioDesignSurface.tsx, inside app-shell as the claim's parenthetical allowed.
    • The metadata-admin live Zod pass now judges fromDraft(draft), the body the save sends, so the false "picklist and options cannot both be declared" advisory is gone. Pinned (A2).
  • Live (objectstack main faf8dce4, local uncommitted fixture, since removed).
    • Before: a served bound field PUT back gets 422 at fields.tier.options. A control with only tier.options removed gets 200.
    • After: 200 in both designers; picker binding saves picklist without options; both objects' new-record forms offer the list's four values, the extension's included; the picklist page is read-only.
  • Ablation. A1–A9 all go red, and each restore is proven.
    • A1, dropping the object fromDraft, turns the real-edit-page pin into expected 422 to be 200.
    • A9, the roster loader back to a client-keyed useMemo, hangs the suite.
  • Gates at 3114a9f.
    • The full app-shell suite ran as two shards under the lock: 1019 of 1020 files passed, 1 skipped.
    • Type-check, lint and build: exit 0.
    • 17 check:* gates and both changeset checks exit 0.
    • CI on this head: 43 checks, 40 green and 3 expected skips.
  • Clause-② fence, measured. Walking dist/index.d.ts reaches 150 declaration files, with positive controls registerMetadataResource and SupportedLocale found. None names any new symbol. The new copy is in metadata-admin/i18n.ts, which the entry does not publish as keys.
  • Changeset. It is @object-ui/app-shell: minor: a new authoring capability, with no exported surface change. Its sentences match the diff.
  • PR body corrected by the seat at landing. The "Gates" section was written at 46320ed while the locked runs were still queued. It now carries the 3114a9f readings above, the A9 row and the OOM cause. The squash commit carries the body, so this was fixed before landing. The edit was issue_patch through the relay and reads back identical.
  • Incident, recorded.
    • Before 3114a9f, this PR's own render loop drove the unsharded full run to 7.8 GB, and the cgroup OOM killer took it. In the same event it took another dev's console dev server (objectui#11664's, which has since landed).
    • Both runs held the shared verify lock while others queued.
    • The cause is fixed and pinned (A9). No lasting effect on any other card.

Filed this round from the report, not acted on in this PR:

  • objectui#11692. The object writers outside the two designers re-send the served options. The Studio OWD overview's save measured 422; the others were found by reading. The fix direction is not ruled; the dev recommends per-writer.
  • Noted, not filed.
    • The metadata-admin canvas draws a just-bound select with no options; runtime forms show them.
    • ObjectFieldInspector's useObjectOptions keys on the client identity. Production memoises the client, so it is safe there.

Landing: on all-green checks on this head, ready, then auto-merge into the merge queue.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 6, 2026 06:31
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 6, 2026 06:31
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit c3623eb Oct 6, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10202-studio-use-picklist branch October 6, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: picklist page and 「use picklist」 in the object designer — the objectui half of objectstack#18164 (phase 2)

2 participants