Skip to content

fix(app-shell): relay a list view's own fieldOrder to ListView (objectui#7516) - #10622

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-7516-fieldorder-relay-rung
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-7516-fieldorder-relay-rung

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #7516
Clause-②: yes — the relay begins accepting a declared spelling it drops today (the ruling's own reading).

Executes ruling 5635656263, ratified 5809517921: add the per-view fieldOrder rung to renderListView's relay in packages/app-shell/src/views/ObjectView.tsx, pin it at the relay, flip the census ledger entry from known-gap to the rung's evidence, one patch changeset.

What changed

  • The rung. fieldOrder: viewDef.fieldOrder ?? listSchema.fieldOrder, placed beside the hiddenFields rung and resolved by the same precedence: the active view's value wins; a view that authors none keeps the list node's own (...listSchema). The value lands in the same schema.fieldOrder slot the list-node spelling fills, so ListView's effectiveFields memo composes the two spellings identically by construction.
  • The pin (ObjectView.fieldOrderRelay-7516.test.tsx, beside the rowColor / description relay pins; ListView stubbed, its schema prop captured): three FIX cases (a per-view fieldOrder arrives verbatim; it overrides a list-node fieldOrder; it resolves by the same precedence as hiddenFields in one render) and two CONTROLs (the list node's own fieldOrder is unchanged when the view authors none; none anywhere stays absent).
  • The census (ObjectView.relayRungCensus-7559.test.ts): the fieldOrder known-gap row is removed, and the rung is recorded the way the census records its other live rungs — a named check, objectui#7516 — the per-view fieldOrder rung is present, beside the description (A list view's description is served but never rendered — ObjectView's relay drops it on the way to ListView #7199) and rowColor (Two more declared list-view keys never reach ListView: fieldOrder and rowColor are read by the renderer but relayed by neither host #7218) ones. The two stale header sentences ("still open", "carries needs-user-decision") are rewritten. The census CONTROL case that used fieldOrder as its example of a declared absence now uses operations (a reader-having node-authored absence), since fieldOrder is no longer one.
  • The relay's header comment now says all three instances of the class carry their rung.
  • Changeset .changeset/7516-fieldorder-relay-rung.md, '@object-ui/app-shell': patch.

Premises, measured on origin/main a7df45f before the first edit

  • H1 (no rung) holds. The fullSchema literal wrote hiddenFields from viewDef and nothing for fieldOrder. Reproduced by the new pin on the base relay: Tests 3 failed | 2 passed (5); the per-view case read expected undefined to deeply equal [ 'stage', 'name' ], the override cases read the list node's [ 'owner', 'stage', 'name' ]. Why the spread carries nothing on this route: plugin-view's composition fills fieldOrder from a NAMED list view only (currentNamedViewConfig?.fieldOrder), and this page hands it views, not listViews.
  • H2 (composition) — no disagreement, so no stop. Spec origin/main (objectstack a08e059) ListViewSchema describe text: hiddenFields removes names from the columns projection "before fieldOrder orders the remainder"; fieldOrder "sorts what survives columns minus hiddenFields and never adds a field. A surviving column absent from fieldOrder sorts LAST, after every listed one, keeping its columns-relative order; a name listed here that did not survive orders nothing." ListView's effectiveFields memo: starts from schema.columns, drops FLS-denied columns (the extra step the spec's own docblock names), drops hiddenFields, then sorts by an index map built from schema.fieldOrder with unlisted entries at Infinity (two unlisted compare as NaN, which the sort treats as equal, so the stable sort keeps their columns order). Same three steps, same order, same unlisted-last rule. Which spelling wins when both carry fieldOrder: the view's, matching the hiddenFields rung.
  • H3 (census) — done as described above; ablation below.
  • H4 (pin) — red on base, green on head, figures above and below.
  • H5 (the relay's header comment) — measured: it listed objectui#7516 among the three shipped instances but did not literally call it open. Edited to say all three now carry their rung, which is true after this change.

Verification — all runs on HEAD 09d496810

  • Pin + census, head: Test Files 2 passed (2) · Tests 30 passed (30).
  • Ablation (rung deleted, one-shot, no file left behind): node scripts/ablation-replace.mjs (objectstack) with anchor fieldOrder: viewDef.fieldOrder ?? listSchema.fieldOrder, → anchor x1 → x0, blob 51a823a8ab22 → d16c4a7d6716, a grep -c inside the run read 0. Predicted before the run: census 2 red, pin 3 red, controls green, 5 failed / 25 passed. Measured: Tests 5 failed | 25 passed (30) — census leaves NO member both unrelayed and undeclared (received [ "fieldOrder" ]) and objectui#7516 — the per-view fieldOrder rung is present; pin: the three FIX cases. Restore: blob after restore == blob at HEAD (51a823a8ab22), git diff HEAD empty. No build is involved: both files read ./ObjectView from source (the census from disk text, the pin through a relative import).
  • Dependency closure: turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 → Tasks: 28 successful, 28 total.
  • @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json): VERDICT command-exit 0; --listFiles on the test project lists both ObjectView.fieldOrderRelay-7516.test.tsx and ObjectView.relayRungCensus-7559.test.ts.
  • @object-ui/app-shell tests, the whole package (799 test files), run from the repo root in chunks under the shared verify lock: every chunk command-exit 0, zero failures; two pre-existing environment-conditional skips (describe.runIf(DRIVEN) in the audit-history date-zone test, skipIf in the flow-node spec-reconciliation test). The chunks also covered 46 tests in other packages and apps/console that read app-shell sources from disk.
  • Gates (lock-free, verdict lines): check-changeset-presence ✅ 3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s) · check:new-line-citations VERDICT new-cross-file-line-citations: 0 new citation(s) · check:control-bytes ✅ OK · check-changeset-no-major ✅ · check-changeset-overwrite ✅ · check-changeset-fixed ✅ · check-pending-changeset-literals ✅ · check-vi-mock-specifiers / -inherit / -override-shape ✅ OK · check-test-path-roots ✅ OK · check-shell-escape-residue ✅ · check-action-ref-convention ✅ · check-type-check-coverage ✅ · check-lint-coverage ✅ · check:unreferenced-sources / handler-key-reads / metadata-write-doors / doc-example-readers / self-import / phantom-deps OK · check:comment-mask-corpus within the residue objectui#7882 holds (the one disagreeing file is apps/console/src/pages/DocsIndex.tsx, untouched here) · check-governed-queue-guard --test on the four paths: NOT GOVERNED. check-changeset-claims (report-only) names .changeset/7070-no-invented-gantt-date-fields.md for mentioning ObjectView.tsx; that paragraph is about the gantt branch, which this change does not touch — re-read, still true.
  • Lint, narrowed and proven: (1) population from ESLint's own config — isPathIgnored is false for all three changed .ts/.tsx files; (2) eslint --format json over them: 3 files, 0 errors (warnings: ObjectView.tsx 171 on head and 171 on base; the new pin 6 no-explicit-any warnings, the same shape as its rowColor sibling; the census 0); (3) invariance — calculateConfigForFile reports no parserOptions.project / projectService (the config extends the non-type-checked tseslint.configs.recommended), and no custom rule under eslint-rules/ reads the filesystem, so this diff cannot move a verdict on any untouched file. The repo-wide pnpm lint is CI's.
  • Not run locally, CI's: performance-budget (needs a full console build; the change is one object-literal property), readme-exports / spec-range-floors / published-dist (not reached: no manifest, export or docs change), E2E.

