Skip to content

feat(types)!: a list view's legacy options bag is the spec list overlay's bag by reference (objectui#6152, round 12) - #12014

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-6152-r12-options-bag
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-6152-r12-options-bag

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #6152 (round 12)
Clause-②: no

This is the round's implementing PR. The seat answered the round's census (report 6070437445) with Q1 → A, Q2 → B and Q3 → A on objectui#6152 (comment 6070475966), under claim 6070030626, and amended the file surface there. Whether the card closes after this round is the seat's call, so this body carries no closing keyword. Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw.

What changes

@object-ui/types, narrowed (breaking). @objectstack/spec's authoring list view declares no options bag. Its one home is the flattened list overlay on the view write door, judged there under objectui#10380's ruling A. ListViewSchema.options was a record of any with three named refusals, so every door accepted what that door refuses. It is now that member's own bag, by reference: stripImportedDefaults(VIEW_METADATA_MEMBERS.listOverlay.shape.options.unwrap()), extended with this package's round-11 kanban, calendar (.partial()), gallery and timeline blocks. So the bag is strict, has the spec's eight kinds, each the list-view slot with every key optional, and keeps the spec's own error map, including the guidance for options.grid. Each named refusal gives one message under options.KIND and under the top-level KIND.

written under options now write instead
a key that is not one of the eight kinds unrecognized_keys at options remove it
grid unrecognized_keys at options, with the spec's guidance the view's own top-level keys
any undeclared key in a kind (kanban.swimlaneField, timeline.descriptionField, tree.titleField, …) the spec's unrecognized_keys at that kind the kind's spec keys
kanban.groupField / kanban.cardFields invalid_type, by name groupByField / columns
gallery.imageField invalid_type, by name coverField
timeline.dateField invalid_type, by name startDateField
calendar.defaultView invalid_type, by name the object-calendar element's flat defaultView
chart.xAxisField / yAxisFields / categoryField / valueField / aggregation the spec's unrecognized_keys at chart chart: { dataset, dimensions, values }
a value of the wrong type refused at its path the declared type

The three refusals already in place (kanban.groupBy, objectui#8365; calendar.dateField / endField, objectui#8355) keep their messages and now report invalid_type, where they reported custom while the bag was a record. The TypeScript face follows: ListViewSchema['options'] has the eight kinds and no index signature.

@object-ui/types, the two config types. ListViewTimelineConfig is now the non-null ListViewSchema['timeline'] (Q2 → B): the spec slot, strict and .partial(), with dateField refused by name. ListViewGalleryConfig is retired (Q3 → A); the remedy is the spec's GalleryConfig, which the package re-exports.

@object-ui/app-shell, the producers (Q1 → A). In ObjectView.tsx, kanbanViewOptions writes columns where it wrote cardFields; timelineViewOptions stops writing descriptionField; galleryViewOptions stops writing imageField beside coverField. Measured on the card before this round: the board's node and $select are identical under columns, and nothing drew the timeline's nested descriptionField. The legacy READS in these producers stay.

@object-ui/plugin-list. One capability-gate rung: availableViews also reads options.gallery.coverField (the objectui#8193 kanban precedent), so the coverField-only bag the relay now writes still offers the Gallery view. One more, compile-forced line in ListView.tsx, beyond the seat's "one rung": the grid branch's options.grid spread is typed at the read site as the stored input it is, because the typed bag no longer declares grid. It renders exactly as before. The README's two examples author the top-level per-kind blocks and a dataset-bound chart, and no longer show the bag.

@object-ui/plugin-timeline. ObjectTimelineProps.schema.timeline takes the new ListViewTimelineConfig; the prop's docs say what it is.

What did not move

The renderers' reads: ListView still merges each options.KIND under the top-level block and still reads the legacy spellings (groupField, cardFields, imageField, dateField, the chart axes, tree.titleField, the grid spread), and ObjectTimeline still reads timeline.dateField. The render path parses nothing, so a row stored before these doors closed renders as before. packages/plugin-view is untouched (PR objectui#12010 holds it).

Fixtures the narrowing flips, re-spelled with their tests

  • calendar-date-alias-refusal-8355.test.ts: surface 2 now pins invalid_type and asserts the message equals surface 1's; its "nothing else is judged" scope control becomes an undeclared-key control.
  • kanban-stray-group-by-refusal-text-9653.test.ts, ListView.strayGroupByRefused-8365.test.tsx: the bag channel's code is invalid_type.
  • list-view-dataset-chart-filter-refusal-10327.test.ts: the object-bound bag control becomes a bag that binds no dataset (what it guards is unchanged).
  • object-chart-legacy-axis-keys-retired-10608.test.ts: block (e) pinned the bag ACCEPTING the legacy axes; it now pins the refusal, with a dataset-bound control.
  • imported-defaults-8317.test.ts: the new crossing is measured. VIEW_METADATA_MEMBERS is a record of schemas, not a schema, so it cannot be the direct argument of stripImportedDefaults. The census gains one enumerated category, SCHEMA_RECORDS, which accepts a read only when the whole member chain is that argument, and a test that each entry is a non-Zod record of schemas read only that way; IMPORTED gains the bag as a row.
  • app-shell: ObjectView.kanbanGroupByRetired-8213 (its residual set reaches zero), galleryBinding-7547, kanbanLane-8193, kanbanSummarizeField-11629.
  • plugin-list ListView.test.tsx: two typed fixtures write groupByField.
  • plugin-timeline timeline-date-binding and ObjectTimeline.absentDateAxisRefusal-7459: both keep the alias fixture (it models a stored block the reader must still bind), and each gains a real @ts-expect-error on a typed ListViewTimelineConfig carrying dateField.

New pins

  • packages/types/src/__tests__/list-view-options-bag-6152.test.ts: the installed overlay bag as a precondition (key set derived by the spec's own rule, each kind strict and fully optional); the mirror IS that bag (same keys, same error map, the round-11 blocks by identity); verdict equality with the spec's bag over 17 documents, plus the sanctioned default difference; every named refusal under options.KIND through all four doors with the top-level message; undeclared keys and kinds through all four doors; the TypeScript face, ListViewTimelineConfig, and the retired ListViewGalleryConfig import as a @ts-expect-error.
  • packages/plugin-list/src/__tests__/ListView.galleryOptionsBagCanonical-6152.test.tsx: the Gallery toggle both ways.
  • packages/app-shell/src/views/ObjectView.optionsBagParses-6152.test.ts: each exported producer's block parses at the bag's door, with a control that the door still refuses the three retired writes.

Verification, on 372fa8f8f

  • Test union under the verify lock: packages/types/, packages/plugin-list/, packages/plugin-timeline/, scripts/__tests__/, app-shell's ObjectView.* and InterfaceListPage.* tests, and every test naming the bag's kinds or the two config types (80 extra files): Test Files 790 passed | 2 skipped (792), Tests 18251 passed | 39 skipped (18290), VERDICT command-exit 0.
  • Closure build (turbo run build --filter='./packages/*', then the console): 39 of 39 tasks. Type-check, after it: types, app-shell, core, i18n, plugin-calendar, plugin-gantt, plugin-grid, plugin-list, plugin-timeline, plugin-tree, plugin-view, console, example-byo-backend-console, each Done. plugin-timeline's test leg reads @object-ui/types through its rebuilt dist, and its two @ts-expect-errors compile only against the narrowed type, so a stale .d.ts would be red there.
  • Ablations, each through ablation-replace.mjs with an on-disk anchor count and a restore checked against the HEAD blob: removing the new gate rung reds exactly the two coverField arms of the gallery pin (2 failed, 5 passed, as predicted); restoring the record bag reds 89 of the bag pin's 119 tests.
  • Doc gates: check:doc-snippets (784 of 784 blocks, 0 failed), check:readme-exports, check:doc-types, check:doc-examples, check:docs-route-closure, doc links, check:doc-fences, check:doc-example-ids: green.
  • Changeset gates: presence (one changeset for the 23 published source files of 4 packages), no-major, claims, pending-literals: green. The overwrite report lists .changeset/6152-list-view-blocks-strict.md: round 11's pending changeset said the bag was unchanged, and it carries an append-only dated note now, its text and frontmatter otherwise unchanged.
  • check:new-line-citations 0 new, check:control-bytes OK, governed guard: NOT GOVERNED (26 paths).
  • Targeted lint over the 21 changed .ts/.tsx files (eslint --no-inline-config): one error, no-restricted-imports on FormField in packages/types/src/index.ts, identical at the base; nothing new. The repo-wide lint is CI's.

Bundle

check:eager-closure, each tree built the same way under the lock: origin/main 7ebff3991 3,391,414 B gzipped; this branch 3,391,315 B (1a067c36a; the one later commit changes test fixtures only). Delta −99 B, under the 3,391,484 B ceiling. The constant is untouched.

Acceptance notes

  • The options.grid read-site typing in ListView.tsx is the one line beyond the seat's fence; without it plugin-list does not compile, and it changes nothing at runtime.
  • ListViewTimelineConfig keeps its export name, now as the derived type, so ObjectTimelineProps keeps one consumer of it.
  • Stored rows written before the view write door judged the bag (@objectstack/spec 17.5.0) keep rendering through the untouched readers; retiring those readers is the later, renderer-side round on objectui#6152.

Generated by Claude Code

claude added 4 commits October 8, 2026 22:54
…ay's bag by reference (objectui#6152, round 12)

ListViewSchema.options is @objectstack/spec's own list-overlay bag, read
off VIEW_METADATA_MEMBERS.listOverlay: strict, the eight kinds that name
a block, each the list-view slot with every key optional. The bag's
kanban / calendar / gallery / timeline are the round-11 blocks, so each
named refusal gives one message in both nestings; options.grid is
refused with the spec's guidance.

The writers move with it: app-shell's relay writes kanban.columns, no
gallery.imageField and no timeline.descriptionField; ListView's gallery
gate also reads options.gallery.coverField; the plugin-list README
authors the top-level blocks and a dataset-bound chart.

ListViewTimelineConfig is NonNullable<ListViewSchema['timeline']>, and
ListViewGalleryConfig is retired.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw
…the legacy bag too (objectui#6152, round 12)

The bag now nests the declared kanban slot's own block, so the arm is a
member there and both nestings report the same code and message.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw
…ut it is (objectui#6152, round 12)

The typed options bag no longer declares grid (the spec's list-overlay
bag refuses it), so the grid branch's read of a row stored before the
view write door judged the bag is typed at the read site. No change in
what renders; the read retires with the other legacy readers.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw
…y (objectui#6152, round 12)

The typed options bag refuses the legacy groupField by name; neither
test is about the alias, and the gate reads groupByField in the bag too.

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

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3311.8 KB 3312.0 KB
Main entry chunk (gzip) 71.5 KB 350 KB
Entry file index-BVPusRgT.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) 18.33KB 6.73KB
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) 199.63KB 47.46KB
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.56KB 69.16KB
plugin-kanban (index.js) 52.22KB 16.38KB
plugin-list (index.js) 117.83KB 29.43KB
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.16KB 22.97KB
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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 372fa8f8f46f960c2b7aa36c9b1ae3c5456c8186
Local-runs: none

PR objectui#12014, round 12 of card objectui#6152 (draft, base main, first line Part of #6152 (round 12), no closing keyword). Inputs, and nothing else: the card body and all 80 comments (the round-12 claim 6070030626, the census report 6070437445, the seat's answers 6070475966 Q1 → A / Q2 → B / Q3 → A with the amended file surface, the implementation report 6071238136, and the standing rulings the earlier rounds carry: objectui#8327, objectui#10380 ruling A, the 「改成追加说明 (Recommended)」 shape for pending changesets, and the Clause-② reading the round-7 to round-11 records PASSed), the PR body, its 26-file list, the net diff against main at the merge base 7ebff3991733bbe9557a2dc185b1362412b02367 (26 files, +1041 / −210, the same 26 paths as the file list), and the 43 check-runs on this head: 40 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard)), 0 failure, Lint, Type Check, Bundle Analysis, Governed Surface Queue Guard and every Changeset gate among the successes. Gate verdicts are read off those check-runs; nothing was built, run or re-run here, and the dev's local unions, probes, ablations and bundle delta are read as declared. One corroborating read outside the inputs, stated so it is not hidden: the sibling objectstack checkout's packages/spec source (version string 17.7.0) shows ListViewOverlayOptionsSchema as a strict object over listViewKindBlocks(), declared on the list overlay member only, with the grid guidance the PR quotes; the verdict does not rest on it, because the PR's own pin measures the installed spec and is green under the Test shards.

① Derived judgments

Zod accept sets (@object-ui/types/zod: the ListViewSchema mirror, the tolerant AnyComponentSchema, the strict StrictAnyComponentSchema, and safeValidateSchema):

  1. ListViewSchema.options: z.record(z.string(), z.any()) with a three-key .check() to ListViewOptionsBag.optional(), where the bag is stripImportedDefaults(SpecViewMetadataMembers.listOverlay.shape.options.unwrap()) extended with this package's KanbanConfig, CalendarConfig.partial(), GalleryConfig and TimelineConfig — RIGHT, under Q1 → A, objectui#8327 and objectui#10380 ruling A. It NARROWS every door: a key that is no kind (and options.grid, with the spec's own guidance) is refused at options with unrecognized_keys; an undeclared key in a kind is refused at that kind; a wrong-typed value is refused at its path; the strict face, which derived an open bag from the record, now derives the closed one. Nothing widens: the old record accepted everything the new bag accepts. No member is hand-copied: gantt, map, chart and tree are the spec's own blocks, and the four replaced kinds are the round-11 blocks, which are themselves the spec slots by reference plus refusal arms. The pin list-view-options-bag-6152.test.ts holds this in order: the installed overlay bag as a precondition (strict, the key set derived by the spec's own rule — a value of the list view's type enum that names a block — each kind strict and fully optional, and the authoring list view declaring no bag); the mirror's key set and error map identical to the bag's, with kanban, gallery and timeline the top-level blocks by identity and calendar the top-level block made partial; and verdict equality with the spec's bag over 17 documents, accepted and refused rows both present. All green under the Test shards.
  2. The one sanctioned difference, the mirror writing no chartType default into a bag that did not carry it — RIGHT: objectui#8317's boundary rule, pinned as such.
  3. The three refusals already in place (options.kanban.groupBy, objectui#8365; options.calendar.dateField / endField, objectui#8355) keep their messages and move from custom to invalid_type — RIGHT and stated in the changeset. The outcome and the string are unchanged; only the code moves, because the arm is now a declared member in both nestings. The 8355, 9653 and plugin-list 8365 pins re-spell exactly that, and the 8355 surface-2 row now also asserts the message equals surface 1's.
  4. Every named refusal gives one message under options.KIND and under the top-level KIND, through all four doors, with its canonical remedy (groupField → groupByField, cardFields → columns, imageField → coverField, dateField → startDateField, endField → endDateField, defaultView with no home) — RIGHT; section 4 of the pin drives each arm through each door with a lit control that the bag app-shell relays parses.
  5. options.chart's legacy axes refused at options.chart with the spec's unrecognized_keys — RIGHT, the consequence of the bag by reference (the spec's chart block is dataset-bound). Block (e) of object-chart-legacy-axis-keys-retired-10608 rightly flips from pinning the acceptance to pinning the refusal, with a dataset-bound control, and list-view-dataset-chart-filter-refusal-10327's object-bound control becomes a dataset-less bag; what it guards (the filter refusal reads dataset) is unchanged.

Published TypeScript face (@object-ui/types):

  1. ListViewSchema['options'] becomes the eight kinds with no index signature — RIGHT, and no objectql.ts edit was needed for it: ListViewSchema is ListViewAuthored & ListViewRuntimeProps, and ListViewAuthored maps ListViewInferred, which is z.input of the mirror, so the face follows the bag. Section 6 of the pin asserts the closed key set and seven @ts-expect-error rows, live because tsconfig.test.json is in the package's type-check (Type Check green).
  2. ListViewTimelineConfig is NonNullable of ListViewSchema['timeline'] — RIGHT under Q2 → B: one declaration, the spec slot by reference, strict, .partial(), dateField refused by name. Keeping the export NAME as the derived type, rather than minting a new one, is the dev's declared way of doing B; it is equivalent to the answer and pinned by Equal in the types pin and in plugin-timeline's timeline-date-binding.
  3. ListViewGalleryConfig retired from objectql.ts and from the index.ts barrel — RIGHT under Q3 → A. At this head the name survives only in the changeset, in one doc comment and in the pin's @ts-expect-error import; no package imports it. The remedy, the spec's GalleryConfig, is already re-exported.
  4. NamedListView.options (Record of string to any) untouched — RIGHT: outside the surface, held by objectui#7928 Q2 (i), and the dev's note says so.

@object-ui/app-shell (the producers, Q1 → A):

  1. kanbanViewOptions writes columns where it wrote cardFields — RIGHT; the renderer reads columns for the cards when no cardFields is present, and the census measured the node and the $select identical. ObjectView.kanbanGroupByRetired-8213's residual set goes to [] with a control that it is empty and a new arm that cardFields is never written; kanbanLane-8193 and kanbanSummarizeField-11629 follow the spelling.
  2. galleryViewOptions stops writing imageField beside coverField — RIGHT, conditioned as the answer conditioned it on the gate rung in judgment 14. The legacy READ (gallery?.imageField as the coverField fallback) stays, and galleryBinding-7547 now pins both: a declared coverField goes out alone, and a stored legacy imageField still answers coverField and rides the block spread as stored.
  3. timelineViewOptions stops writing descriptionField — RIGHT; the spec's timeline block has no such member, the bag refuses it, and the census measured that nothing drew the nested value. The one read that goes with it (viewDef?.timeline?.descriptionField) fed only that write, so this is the ordered producer change and not a reader retirement; the readers the fence protects (the alias fold, the defaultView lift, the two spreads, the collectors, ObjectTimeline's dateField rung) are untouched, and the relay's forwarding of the stored, door-judged bag is untouched.
  4. The new ObjectView.optionsBagParses-6152.test.ts parses each exported producer's block at the bag's own door for its kind, with a control that the door still refuses the three retired writes by name — RIGHT, and it is what keeps the relay's output inside the contract from here on.

@object-ui/plugin-list:

  1. availableViews gains the options.gallery.coverField rung — RIGHT, the one rung the seat allowed (the objectui#8193 kanban precedent). Without it the coverField-only bag the relay now writes rendered a gallery the switcher never offered; the new pin ListView.galleryOptionsBagCanonical-6152 answers YES for the canonical bag and for the bag app-shell writes, keeps the legacy and top-level rungs as controls, and answers NO for no block, a bag with no cover binding and an empty block. No alias read was removed.
  2. The grid branch's ...(schema.options as { grid?: Record ... } | undefined)?.grid cast — RIGHT, and the one line beyond the seat's fence, declared by the dev and stated in the PR's Acceptance notes: the typed bag no longer declares grid, so the legacy READ no longer compiled; the cast types it as the stored input it is and changes nothing at runtime. It is a reader kept, not a reader touched.
  3. README.md: both examples author the top-level per-kind blocks (kanban { groupByField, columns, titleField }, calendar, a dataset-bound chart), grid: {} is gone, and a paragraph says a stored view may carry the legacy bag, which the write door judges with the same block schemas, and to author the top-level blocks — RIGHT; the typed Schema block compiles under Doc Snippet Type Check, and README Export Check is green.

@object-ui/plugin-timeline:

  1. ObjectTimelineProps.schema.timeline keeps its type reference, which is now the narrowed ListViewTimelineConfig; doc comments say what it is and that the dateField READ stays until the readers' round — RIGHT under Q2 → B. The two alias fixtures (timeline-date-binding, ObjectTimeline.absentDateAxisRefusal-7459) keep their runtime value and each gains a real @ts-expect-error on a typed block carrying dateField; plugin-timeline's test leg reads @object-ui/types through the rebuilt dist, so a stale .d.ts would be red there (Type Check green).

Test instruments and pins:

  1. imported-defaults-8317.test.ts: IMPORTED gains the bag as a row, and the census gains one enumerated category, SCHEMA_RECORDS, which accepts a read of a non-Zod record of schemas only when the WHOLE member chain rooted at the binding is the single argument of stripImportedDefaults — RIGHT. VIEW_METADATA_MEMBERS has no Zod internals and cannot be the direct argument, so the pin's own rule had no spelling for it; the new spelling still puts everything that crosses through the boundary, and the new leg pins that every entry is a non-Zod record whose every value is a schema, matched by at least one read, every read wrapped via its member chain. A one-hop read of the record, or an unwrapped member read, stays an offender. This is a test-side exemption category, not a published face, and it is the narrowest one that admits the crossing.
  2. The eight re-spelled fixtures (calendar-date-alias-refusal-8355, kanban-stray-group-by-refusal-text-9653, list-view-dataset-chart-filter-refusal-10327, object-chart-legacy-axis-keys-retired-10608, plugin-list ListView.test.tsx and ListView.strayGroupByRefused-8365, app-shell galleryBinding-7547 and kanbanGroupByRetired-8213) — each RIGHT: every flip is the narrowing's own consequence, and no assertion was deleted into a vacuum (the 8355 scope control becomes an undeclared-key control; the 10608 control becomes a refusal with a dataset-bound control).

Docs and changesets:

  1. packages/types/src/zod/README.md gains the section on the bag by reference — RIGHT; Build Docs and Internal Docs Link Check are green.
  2. Round 11's pending .changeset/6152-list-view-blocks-strict.md, which said the legacy bag was unchanged, carries a dated append-only note (+5 / −0, frontmatter and every prior line intact) — RIGHT under 「改成追加说明 (Recommended)」; Changeset Overwrite Report and Changeset Claim Re-read are green.

File surface against the claim 6070030626 as amended in 6070475966: every one of the 26 files is on the amended surface, "the tests beside these", or a dev-declared, seat-conditioned addition (the one ListView.tsx cast, judgment 15). The fenced paths are untouched: packages/plugin-view/src/ObjectView.tsx and ObjectGrid.tsx (PR objectui#12010), the ObjectGridSchema.defaultFilters docblock in objectql.ts (the hunks sit at the two config types only), and every renderer-side reader. No governed surface is in the list, and Governed Surface Queue Guard is green.

② Semver level

  • What the diff publishes: a NARROWING of @object-ui/types on every zod door (the bag by reference) and on the TypeScript face (ListViewSchema['options'], ListViewTimelineConfig), one export removed (ListViewGalleryConfig), and an issue-code change on three existing refusals; a producer change in @object-ui/app-shell whose rendered output is measured unchanged; one capability-gate rung and a README rewrite in @object-ui/plugin-list; and a prop type in @object-ui/plugin-timeline that narrows through its dependency, with doc comments. No accept set and no public surface widens.
  • .changeset/6152-list-view-options-bag.md: @object-ui/types minor, headed "Narrowed (breaking)", with the FROM → TO mapping for every refused spelling and the one-line fix for each (columns, coverField, startDateField, the object-calendar element's flat defaultView, chart: { dataset, dimensions, values }, remove options.grid), the custom to invalid_type move stated, ListViewTimelineConfig under "Changed" and ListViewGalleryConfig under "Retired" with its remedy, and app-shell, plugin-list and plugin-timeline patch with what each changes — MATCHES what the diff publishes. objectui prices its own breaking changes as minor with the break stated in the body (the repo rule, enforced by Changeset Bump Policy, which is green), and the fixed release group carries all four packages at one version, so the three patch rows are the right declaration for packages whose own source changes no API. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report and Changeset Claim Re-read are all success.
  • The Clause-②: line reads no on the claim, on the PR body (at a line start) and in the changeset — RIGHT, under the reading in force since round 7 on this card (yes when the diff widens an accept set or the public surface; the round-10 claim read yes for exactly that reason, and the round-7, 8, 9 and 11 records PASSed no for pure narrowings). The one point worth saying out loud: the gate rung in judgment 14 is renderer behaviour (the switcher offers a view the renderer already rendered for that bag), not a validator accept set and not an exported surface, so it does not move the line.
  • Cosmetic, not verdict-changing: the PR title and the changeset's first line read feat(types)!: while four packages are bumped; the body names each package's change, so the CHANGELOG entry is complete.

③ Boundary flags

Dev-declared deviations (report 6071238136), each answered:

  • One line in ListView.tsx beyond the seat's "one gate rung" (the options.grid read-site cast) — accepted, and required: without it plugin-list does not compile against the narrowed bag; runtime is unchanged; the reader is kept, not retired (judgment 15).
  • A gate pin edited (imported-defaults-8317, the SCHEMA_RECORDS category and the new IMPORTED row) — judged RIGHT at judgment 18; the pin's own message prescribes adding the row, and the category is pinned to the narrowest shape that admits the crossing.
  • The branch fast-forwarded from 902fbe19 to origin/main 7ebff3991 before the first commit — not a contract matter; the merge base is that commit and the diff is read against it.
  • Two typed-fixture files outside the seat's list re-spelled (ListView.test.tsx, ListView.strayGroupByRefused-8365) and round 11's pending changeset annotated — accepted: "the tests beside these", and the append-only shape (judgments 19 and 21).
  • Q2 implemented by redefining the exported ListViewTimelineConfig rather than a new type, so the prop keeps its one consumer — accepted as equivalent to the answer, pinned by Equal on both sides (judgment 7).
  • The two plugin-timeline alias fixtures keep their runtime value and gain a real @ts-expect-error — accepted; they pin the untouched dateField read for stored blocks, which is the fence's intent (judgment 17).
  • The scratchpad hazard (vanishing pid files and logs during long waits) — not a contract matter; every figure the dev quotes is a declared local reading, and the verdicts here are the check-runs.

The census report's deviations (6070437445): nothing built there reached this head (the measured-then-reverted bag edit left objectql.zod.ts at the base before the round's first commit, and the diff is read whole against the merge base); readers cited by symbol, not by line — the right reading under objectui AGENTS.md #11, as round 11's answer already accepted.

open_questions: none in the implementation report. The census's three questions were answered by the seat in 6070475966 (Q1 → A, Q2 → B, Q3 → A), each within rulings already in force (objectui#8327, objectui#10380 ruling A, the startup rule on zero-consumer exports), and judgments 1, 7, 8, 10 to 14 and 17 find the diff implementing each as answered. The seat's note that Q1's option D (taking options off the authoring face) would be the maintainer's to reopen is informational; nothing on this head depends on it.

The seat's conditions on the round: the bundle delta was to be measured against main and a non-fitting delta was a stop — the dev reports −99 B under the ceiling, the constant untouched, and Bundle Analysis is success on this head; the renderer-side retirement and PR objectui#12010's files were fenced — untouched.

Noted, not flagged:

  • The legacy reads this round leaves in place (ListView's options.grid spread, the kanban groupField / cardFields rungs, the gallery imageField rungs, the dateField rungs in resolveTimelineDateBinding and ObjectTimeline, the chart legacy axes, the tree titleField fallback, and app-shell's producer reads of the view's own retired spellings) now read keys every door refuses; they answer only for rows stored before the view write door judged the bag. Carrier: the renderer-side retirement round on this card, as the dev's finding says.
  • NamedListView.options stays a Record by objectui#7928 Q2 (i); a listViews entry is the spec's strict record and refuses the bag. Observation only.
  • The census in the ListViewOptionsBag docblock is a one-time measurement (objectui AGENTS.md 完善设计器的每一个细节 #9) and says so; the live pins are the four-door tests.

Escalations: none. No maintainer decision is needed for this head to land.

Implemented-by: claude/issue-6152-r12-options-bag
Reviewed-by: session_01DBZ9bntPZ7VKyQNtJeNsgw

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 00:09
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 00:09
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 5d77c09 Oct 9, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-6152-r12-options-bag branch October 9, 2026 00:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants