Skip to content

feat(plugin-list, plugin-chatbot): the list view and the chatbot pick with the shared Select (objectui#11865, slice p) - #12025

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11865-listview-chatbot-select
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11865-listview-chatbot-select

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #11865
Clause-②: no

Slice p of claim 6073419396 (domain:ui seat 2, account os-bill, session session_01MgfduSkFrfM3eorB3UGfAU): the native selects left in the list view and the chatbot now use the shared Radix Select from @object-ui/components, the control the rest of the console picks with. It follows the card's landed slices (objectui#11976 through objectui#12021): items carry option indexes, so an option whose value is '' needs no empty SelectItem, and a stored value outside the options shows as its own item. Each picker is module-private; nothing new is exported.

File Control Picker Console chunk (base build)
packages/plugin-list/src/ListView.tsx "Color by field", in the toolbar's Color popover ListOptionPicker plugins-views, eager
packages/plugin-list/src/ListView.tsx "Rows per page", in the record-count bar of views without a grid pager ListOptionPicker plugins-views, eager
packages/plugin-chatbot/src/ChatbotEnhanced.tsx the composer's model picker ModelPicker plugin-chatbot, eager

Count first. On base 2063f7a96 the census matched 3 lines in ListView.tsx and 1 in ChatbotEnhanced.tsx; the real elements are 2 and 1. The third ListView.tsx line sat in the multi-line JSX comment above the rows-per-page selector ("Rendering a second native select here produced a duplicate control"). That comment now says "a second selector", so the census stops counting it. On head, each file matches the census once more, in prose: the new picker's docblock says the control "used to be a browser-native select", in the same words as the twin's docblock.

What stays the same

Every option writes what the native control wrote. A scratch probe on base 2063f7a96 fired a change event for every option of each native control and recorded the write. Each pin compares against those readings as JSON text:

  • Color by field: the row-colour config the list hands its grid, over four configs: none, a field with colours, a field with no colours, and a field none of the options carries. "None" still writes undefined, and a picked field keeps the config's colours.
  • Rows per page: the size handed to onPageSizeChange, and the next fetch's $top, over three paginations: a declared 25, none declared, and a declared 7.
  • Model picker: the id handed to onModelChange, over three selections: none (the first model is in force), an offered model, and a model none of the options carries.

Re-picking the current option writes nothing, as a browser's native select did. The probe's synthetic change fired for the current option too, so the re-pick rows pin the primitive.

Accessible names:

  • The model picker keeps its name, the aria-label its model label gives it (pinned, with a label override).
  • "Color by field" and "Rows per page" had no name on base: the colour label has no htmlFor, the "Rows per page" text is a span, neither had an aria-label, and the probe read an empty name. So none is kept, and none is added, as slice m did for the twin.

Keys: Enter opens each picker and Enter on an option selects it; pinned on all three. In the chatbot this happens inside the composer's form, and the draft is not sent (pinned).

Read-only: the list's two surfaces have no read-only state. The chatbot's readOnly transcript has no composer, so no picker, as before (pinned). Its disabled prop did not disable the native select on base and does not disable the picker now: unchanged, and noted below.

One display change

A value none of a picker's options carries now shows as itself. On base, the native select showed its first option instead:

  • a row-colour field ghost outside the list's columns read "None", while the popover's Clear button said a rule was set;
  • a page size in force that is not an option read as the first option: with none declared (an unpaged view fetches 100) and with a declared 7, the selector read "10". In a browser that first option could then not be picked, since the native select already showed it as selected;
  • a selected model the environment no longer offers read as the first model.

All three are pinned, and picking the outside item writes nothing.

"Color by field" now matches its twin, ViewSettingsPopover's ColorFieldPicker (objectui#12019), whose header asks the two to stay in step: the same outside item and the same "None" handling. ViewSettingsPopover.tsx is not edited.

objectui#11984's field-read pin, carried in

ListView.fieldListRead-11984.test.tsx read ListView's own "Color by field" through native-select readers. They now read the shared Select:

  • the wide toolbar's Row color position reads the listbox through the pin's pickerLabels(); the native selectLabels() helper is removed;
  • the stored-config row-colour row reads the trigger text and the listbox, where it read toHaveValue;
  • one row is added: a rule on an unreadable field that is also outside the columns gets no outside item. The picker is handed the offered rule (offeredRowColorConfig), so a field the caller may not read never reaches that item.

Every enumerated position still asserts the same field set. The ablations ran through objectstack scripts/ablation-replace.mjs in wrap mode. Each printed "ok mutation landed", then "ok restored: blob == HEAD" with git diff HEAD empty.

Ablation of ListView.tsx Pin on base 2063f7a96 Pin on head 16f8fa0c5
allFields drops canReadField (object columns) 11 of 20 red 13 of 21 red
offeredRowColorConfig drops canReadField not run 2 of 21 red (head 6c4bf4ff1)
  • allFields ablation: the base 11 stay red, the wide Row color position and the View settings one among them. Both stored-config row-colour rows join them, red at the listbox read, which offered "Secret Note" and "Secret Status". On base the stored-config row stayed green: its native reader never read allFields. Their full-read legs cannot turn red under this ablation, by construction: with full read, canReadField answers true.
  • offeredRowColorConfig ablation: both stored-config row-colour rows turn red, and the trigger reads secret_note. That is the outside item naming a withheld field, the exact hazard the added row pins.
  • Neither the pin nor ListView.tsx changed after the heads named.

Reverse leg (both sources put on their base blobs and checked by hash, trap-restored to HEAD, blob checked): the five touched test files read "Tests 54 failed | 255 passed (309)".

  • ListView.sharedSelect-11865: 34 of 34 red.
  • ChatbotEnhanced.modelPicker-11865: 13 red, 2 green. The green rows are the name row and the read-only row, which keep a behaviour.
  • ListView.test: 3 red (the page-size readers), 145 green.
  • ListView.fieldListRead-11984: 3 red (the Row color position and both stored-config row-colour rows), 18 green.
  • ChatbotEnhanced.test: 1 red (the model-picker test), 90 green.

First-load bytes

apps/console, CI=true pnpm exec vite build, under the verify lock, in this worktree: at base 2063f7a96 before any edit, and at head 4a02099e4.

eagerGzipBytes eagerRawBytes eager chunks
base 2063f7a96 3,237,430 10,893,710 289 of 2474
head 4a02099e4 3,237,589 10,894,267 289 of 2474
delta +159 +557 0
  • The movers are plugin-chatbot (+153 gzip / +335 raw: ModelPicker) and plugins-views (+18 / +222: ListOptionPicker; the twin already brings the shared Select into that chunk). The rest is minifier churn: index -8, studioScope -2, ViewVariantInspector -1, src -1.
  • pnpm check:eager-closure on the head build prints "Console eager closure is 3161.7 KB gzipped across 289 of 2474 chunks (budget: 3204.6 KB, headroom: 42.8 KB)", with the chunk membership green.
  • scripts/check-eager-closure-budget.mjs is untouched.

Evidence (head 4a02099e4)

  • pnpm exec vitest run --maxWorkers=2 packages/plugin-list/ packages/plugin-chatbot/ packages/core/src/utils/__tests__/column-identity.ratchet.test.ts scripts/__tests__/one-authority-per-exported-name-6273.test.ts, at the repo root under the lock, reads "Test Files 185 passed (185)" and "Tests 1992 passed (1992)", with VERDICT command-exit 0.
  • After building the closure (pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-list^...' --filter '@object-ui/plugin-chatbot^...' run build: 13 of 47 projects, exit 0):
    • pnpm --filter @object-ui/plugin-list type-check exits 0;
    • pnpm --filter @object-ui/plugin-chatbot type-check exits 0;
    • each runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFilesOnly lists all five touched test files.
  • pnpm exec eslint --format json on the 7 touched source and test files reads 0 errors. Warnings per file equal the base blob's: ListView.tsx 187, ChatbotEnhanced.tsx 10, ListView.test.tsx 26. The others, both new pins included, read 0.
  • node scripts/check-changeset-presence.mjs exits 0: "7 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)".
  • node scripts/check-changeset-no-major.mjs exits 0.
  • pnpm check:new-line-citations exits 0: "0 new citation(s)".
  • pnpm check:control-bytes exits 0, and a control-byte grep over the changed files finds nothing.

Not on it

  • No write path beyond the control swap.
  • Not touched: packages/components/src/ui/**, packages/i18n/**, StudioDesignSurface.tsx (slice o), ViewSettingsPopover.tsx and scripts/check-eager-closure-budget.mjs.
  • No README or guide describes these three controls' markup, so no doc changes. The one native select in the chatbot README is consumer example code for useAgents.

Acceptance notes

  • "Color by field" and "Rows per page" have no accessible name, on base and on head. Their visible labels are not associated with them. Slice m noted the same for the twin. Carrier: none.
  • In AiChatPage.tsx, the comment beside models= says the chatbot "renders the footer" native select. It is now the shared Select. That is wording only, in a file outside this slice. Carrier: the next PR that touches AiChatPage.tsx.
  • The chatbot's disabled prop disables the composer's textarea and submit, but not the model picker, on base and on head. This slice keeps that. Carrier: none.
  • elements/prompt-input.tsx already exports PromptInputSelect wrappers around the shared Select, and nothing calls them. This slice uses the shared Select directly, as triage directs. Carrier: none.

Generated by Claude Code

claude added 4 commits October 9, 2026 03:17
… with the shared Select (objectui#11865, slice p)

ListView's "Color by field" and its rows-per-page fallback selector, and
ChatbotEnhanced's model picker, were browser-native select elements. Each
is now the shared Select through a module-private picker: items carry
option indexes, so the "None" option (value '') needs no empty SelectItem,
and a value outside the options shows as itself. Each pick writes what the
native control wrote.

Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU
Co-authored-by: Claude <noreply@anthropic.com>
…ove objectui#11984's readers (objectui#11865, slice p)

New pins for ListView's "Color by field" and rows-per-page selector and for
ChatbotEnhanced's model picker: each is the primitive, writes what the
native control wrote (literals read on base), shows an outside value as
itself, and opens and selects from the keyboard.

objectui#11984's field-read pin read ListView's own "Color by field"
through native-select readers. The wide Row color position now reads the
listbox, and the stored-config rows read the trigger and the listbox; a
new row pins that a rule on an unreadable field outside the columns never
becomes the picker's outside item.

The existing page-size and model-picker tests read the shared Select.

Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU
Co-authored-by: Claude <noreply@anthropic.com>
… pickers (objectui#11865, slice p)

Also types the new pin's grid spy props instead of `any`.

Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU
Co-authored-by: Claude <noreply@anthropic.com>
…bjectui#11865, slice p)

The reverse leg read the name row and the read-only row green on the
native control, by design; the pin's direction note now names both.

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

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 289 chunks) 3161.7 KB 3204.6 KB
Main entry chunk (gzip) 71.6 KB 350 KB
Entry file index-ZjAPUxHw.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) 19.52KB 7.19KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 587.83KB 141.44KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 268.74KB 68.04KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.11KB 45.88KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.62KB 69.16KB
plugin-kanban (index.js) 52.77KB 16.56KB
plugin-list (index.js) 120.25KB 30.26KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.20KB 23.04KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 04:25
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 04:25
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 8f8f760 Oct 9, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11865-listview-chatbot-select branch October 9, 2026 04:43
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.

2 participants