Acceptance notes

  • known-gap now has no entry in the ledger. Its kind and its evidence check stay in the census as the machinery for the next gap; the check iterates nothing today.
  • plugin-view's own composition reads fieldOrder from the named list view only, while it reads hiddenFields from activeView (the views entry). Not a defect this card carries: the only in-tree hosts passing renderListView are this page, whose rung now covers the views entry, and the Studio design surface, which has no saved views. Noted, not filed.

Generated by Claude Code

renderListView's relay carried the active view's hiddenFields onto the
schema it hands ListView but had no rung for fieldOrder, the other per-view
half of the columns x hiddenFields x fieldOrder composition, so a per-view
order was accepted, served and then dropped. Add the rung beside
hiddenFields with the same precedence (view over list node), pin it at the
relay, and replace the census ledger's known-gap row with a named check for
the rung.

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

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/7070-no-invented-gantt-date-fields.md

  • names app-shell/src/views/ObjectView.tsx → packages/app-shell/src/views/ObjectView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with c2d86599b (merge-base with origin/main): 3 file(s) changed outside .changeset/, read against 1478 pending declaration(s) that publish a body (2062 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3048.1 KB 3104.5 KB
Main entry chunk (gzip) 147.9 KB 350 KB
Entry file index-CeLRV5fR.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) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 546.37KB 130.65KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.05KB 65.69KB
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.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.40KB 14.61KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.50KB 35.37KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 147.92KB 37.83KB
plugin-gantt (index.js) 169.62KB 41.91KB
plugin-grid (index.js) 215.37KB 58.92KB
plugin-kanban (index.js) 48.26KB 15.04KB
plugin-list (index.js) 114.60KB 28.31KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.84KB 21.96KB
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.66KB 3.50KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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 (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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
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: 09d4968100df176d025ce6331b4468bd74b03020

① Derived judgments

Composition vs spec — agree, no stop. Read side by side: objectstack origin/main 949e99b packages/spec/src/ui/view.zod.ts (docblock :2655-2687 "#15184 ruling B", describe strings :2689-2701) and packages/plugin-list/src/ListView.tsx effectiveFields :2921-2954 on the PR head (file untouched by the PR). Step for step: columns projects (Array.isArray(schema.columns) ? … : []); the FLS drop sits between steps 1 and 2 exactly where the docblock names it (perms.checkField(…, 'read')); hiddenFields subtracts and can only remove; fieldOrder builds an index map, unlisted survivors score Infinity so every listed one sorts first, two unlisted compare Infinity - Infinity = NaN which ECMAScript's sort treats as +0, and the stable sort keeps their columns order — "sorts LAST, keeping its columns-relative order" holds; a listed name with no surviving column orders nothing; nothing is ever added. Empty columns: hasAuthoredColumns is false (:2956-3130) so the grid derives defaults and fieldOrder is never applied to that path — "steps 2 and 3 do not apply" holds. Empty fieldOrder []: spec reading (every survivor unlisted, all last in columns order) and ListView's length greater-than-0 guard produce the same columns order. Duplicates: the spec text is silent; ListView's Map takes a duplicated name's LAST index — undeclared, not a disagreement. objectstack's view-field-order-composition.pin.test.ts exists on origin/main and asserts nothing about empty fieldOrder or duplicates.

The rung. packages/app-shell/src/views/ObjectView.tsx:2704 fieldOrder: viewDef.fieldOrder ?? listSchema.fieldOrder vs :2687 hiddenFields: (viewDef as any).hiddenFields ?? listSchema.hiddenFields: same operator, same operands, same precedence. ?? is right: null/undefined on the view falls to the list node, as hiddenFields does. An explicit [] on the view wins via ?? and ListView then sorts nothing (columns order) — the same shape as hiddenFields: [] (no subtraction) and the same result the spec gives an empty fieldOrder; consistent. The uncast read type-checks: ViewBody = Record OF string, any (utils/viewIdentity.ts:18). viewDef = activeView (:2472) where activeView = viewDraft merged over baseView (:1943-1947), baseView found by activeViewId = resolvedViewId || defaultViewId || views[0]?.id — named listViews entry, default view, runtime switch (handleViewChange sets resolvedViewId) and an open draft all reach it, and renderListView's deps include activeView (:3044); identical to what the hiddenFields rung reads. The authored value survives every path to viewDef: viewEntry is Object.assign over the bodies with id stamped last (viewIdentity.ts:127-137); adapter rows spread ...sv at :1777 and :913. Other app-shell composers of the schema ListView receives (git grep, 4 non-test sites): ObjectView.tsx:2979 (this relay), StudioDesignSurface.tsx:2606 (...listSchema spread; its plugin ObjectView at :3446-3470 receives neither listViews nor views), ObjectDataPage.tsx:609 (no saved view at all), InterfaceListPage.tsx:571 — this one resolves listViews[cfg.sourceView] (resolveSourceView, :288) and hand-projects keys, carrying NEITHER hiddenFields NOR fieldOrder (0 mentions of either in the file): a symmetric interface-page door, not the renderListView asymmetry this card measured. Out of scope; noted below.

Census. The known-gap row (base :480) is gone; the header sentences at base :22 and :87 are rewritten; the new check objectui#7516 — the per-view fieldOrder rung is present (:597-610) has the #7199 (:575) / #7218 (:588) shape — readsView(writtenAt(key)) with a restore message — plus listViewReads('fieldOrder'). Members are still re-derived: MIRROR_MEMBERS = Object.keys(ListViewMirror.shape) (:148) from @object-ui/types/zod (packages/types/src/zod/objectql.zod.ts:1391, spec fields by reference including fieldOrder) union ListViewRuntimeProps parsed from objectql.ts. The known-gap kind (:393) and its evidence check (:738) remain, iterating nothing. CONTROL swap to operations: it is node-authored (:479), a mirror member (objectql.zod.ts:1522), not in RELAYED, and ListView.tsx:1590 reads schema.operations?.export — a real declared, reader-having absence. Ablation re-run in a scratch git worktree at the head (offline pnpm install --offline --frozen-lockfile --ignore-scripts from the store, 6.1s): head pnpm exec vitest run PIN CENSUS = Test Files 2 passed (2) · Tests 30 passed (30); rung line deleted with sed (grep -c 1 to 0) = Tests 5 failed | 25 passed (30) — census leaves NO member both unrelayed and undeclared received [ 'fieldOrder' ], the #7516 named check red (expected false to be true), the three FIX pin cases red; both pin CONTROLs and the census CONTROL green; git checkout -- restored, git diff HEAD empty. Matches the dev's 5 failed | 25 passed exactly. Worktree removed; /home/user/objectui untouched (status 0 entries).

Pins (3 FIX + 2 CONTROL). Against the base relay (git show a7df45ff1:…ObjectView.tsx, rung grep -c 0): Tests 3 failed | 2 passed (5) — expected undefined to deeply equal [ 'stage', 'name' ] (verbatim case) and expected [ 'owner', 'stage', 'name' ] to deeply equal [ 'stage', 'name' ] (both override cases): red for the defect, not for a harness reason. Green on head (30/30 above). CONTROL 1 (list node's fieldOrder kept when the view authors none) is real — a stomping fix fieldOrder: viewDef.fieldOrder fails it — but its listSchema.fieldOrder limb is reachable only through the mocked host: in-tree that slot is always undefined on this route (plugin-view fills it from currentNamedViewConfig only and app-shell hands no listViews); the PR body says so. CONTROL 2 (undefined ?? undefined) real. Waiting on captured.options as the relay-ran signal is sound.

The plugin-view finding — true as scoped. packages/plugin-view/src/ObjectView.tsx:2360 hiddenFields: activeView?.hiddenFields ?? (schema as any).hiddenFields vs :2376 fieldOrder: currentNamedViewConfig?.fieldOrder (comment :2361-2375, objectui#8980 "view-sourced only"). In-tree renderListView hosts (git grep packages/ apps/ non-test): app-shell ObjectView.tsx:3398,:3441 and StudioDesignSurface.tsx:3469, nothing else. App-shell passes views={mergedViews}, activeViewId, and a schema={objectViewSchema} (:3057-3089) with no listViews, so plugin-view's currentNamedViewConfig is null, fieldOrder arrives undefined and the new rung fills it from viewDef; for hiddenFields, plugin-view's activeView (:929, viewsProp.find by activeViewId) is the same entry app-shell's viewDef is, and app-shell overrides it anyway. Studio hands neither listViews nor views. No reachable in-tree host reads the same authored view differently after this PR. The asymmetry stays latent for an out-of-tree host passing plugin-view views carrying fieldOrder (it would relay that view's hiddenFields and not its fieldOrder); the dev's "noted, not filed" leaves it unfiled. Not this card.

② Semver level

'@object-ui/app-shell': patch (package @object-ui/app-shell 17.6.0, in the changeset fixed group) and Clause-②: yes are both the ruling's own words (5635656263: "the rung, a pin at the relay, the ledger entry, a patch changeset"; "Clause-②: yes — the relay begins accepting a declared spelling it drops today"). Changeset prose, sentence by sentence: "accepted and served, then silently dropped" — base relay grep -c 0, spec door admits it (objectstack pin :232-237), lint-validated per the ruling; "could only ever be ordered by a fieldOrder already on the host's list schema" — true (...listSchema was the only source); "relayed now, beside hiddenFields and with the same precedence" — :2687/:2704; "ListView composes it exactly as it always has" — ListView.tsx is not in the diff (4 files: relay, census, pin, changeset), memo unchanged; the four-step summary matches the describe text; "the composition objectstack#15184 wrote into the protocol" — view.zod.ts docblock :2655 and describe :2689-2701 carry it, and the spec pin holds it. PR body: every checked sentence holds — H1 figures reproduced verbatim; H2's NaN/stable-sort reading is correct; "this page hands it views, not listViews" (:3388-3401); "viewEntry … Object.assign … stamps only id afterwards"; "beside the rowColor / description relay pins" (ObjectView.rowColorRelay-7218.test.tsx, ObjectView.viewDescriptionRelay-7199.test.tsx exist); the operations CONTROL description; "known-gap now has no entry … machinery stays" (:393, :738); "Premises, measured on origin/main a7df45f" equals the merge-base. Whole-package test, lint and build-closure figures were not re-run here; CI's Lint, Type Check, 8 test shards and Build & E2E are green and cover them.

③ Boundary flags

  • CI on the head: final poll 43 check-runs — 40 success, 3 skipped (Test (coverage), Test (coverage shard …/4), dependabot: configured skips), 0 failed, 0 in progress; combined status success. No red check.
  • merge-tree: git merge-tree --write-tree refs/review/main refs/review/pr-10622 exit 0, tree 187e501, no conflicts. main (b1030c7) is 16 commits past merge-base a7df45f; three touch neighbour files (fix(plugin-view): resolve context tokens in the filters ObjectView hands on (objectui#10506) #10577 plugin-view ObjectView.tsx, fix(plugin-list): a held user filter does not narrow the fetch on a dataset-bound chart (objectui#10512) #10597 ListView.tsx, fix(cli,types): say what objectui check does; name objectui validate for the verdict (objectui#10524) #10595 types) and none of their hunks touch fieldOrder, hiddenFields, effectiveFields or the upstream reads the census parses from disk. API mergeable: true, mergeable_state: behind.
  • PR assignee: assignees: [] — unset (the dev's label-write --assign was refused by the classifier); the seat still owes it.
  • PR is draft: true — must be marked ready before a squash merge; body and title are squash-ready as written.
  • Cross-repo staleness (not a stop, not this repo): objectstack packages/spec/liveness/view.json /props/list/children/fieldOrder note says the app-shell relay "still records fieldOrder as a declared absence (objectui#7516) in its relay-rung census" — false once this lands; a re-cite is owed on the objectstack side.
  • Out-of-scope doors (not a stop): InterfaceListPage.tsx carries neither hiddenFields nor fieldOrder from listViews[cfg.sourceView] (symmetric, its own class); plugin-view's activeView-vs-currentNamedViewConfig split for hiddenFields/fieldOrder stays latent for out-of-tree hosts and is unfiled.
  • Nothing above should stop landing.

Implemented-by: claude/issue-7516-fieldorder-relay-rung
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 12:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 41ecaed Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-7516-fieldorder-relay-rung branch September 25, 2026 12:55
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.

ListViewSchema.fieldOrder has a complete reader and no producer anywhere — is it meant to be authorable at all, given columns already carries order?

1 participant