Skip to content

fix(plugin-view): route 2 reads a named view's fieldOrder, navigation and inlineEdit (objectui#10885, member 4) - #10967

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-10885-route2-named-view-rest
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-10885-route2-named-view-rest

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10885
Clause-②: no

Member 4 of objectui#10885, under PM claim 5868584175 and the seat ruling in 5867513786 (option A widened by data, contract review 5866387245 item 10). Branch claude/issue-10885-route2-named-view-rest, head bd41f5cf18, base 06a96e9480. The gates below ran at 600cf89017; fix round 1 changes prose and one test docblock only.

Three of the five members land here: navigation, inlineEdit and fieldOrder. label and data are ruled B, not relayed on route 2 (seat ruling 5869413175). data's provider conflict is objectui#10971. Member 1 (PR objectui#10937) and member 3 (PR objectui#10955) have merged, and member 2 was enumerated, not graded (see Acceptance notes). So this PR closes the card.

Fix round 1 (bd41f5cf18, prose only) applies contract review 5869604558's five wording fixes:

  • label / data are stated with their reasons in both changesets, the README and the docs page;
  • "named view", not "saved view", in Opening a record;
  • the reach of a named navigation over the create and edit forms, including the New button under split / popover (objectui#10975);
  • the host onRowClick qualifier;
  • the member-1 pin docblock.

The review's two follow-up carriers are objectui#10975 and objectui#10976.

What changed

All code is in packages/plugin-view/src/ObjectView.tsx, outside both fences.

member the read precedence pins
navigation navigationConfig = currentNamedViewConfig?.navigation ?? schema.navigation named view first, whole object, then the node. No per-key merge and no activeView rung. Its consumers follow it: handleRowClick (the onRowClick route 2 hands ObjectGrid and the delegation hands renderListView), formLayout and the drawer width ObjectView.namedViewNavigation-10885.test.tsx: route 2 row click, whole-object, delegation row click, drawer width over formLayout, plus 3 controls
inlineEdit gridSchema.editable = currentNamedViewConfig?.inlineEdit named view only. Route 2's gridSchema literal carried no editable key at base (0 occurrences in that memo), so there is no node rung. ObjectGrid still ANDs it with the object's inline-edit verdict and the update grant (inlineEditable) ObjectView.namedViewInlineEdit-10885.test.tsx (real ObjectGrid): an editor opens where the object grants inline edit; no editor where userActions.edit is false; control
fieldOrder new module-private inFieldOrder, applied to both projection slots after withoutHiddenFields named view only, like the fence's fieldOrder rung. It copies ListView's effectiveFields step: a stable sort by position in fieldOrder, with unnamed or identity-less entries sorted last. A name the projection does not carry orders nothing, and with no projection nothing is added @object-ui/app-shell objectViewRouteParity.fieldOrder-10885.test.tsx: both routes of the same ObjectView on the same named view, route 2's projection compared with what the real ListView hands its grid. 4 cases plus 2 controls

Route 2's comment that said navigation "is not relayed" now says that it is still not relayed to ObjectGrid, and that the handler route 2 passes reads it.

Two files outside the claim's surface:

  • packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts: its READ_TEXT.navigation pins the exact text of the navigationConfig line, so it went red at head (1 of 237). The entry now spells the new line, which still reads schema.navigation as the fallback. No census figure moved (see below).
  • packages/app-shell/src/__tests__/objectViewRouteParity.fieldOrder-10885.test.tsx: the fieldOrder parity pin needs ListView. @object-ui/plugin-view does not depend on @object-ui/plugin-list, and @object-ui/app-shell is the one package that depends on both.

Prose:

  • packages/plugin-view/README.md and content/docs/plugins/plugin-view.mdx: the member-1 sentence "label, data and navigation, also declared on both, are not read off the named view on this path" is replaced. The new text says what route 2 reads now (fieldOrder, inlineEdit, navigation) and what it still does not (label, data). The "Opening a record" section says an active saved view's navigation replaces the node's. A grep for the old sentence across the repository hit only these two files and the changeset below.
  • .changeset/10885-object-view-route2-named-grid.md (pending): one dated note is appended in the objectui#10533 form. There are 2 insertions and 0 deletions, and the frontmatter md5 is 4d3ab4fd127d2f0feaabd7840d888656 before and after.
  • .changeset/10885-object-view-route2-named-view-rest.md (new): '@object-ui/plugin-view': patch.

Fences

Each fence was extracted from its #region line through its #endregion line.

fence base 06a96e9480 head 600cf89017
objectui#5097 HOST-COMPOSITION SURFACE (186 lines) 40bc22920811a42b0284f478746ff5a5f6b18d10 40bc22920811a42b0284f478746ff5a5f6b18d10
VIEW-TYPE BRANCHES a140d0421c41a4401a8b96f6f356b9e8daa9b168 a140d0421c41a4401a8b96f6f356b9e8daa9b168

Census

object-view-unmirrored-keys-7779 and schema-reference-named-list-view-keys-7923:

  • Base (ObjectView.tsx = base blob 34f0656469): 237 passed of 237.
  • Head 600cf89017: all passed, in a 5-file run of 315 tests with the other three packages/types readers.

The partition is 43 read and 21 unread of 64 on both sides. The pins asserting it are "the renderer reads exactly forty-three names off a named view" and "the census partitions the declaration exactly: 43 read + 21 unread = 64". All three keys are already in the read set. readSites.label stays at 2, because no label read was added.

Red on base, green on head, and per-member ablation

Each leg below started from HEAD. It proved its mutation was on disk with an occurrence count, ran the 3 new pin files (16 tests), then restored with git checkout HEAD and a blob-hash check (git diff HEAD empty).

leg mutation, proven on disk result red rows
base ObjectView.tsx replaced by the base blob (hash equal, inFieldOrder 0 hits) 7 passed, 9 failed every non-control row of all three members
head none 16 passed none
navigation navigationConfig back to schema.navigation (named read 0, node-only 1) 12 passed, 4 failed exactly the 4 navigation fix rows
inlineEdit the editable line removed (0 hits) 15 passed, 1 failed exactly the inlineEdit fix row
fieldOrder fieldOrder forced undefined (read 0, ablated 1) 12 passed, 4 failed exactly the 4 parity rows
grant (can-fail proof) ObjectGrid's inlineEditable without objectInlineEditable 2 passed, 1 failed exactly "THE GRANT STILL WINS"

The 7 base-green rows are the controls. Each base failure is an assertion failure, for example expected [ 'a', 'b', 'c', 'd' ] to deeply equal [ 'c', 'a', 'b', 'd' ], or onNavigate not called with r1, view.

label and data: measured, then ruled B

label. The order says: stop if relaying paints a second visible title. Measured by relaying it in a scratch copy with the real ObjectGrid:

  • With two named views, the tab strip shows LABEL_ALL and the table gains a caption element reading LABEL_ALL.
  • With one named view (no tab strip), the caption is the only label on screen.
  • The delegation paints no caption either way, because ListView hands its grid no label.
  • Relaying would also move the 7779 readSites.label pin from 2 to 3.

label is already read on route 2, as the tab strip's text.

data. The order says: stop if ListView and ObjectGrid disagree on a provider. Measured per provider on the same named view, with node objectName = probe_task:

provider delegation (ListView) route 2 if relayed (ObjectGrid)
value rows from items rows from items
object (other_obj) fetches probe_task, the node (objectui#7477 ruling B: an objectName already present wins) fetches other_obj (record-source ladder: data first)
api fetches probe_task fetches probe_task
schema fetches probe_task fetches probe_task

At head, route 2 fetches probe_task for all four.

Gates (head 600cf89017 unless noted)

  • @object-ui/plugin-view type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0. Both new pins are in the test program (--listFilesOnly).
  • vitest run packages/plugin-view/: Test Files 56 passed (56), Tests 549 passed (549).
  • @object-ui/types type-check: exit 0. The 5 packages/types ObjectView readers: 5 files, 315 tests passed.
  • @object-ui/app-shell type-check: exit 0, with the new parity pin in the program.
  • ObjectView readers in other packages: 34 files, Tests 1218 passed (1218). The set was enumerated by git grep for files that read the source or import @object-ui/plugin-view: 21 app-shell files, react 9357, plugin-map ×2, plugin-list 4024, 4 script tests, schema-catalog gallery, 3 console tests and cli app-generator.
    • Declared narrowing: the other app-shell files were not run locally, and CI runs the whole package. No published surface moved: the helper is module-private and the exports are unchanged.
  • The changeset and citation gates all exit 0:
    • check-changeset-presence: "1 changeset(s) added".
    • check-changeset-overwrite: report-only, case 2, plugin-view: patch before and after.
    • check:changeset-claims: 6 pending entries name touched files, and each was read and still holds.
    • check:pending-changeset-literals.
    • check-changeset-no-major.
    • check:new-line-citations: VERDICT … 0 new citation(s).
    • check:control-bytes.
  • The doc gates all exit 0:
    • check-doc-links: "Links are valid across 17 scan roots".
    • check:spec-symbols.
    • check-doc-component-types, check-prompt-component-keys and check-doc-example-ids.
    • check:doc-fences, check-doc-snippet-types, check-doc-example-types and check:doc-example-readers.
    • check:readme-exports (after building cli and plugin-ai): "549 real, 0 wrong-path, 0 fabricated".
    • check:docs-route-closure.
  • check:phantom-deps, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape and check:test-path-roots: all exit 0.
  • Console eager-closure budget, check:eager-closure after vite build of apps/console:
    • Base: 3102.9 KB (headroom 1.6 KB), eagerGzipBytes 3177396, plugin-view chunk gzip 18798.
    • Head: 3103.0 KB (headroom 1.5 KB), eagerGzipBytes 3177460, plugin-view chunk gzip 18866.
    • The difference is +64 bytes gzip across the closure. The plugin-view chunk is eager.
  • git merge-tree --write-tree is clean against fresh main 95a7c8d38f (tree bffa22dd6a) and against PR objectui#10955's head acf69b7835 (tree 22d3cf4ecc).
    • PR objectui#10955 has merged, as 462c85868d.
    • None of the 5 commits since base touches this PR's files.
  • ESLint on the 5 changed TypeScript files: 0 errors. This PR adds no any to ObjectView.tsx. The repo-wide pnpm lint is left to CI.
  • Governed surface: check-governed-queue-guard --test over the 9 paths reports NOT GOVERNED.

Acceptance notes

  • Member 2 (the delegation's eight view-kind blocks) was enumerated, not graded, and is not taken here, per triage execution note 2. Its reach, re-measured: git grep -n "renderListView=" over non-test sources finds 3 host sites, two in the console object page and one in the Studio design surface. None of the schemas they pass carries listViews: the console page's objectViewSchema literal has 0 listViews hits, and StudioDesignSurface.tsx has 0. No in-tree host passes both.
  • The navigation fix reaches the delegation by construction, through the shared handler. Since no in-tree host passes listViews there, its in-tree reach today is route 2: the Studio ViewPreview and authored nodes.
  • The member-1 divergence the contract review noted still stands, unchanged: with a non-empty hiddenFields, route 2 keeps a column entry that has no identity and ListView drops it. The fieldOrder parity pins use identified columns.
  • Relays that were measured and then reverted were scratch-only: the label and data relays, and a probe file that imported @object-ui/plugin-list from plugin-view. Nothing of them is in the branch.

Generated by Claude Code

… and inlineEdit (objectui#10885)

`navigationConfig` reads the active named view's `navigation` first, whole
object, then the node's, so the row-click handler both routes hand down, the
record surface and the drawer width follow it. Route 2 puts its projection in
the named view's `fieldOrder` the way `ListView` does, and hands `ObjectGrid`
the named view's `inlineEdit` as `editable`, which the grid still ANDs with
the object's inline-edit grant. The objectui#5097 fence is untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…d fieldOrder (objectui#10885)

The row click on route 2 and on the delegation, and the drawer width, follow a
named view's `navigation`; a named `inlineEdit` opens the real grid's in-cell
editor only where the object grants it; and route 2's `fieldOrder` projection
is compared with `ListView`'s on the same named view.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…lineEdit (objectui#10885)

The README and the plugin-view docs page stop saying `navigation` is not read
off a named view on the registered renderer, and say what the path reads now.
The pending member-1 changeset gets a dated note for the same sentence, its
frontmatter untouched, and this member gets its own patch changeset. The
objectui#7779 read-text pin follows the `navigationConfig` line, which still
reads the node's `navigation` as the fallback.

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

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 6 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/6726-find-envelope-records-arms.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

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

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/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.

.changeset/7499-gantt-non-axis-floors-omitted.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/8221-retire-legacy-string-sort.md

  • names content/docs/plugins/plugin-view.mdx → content/docs/plugins/plugin-view.mdx — edited by this change

    Docs teach the array only: content/docs/plugins/plugin-map.mdx, content/docs/plugins/plugin-view.mdx and packages/plugin-view/README.md.

  • names packages/plugin-view/README.md → packages/plugin-view/README.md — edited by this change

    Docs teach the array only: content/docs/plugins/plugin-map.mdx, content/docs/plugins/plugin-view.mdx and packages/plugin-view/README.md.

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/object-view-unmirrored-keys-7779.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    What was measured. Every reading was taken on the object-view node renderer (packages/plugin-view/src/ObjectView.tsx, registered by plugin-view/src/index.tsx) with schema.objectName / schema.layout as the positive controls of the same schema.KEY query, so each zero is a reading; the repo-wide census of viewTabBar finds the key in no source file outside @object-ui/types (two doc tables listed it as authorable and are corrected here). The spec side was read through the installed pin (@objectstack/spec@17.2.0, ui entry, 117 exported object schemas walked; control keys objectName / columns / navigation / listViews hit): the three spec-modelled keys are optional slots on ListViewSchema and ObjectListViewSchema; the six local keys have no spec slot anywhere.

  • names object-view-unmirrored-keys-7779.test.ts → packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts — edited by this change

    ⚠️ Dated note, 2026-09-28 — the value type has since been decided, and listViews is mirrored by reference — objectui#7928. Later in this same release the maintainer's ruling A made ObjectViewSchema.listViews the protocol's own named-view record, by reference: on both faces its value is @objectstack/spec's strict ObjectListViewSchema, and a NamedListView is not assignable to it. The docs have taught that shape since objectui#8255. So "listViews stays unmirrored", "The declaration's value is the local NamedListView", the key "stays in the parity ledger … until the maintainer decides its value type" and the table's "still unmirrored" above no longer hold: the ObjectViewSchema entry has left UnmirroredDeclared. NamedListView stays exported with its 64 declared members. The renderer reads some of them off a named view and leaves the rest unread, and the split is no longer the 21 and 43 this entry counted: objectui#7928 stopped reading the options bag, which the record refuses, and objectui#10758 made the host delegation read a named view's remaining protocol members off the named view. The census in object-view-unmirrored-keys-7779.test.ts pins the current figures. .changeset/7928-listviews-by-reference-fold.md (PR objectui#10821) states what ships; the text above is kept as the reading of this change.

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 328abeb55 (merge-base with origin/main): 8 file(s) changed outside .changeset/, read against 1689 pending declaration(s) that publish a body (2289 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3103.0 KB 3104.5 KB
Main entry chunk (gzip) 149.4 KB 350 KB
Entry file index-BE5gmSqG.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.58KB 6.17KB
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.22KB 10.61KB
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.70KB 2.23KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
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.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) 558.56KB 133.87KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.61KB 63.16KB
fields (index.js) 261.01KB 66.28KB
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 (translateFn.js) 0.20KB 0.18KB
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.32KB 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.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.08KB 46.94KB
plugin-dashboard (index.js) 137.82KB 36.70KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.51KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.15KB 9.14KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
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) 119.16KB 39.05KB
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) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
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.26KB
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.27KB 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: 600cf89017790f5d8cde7296836db6be1c2a5300

① Derived judgments

The code is sound. The prose needs five fixes (listed at the end of this section).

navigation

  • What the fence hands ListView. It passes navigation: currentNamedViewConfig?.navigation ?? activeView?.navigation ?? (schema as any).navigation. That is three rungs, each taking the whole object. I re-derived both fence sha1s and they match the dev's at base and head (40bc2292… and a140d042…), so the fence is byte-identical.
  • Which navigation the delegation's row click follows. It follows the named view when that view declares navigation, and the node otherwise. It never follows the activeView.
    • ListView's useNavigationOverlay calls the onRowClick it is handed and then returns, so ListView's own overlay never opens. ObjectView's formLayout is the only record surface, and for a named view the handler and the relay now agree.
    • They still disagree in one case: a host views entry carries navigation and the named view does not. The relay then carries the activeView value and the handler the node's. This predates the PR, and the order forbids an activeView rung.
    • This case has no in-tree reach. The console passes its own onRowClick and no listViews, and no in-tree host passes both listViews and renderListView.
  • Whole-object replacement is correct.
    • In the spec, NavigationConfigSchema is a strict object whose keys all have defaults (mode defaults to page; preventNavigation and openNewTab to false). A named view's navigation is .optional(). So a parsed named navigation is always complete, and a per-key merge could not mean anything.
    • The fence and useNavigationOverlay both take the whole object. Nothing merges key by key.
    • The whole-object pin tells a replacement apart from a merge: the node's preventNavigation would survive a merge.
  • Consumers of navigationConfig. They are the handleRowClick branches (none/preventNavigation, new_window/openNewTab, drawer, modal, page, split/popover), formWidthClass, formLayout and the NavigationOverlay width. None of them has to stay node-level. But formLayout is also the container for the create and edit forms, because handleCreate and handleEdit open the same isFormOpen state. The PR discloses none of the following:
    • A named navigation.mode now also moves the New and Edit forms onto its surface. split swaps the whole tree ObjectView returns.
    • Under a named split or popover, the New button opens nothing.
      • The split branch draws the overlay only when selectedRecord is set, the popover branch requires it too, and handleCreate sets it to null.
      • This defect already exists for the node's own navigation. It is now reachable from a named view on route 2, where authored nodes show New by default. The Studio is not affected, because ViewPreview sets showCreate: false.
      • A code fix belongs on a follow-up card; the seat should file one.
    • A named navigation also turns on row clicks under a node's operations.read: false. The handler checks operations.read only when there is no navigation config. This matches how the node's own navigation behaves, and the docs already carry the "without a navigation config" qualifier.
  • Memo stability.
    • With no named view active, navigationConfig is schema.navigation by reference, the same as base.
    • currentNamedViewConfig is memoized on [hasNamedViews, activeNamedView, namedListViews], so handleRowClick is rebuilt only on a real switch. gridSchema does not depend on it.

inlineEdit → editable

  • editable is the right member. ObjectGridSlotKey declares it, and ListView makes the same mapping (editable: inlineEdit && inlineEditOffered).
  • The gate, read in ObjectGrid. inlineEditable = (schema.editable ?? false) && objectInlineEditable. objectInlineEditable is isObjectInlineEditable(objectSchema, effectiveApiOps) AND perms.can(objectName, 'update'). It feeds editable, renderCellEditor and the save column, so a named inlineEdit cannot widen editing past a grant.
  • Other sources of editable / inlineEdit:
    • The node's own inlineEdit. It is one of the objectui#5097 host-composition keys (OBJECT_VIEW_HOST_COMPOSITION_KEYS), and objectui#5248 forbids reading those outside the fence. So having no node rung is required, not a gap. The code comment gives the weaker reason ("Route 2 handed the grid no editable before"); naming the host-composition reason instead is an optional tightening.
    • activeView. The registered renderer passes no views, and member 1's named-member block has no activeView rung.
    • table.editable. It is a declared ObjectViewSchema.table member that route 2 has never read, at base or at head. This PR does not shadow it. It is a declared-but-unenforced key sitting next to the new rung, and a candidate for a follow-up.
    • The delegation's other rungs. Its activeView and node rungs are reachable only through host composition, so the PR does not need to disclose the difference.

fieldOrder

  • inFieldOrder copies ListView.effectiveFields step for step.
    • Same columnIdentity from @object-ui/core, the same rank Map and the same ?? Infinity subtraction comparator. Infinity − Infinity is NaN, which Array sort treats as 0, so unnamed and identity-less entries keep their relative order on both routes.
    • Both apply it after hiddenFields. A name the projection does not carry orders nothing. With no projection it returns undefined, and ListView then hands its grid no columns. Both slots are ordered.
    • The only differences are an Array.isArray guard (stricter, harmless) and no field-level-security step. FLS belongs to ObjectGrid and does not affect order.
  • The parity pin drives both real routes. It mounts route 2 of the real ObjectView, with the ObjectGrid export mocked as a sink, and the real ListView through renderListView, with the registry's object-grid as a sink. It is not a reimplementation.
  • The member-1 divergence is not made worse. The set of kept and dropped identity-less entries is unchanged. Where route 2 keeps such an entry, it now sorts last.

The objectui#7779 READ_TEXT edit

  • The edit is one READ_TEXT line plus a two-line comment. No figure moved.
  • navigation, fieldOrder and inlineEdit were already in the whole-file read set through the fence, so the census stays at 43 read / 21 unread of 64 by reading.
  • readSites.label stays at 2: no label read was added.

Pins

  • Counts. There are 16 rows: navigation 7 (4 fix, 3 control), inlineEdit 3 (1 fix, 2 control) and fieldOrder 6 (4 fix, 2 control).
  • Red on base, green on head. I walked each fix row by construction, and each is red on base. The dev's ablation counts (4, 1 and 4) match these rows.
  • The controls are lit. Each one asserts a positive outcome: onNavigate is called, the form opens, the click lands on the row, or a concrete column array arrives.
  • The grant probe is consistent. Only "THE GRANT STILL WINS" depends on objectInlineEditable.
    • That row exercises the object-verdict half of the gate (userActions.edit: false), not the update-grant half.
    • This is acceptable: I read the gate directly.

Prose: five fixes before landing (wording only, no figure or pin moves)

  1. label / data, in four places.
    • As written, the sentence is untrue for label: route 2 reads the named label as the tab text. The word "still" also implies pending work on a card the seat closes with Fixes. The sentence should give the reasons and cite objectui#10971.
    • New changeset. Replace "label and data, also declared on both a named view and object-grid, are still not read off the named view on this path." with:
      Replacement: "label and data, also declared on both a named view and object-grid, are not handed to the grid on this path, by ruling. The named view's label is already the tab's text, and handing it to the grid would paint a second caption the delegation never paints. data is held back because ListView and ObjectGrid pick different objects when data.object differs from the node's objectName (objectui#10971)."
    • README and mdx. Replace "label and data, also declared on both, are still not read off the named view on this path." with:
      Replacement: "label and data, also declared on both, are not handed to the grid on this path: the named view's label is already the tab's text, and data waits on objectui#10971, because ListView and ObjectGrid pick different objects for it."
    • Dated note in 10885-object-view-route2-named-grid.md. Replace "label and data are still not read off the named view on this path." with:
      Replacement: "label and data are not handed to the grid on this path, by ruling: label is already the tab's text, and data waits on objectui#10971."
  2. "saved view" in "Opening a record" (README and mdx).
    • In this package, "saved view" names the host views / adapter rows (the README's ManageViewsDialog covers "every saved view"). Their navigation does NOT replace the node's, because there is no activeView rung.
    • Replace "An active saved view (listViews, under Read/List) that declares its own navigation replaces this one while it is shown." with:
      Replacement: "An active named view (listViews, under Read/List) that declares its own navigation replaces this one, as a whole, while it is shown."
  3. Disclose the formLayout reach.
    • New changeset. Replace "The row click follows it, and so do the record form it opens and that form's width." with:
      Replacement: "Everything ObjectView derives from it follows: the row click, the surface the record, create and edit forms open on, and that surface's width. Under a named split or popover, as under the node's own, the New button opens no form, because those two surfaces open only beside a selected record."
    • README and mdx. Replace "the row click, and the record form it opens, follow the active named view" with:
      Replacement: "the row click, and the surface and width of the record, create and edit forms, follow the active named view"
    • Dated note. Replace "and so does the record form it opens" with:
      Replacement: "and so do the surface and width of the forms it opens"
  4. "on this path and on a host's renderListView alike" is overstated. The host must wire the handler, and an onRowClick passed to ObjectView wins on both routes.
    • README and mdx. Replace that phrase with:
      Replacement: "on this path, and on a host's renderListView when the host wires the onRowClick it is handed and passes ObjectView no onRowClick of its own"
    • New changeset and dated note. After "both paths hand down the same click handler", append:
      Replacement: "A host onRowClick passed to ObjectView still wins on both paths."
  5. The member-1 pin docblock is stale. The order said to grep for the sentences this member makes false. In packages/plugin-view/src/__tests__/ObjectView.routeTwoNamedGridMembers-10885.test.tsx, the docblock still lists inlineEdit as chrome that "route 2 has no slot to read it into".
    • Remove inlineEdit from that list and add this bullet:
      Replacement: "inlineEdit — object-grid declares it as editable; since member 4 route 2 hands it over under that name (ObjectView.namedViewInlineEdit-10885.test.tsx)."
    • Extend the navigation bullet with:
      Replacement: "since member 4 the handler route 2 passes reads it (ObjectView.namedViewNavigation-10885.test.tsx)."

② Semver level

patch holds, and so does Clause-②: no. I confirmed this by reading:

  • inFieldOrder is module-private.
  • packages/types gains only a test edit, and @object-ui/app-shell gains only a test file.
  • No declared type, accepted key or export moves.
  • The level is not major.

③ Boundary flags

  • CI on the head is final. There are 43 check runs: 40 succeeded (all 8 Test shards, Type Check, Lint, Build & E2E, the changeset gates and the Governed Surface Queue Guard) and 3 were skipped (dependabot, Test (coverage) and the coverage shard matrix). None failed and none is pending. Vercel succeeded.
  • The console eager closure is inside its budget. The Console Performance Budget comment reads 3103.0 KB against the 3104.5 KB budget (1.5 KB headroom), matching the dev's head figure. Bundle Analysis and the Docs Route Eager Closure Check both succeeded.
  • git merge-tree --write-tree against main 328abeb553 is clean (tree 8197dba48d). Main has moved 8 commits past the base, and none of them touches the PR's 9 files.
  • No other open PR touches these files. I scanned all 14 open PRs.
  • check-governed-merges: NOT governed. None of the 9 paths hits the register, and 559 changed lines is under the 5000-line threshold.
  • Draft and assignee. The PR is in draft and assigned to os-elon-musk. Its labels are documentation, package: types, plugin, tests and package: app-shell, with no needs:contract-review, which fits Clause-②: no.
  • Landing edit to the PR body. When the seat changes line 1 to Fixes #10885, two more pieces of the body must change with it:
  • Not re-run, per the rules. I did not re-run vitest (the dev's 549 of 549) or the ablations. The CI Test shards are consistent with those claims.

Implemented-by: claude/issue-10885-route2-named-view-rest
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

The five wording fixes in ① (label/data in four places, "saved view", the formLayout disclosure, the host qualifier, and the stale member-1 docblock) must land before merge. No code change is required. The seat should also file follow-up cards for the New button opening nothing under split/popover and for the unread table.editable.

…ed navigation (objectui#10885)

Contract-review fix round 1, wording only. `label` and `data` are not handed
to the grid on route 2, by ruling; `data` waits on objectui#10971. A named
view's `navigation` moves the surface and width of the record, create and edit
forms, not only the row click, and a host `onRowClick` passed to `ObjectView`
still wins on both paths. The member-1 pin's docblock names what member 4 now
reads.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3103.4 KB 3104.5 KB
Main entry chunk (gzip) 149.5 KB 350 KB
Entry file index-CaFEpqoY.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.58KB 6.17KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 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.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) 558.56KB 133.87KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.61KB 63.16KB
fields (index.js) 261.01KB 66.28KB
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 (translateFn.js) 0.20KB 0.18KB
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.32KB 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.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.08KB 46.94KB
plugin-dashboard (index.js) 137.82KB 36.70KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.51KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.15KB 9.14KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
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) 119.16KB 39.05KB
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) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
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.26KB
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.27KB 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: bd41f5cf18373f1daab3b1d1f965cb4ee2c18d3c

This is an in-seat delta review of fix round 1: 600cf89017 to bd41f5cf18, one commit. I checked it against record 5869604558 (FAIL, wording only).

① Derived judgments

  • Scope.
    • 5 files: 30 insertions and 17 deletions.
    • The only change in a code file is a docblock comment in ObjectView.routeTwoNamedGridMembers-10885.test.tsx.
    • ObjectView.tsx is byte-identical to the reviewed head, so the fences, census, pins and semver judged in 5869604558 carry over unchanged.
  • Fix 1, label / data: in place in all four sites.
    • "are not handed to the grid on this path" appears once in each of the new changeset, the dated note, the README and the docs page.
    • Each gives the reason (label is already the tab's text) and cites objectui#10971 for data.
    • Member 1's own paragraph 3 still says "are still not read off the named view". That is the historical text the dated note supersedes, which is the objectui#10533 form, so leaving it is correct.
  • Fix 2, "named view": in place in the README and the docs page.
    • "An active named view (listViews, under Read/List) … replaces this one, as a whole".
    • Three older "saved view" phrases remain in the README (the ManageViewsDialog note, "Saved views are listViews" and the view-order section). This round does not touch them, and they are outside the review's ask.
  • Fix 3, the formLayout reach: in place.
    • The new changeset names "the surface the record, create and edit forms open on" and the New button under a named split / popover. That defect is filed as objectui#10975.
    • The README and docs page name "the surface and width of the record, create and edit forms".
    • The dated note says "the surface and width of the forms it opens".
  • Fix 4, the host qualifier: in place.
    • The README and docs page say "when the host wires the onRowClick it is handed and passes ObjectView no onRowClick of its own".
    • Both changesets add "A host onRowClick passed to ObjectView still wins on both paths."
    • In the new changeset it closes the sentence rather than splitting it mid-clause. That is an equivalent placement, and it is accepted.
  • Fix 5, the member-1 pin docblock: in place.
    • inlineEdit has left the chrome list, and its own bullet names editable and the member-4 pin.
    • The navigation bullet names the member-4 pin.
    • "Two bucket-① members" became "Three", the one word outside the ordered strings. The new bullet would otherwise make that sentence false, so it is accepted.
  • Frontmatter: byte-identical on both changesets (md5 4d3ab4fd127d…, '@object-ui/plugin-view': patch).
  • The README and docs page route-2 paragraphs are identical to each other.
  • Commit trailers are the model-free Claude pair.

② Semver level

patch and Clause-②: no hold, unchanged from 5869604558. This round moves no type, key or export.

③ Boundary flags

  • CI: this record posts only after every check at bd41f5cf18 is final and green. The landing queue gates on it.
  • Merge-tree: the landing queue re-runs git merge-tree against current main first.
  • Landing edit: the seat rewrites the PR body as the review asked.
    • Line 1 becomes Fixes #10885.
    • The "Part of" paragraph becomes the rulings (5869413175, objectui#10971).
    • The open-questions heading becomes "label and data: measured, then ruled B".
  • Carriers the review asked for: objectui#10975 (the New button under split / popover) and objectui#10976 (ObjectViewSchema.table typed to carry 60 keys, 13 of them read).

Implemented-by: claude/issue-10885-route2-named-view-rest
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 12:29
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 4d22e33 Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10885-route2-named-view-rest branch September 28, 2026 12:46
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…nvalidation (objectui#10887) (objectstack-ai#10973)

Fixes objectstack-ai#10887
Clause-②: yes — one identifier, `useDataInvalidation`, joins the
published author scope of `kind: 'react'` pages, and
`content/docs/guide/react-pages.md` teaches it.

**Why a closing keyword.** The card has three members. Members 1 and 2
landed in PR objectstack-ai#10898 (squash `9f0c84a44`). This PR lands member 3, ruled
A (ruling `5864668610`), so all three are in once it merges. The four
review notes from the members 1 and 2 round ride this round: (a) and (d)
are text and are corrected here; (b) and (c) need a behaviour change, so
they are reported as findings and not changed here (see Acceptance
notes). The objectstack half of the ruling (the docs page, the skill and
the showcase pages) is carried by objectstack-ai/objectstack#20395. The
`Blocked-by` line on objectui#10519 is the seat's to update; this PR
does not touch that card.

## What changed

- **`@object-ui/components`, the react-page renderer.** `ReactKindPage`
injects `useDataInvalidation` from `@object-ui/react` into the author
scope, beside `useAdapter`. It is the same hook `ListView` reads to
refresh its rows: `useDataInvalidation('object')` returns a nonce that
moves when the bus reports a write to that object, or `'*'`. A page
names it in the dependency list of the effect that reads through
`useAdapter`, and that read re-runs in place. The hook is a module-level
function, and the scope memo still depends on the schema and the adapter
only, so the scope keeps its identity and no page recompiles or loses
state. The file header's scope list gains the entry.
- **The react-pages guide.** The scope table gains a
`useDataInvalidation` row. The Live data example reads `const changed =
useDataInvalidation('showcase_project')` and names `changed` in the
effect's dependency array, followed by one sentence on why: the nonce
moves each time the bus reports a write to that object, so the effect
reads again in place without remounting the page.
- **Changeset** `10887-react-page-data-invalidation`: `minor` for
`@object-ui/components`, `Clause-②: yes`.
- **No new dependency edge.** `useDataInvalidation` is exported by
`@object-ui/react`, which `@object-ui/components` already depends on and
already imports `useAdapter` from.

## Review notes from the members 1 and 2 round (`5864212493`)

| note | what it said | here |
| --- | --- | --- |
| (a) | the plugin-view changeset's "switches off that view's own bus
reader" is imprecise for a gantt handed zero rows | **Corrected
(text).** The pending changeset `10887-object-view-non-grid-bus-reader`
now names the exception: a gantt handed zero rows still queries for
itself and keeps its own bus reader (objectui#7333). Frontmatter
byte-identical (the first three lines hash the same before and after);
the file has not shipped (no CHANGELOG carries its text), so it is
corrected in place. The same imprecision in `ObjectView`'s member 1
comment is corrected too. |
| (b) | `fetchDrawsView` is a deny-list, so a host `views` entry the
renderer does not draw adds one unused read | **Not changed:
behaviour.** An allow-list changes which views subscribe. Reported as a
finding. |
| (c) | a calendar re-read flashes its loading placeholder | **Not
changed: behaviour.** `ObjectView`'s non-grid fetch sets `loading` on
every run and passes it to the view, and `ObjectCalendar` shows it; a
silent re-read changes behaviour. Reported as a finding. |
| (d) | the `identityKey` comment is incomplete | **Corrected
(comment).** It now says the non-grid fetch re-reads when `refreshKey`
moves or when the bus reports a change to the object (objectui#10887),
that a gantt handed rows draws them while a gantt handed zero rows
queries for itself (objectui#7333), and that `tree` reads the bus itself
(objectui#10778). |

Both comment edits sit inside `ObjectView`'s component body, so neither
reaches a published `.d.ts`; no plugin-view surface moves and the
plugin-view changeset keeps its `patch`.

## Pin, through a real react page

`packages/components/src/__tests__/react-page-invalidation.test.tsx`.
The page is compiled from source by `@object-ui/react-runtime` inside
the real `ReactKindPage`, dispatched by the real `PageRenderer` for
`type: 'home'`; the events go through the real bus
(`notifyDataChanged`). Only the adapter is a stand-in, so reads can be
counted. The page source is the guide's Live data pattern with the
nonce.

- One record-scoped bus event on `showcase_project` re-runs the page's
`find` exactly once (1 to 2), the re-read rows reach the page, and a
counter the user clicked before the event still reads 1, so the page was
not remounted.
- Lit control: an event on `showcase_invoice` does not re-run the read
(`find` stays at 1, the rows stay the first answer), while a bare
`useDataInvalidation('showcase_invoice')` reader mounted beside the page
moves from 0 to 1, so the event demonstrably reached the bus.

| run | result |
| --- | --- |
| base `react-page.tsx` (blob `f70b8f425`, restored from `HEAD`
afterwards: blob equal, `git diff HEAD` empty) | `Tests 2 failed (2)`:
both tests, each at the page-error assertion, `ReferenceError:
useDataInvalidation is not defined` in the page's error panel |
| head | `Tests 2 passed (2)` on the implementation's first commit
`f64ba6e66`, and again inside the package run below at `af76d224c` |
| ablation: the injected hook replaced by a stub that never moves
(`ablation-replace`, anchor `useDataInvalidation,` hit once, blob
`ab712dfac` to `9625f569b`, restored to the `HEAD` blob, `git diff HEAD`
empty) | `Tests 1 failed \| 1 passed (2)`: only the re-read test,
`expected 'read 1' to be 'read 2'`; the lit control stays green |

The pin imports its subject by relative path, and `@object-ui/react` and
`@object-ui/react-runtime` through the root vitest alias to their
sources, so no `dist/` sits between a mutation and the run.

## Tests and gates (head `af76d224c`)

- `pnpm exec vitest run packages/components/ packages/plugin-view/
--maxWorkers=2` under the verify lock at `af76d224c` (the run printed
that sha): `Test Files 379 passed | 1 skipped (380)`, `Tests 3689 passed
| 24 skipped (3713)`.
- `pnpm exec vitest run scripts/ --maxWorkers=2` under the verify lock
at `af76d224c`: the whole `scripts/` test project, so every
`scripts/__tests__` gate whose corpus reads a touched file (the
changesets, the guide, the package sources) is in it: `Test Files 177
passed | 2 skipped (179)`, `Tests 5332 passed | 2 skipped (5334)`.
- `type-check` for `@object-ui/components` and `@object-ui/plugin-view`
(echoed `tsc --noEmit && tsc -p tsconfig.test.json`): Done, Done, at
`2ca2478ee`, after `turbo run build` over both packages' dependency
closures (15 tasks). The one later commit reflows comment and changeset
prose only. `tsc -p tsconfig.test.json --listFiles` in
`packages/components` names the new pin once, so the test program
compiles it.
- At `af76d224c`, each exit 0: `check:control-bytes`,
`check:new-line-citations` (`0 new citation(s)`), `changeset:check`,
`check-changeset-presence` (`3 source file(s) of 2 released package(s)
changed, and this change declares 1 changeset(s)`),
`check-changeset-overwrite` (reports the plugin-view changeset edit as a
correction, its case 2), `check:changeset-claims`,
`check:pending-changeset-literals`, `check:test-path-roots`,
`check:phantom-deps`, `check:unused-deps`, `check:shell-escape-residue`,
`check:doc-fences`, `check:doc-types`, `check:doc-example-ids`,
`check:doc-example-readers`, `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`,
`check:comment-mask-corpus`.
- ESLint over the three changed source files, `--no-inline-config
--format json`: 3 files, 0 errors. The new pin carries 0 warnings.
`react-page.tsx` (6) and `ObjectView.tsx` (60) carry the same warnings
per rule as their base blobs, read through `--stdin-filename`. This is a
narrowed run, not the repo lint: the root ESLint config enables no
type-aware linting (no `parserOptions.project`, no `projectService`), so
this diff cannot move a verdict on an untouched file. `pnpm lint` is
CI's.
- `check:doc-snippets` and `check:doc-examples`: NOT MEASURED locally,
prerequisite not met (they need the whole covered package set built).
The guide's changed block is fenced `jsx`, which `check:doc-snippets`
does not compile (its compiled fence set is `ts`, `tsx`, `typescript`).
CI runs both.
- `check-governed-queue-guard --test` over the 6 changed paths: NOT
GOVERNED.

## One case the widened scope can break

The scope's names become the parameters of the function the page source
is evaluated in. A page that declares its own top-level `const` or `let`
named `useDataInvalidation` therefore now fails with a `SyntaxError`
(`Identifier 'useDataInvalidation' has already been declared`), shown in
the page's error panel; a `function` or `var` declaration of that name
only shadows the injected one. Measured once through the real renderer
with a throwaway test (panel shown, message as quoted), not kept.
Census: no page source in objectui (apps, examples, content, skills) or
in objectstack `origin/main` (examples, content, skills, packages)
declares the name. The changeset states this case.

## Serial

Open PRs, read again just before this PR (12 open, every file list
complete under 100 entries): none touches `react-page.tsx`, the
`react-page-*` tests, `react-pages.md` or the `10887-*` changesets. One
touches `ObjectView.tsx`: PR objectstack-ai#10967 (opened after this claim, route 2's
named-view reads). `git merge-tree --write-tree` of this head against
its head `600cf8901`: clean, exit 0. Its hunks and this PR's two comment
hunks do not overlap. PR objectstack-ai#10777 touches `PageView.tsx`, its tests and
its own changesets only.

## Acceptance notes

- **(b), not filed.** `ObjectView`'s `fetchDrawsView` names the types it
excludes (`grid`, `tree`, `chart`) rather than the types
`generateViewSchema` draws, so a view type `generateViewSchema` has no
case for (it returns `null`, and `renderContent` falls through to
`ObjectGrid`, which queries for itself) still subscribes here and
re-reads once per matching event, for rows nothing renders. Pre-existing
shape of member 1; carrier: none.
- **(c), not filed.** The non-grid fetch sets `loading` at the start of
every run, including a bus re-read, and `ObjectCalendar` swaps to its
loading placeholder while it is set, the same flash as the calendar's
own objectui#10572 reader. A silent re-read would change behaviour;
carrier: none.
- **objectstack half, beyond objectstack-ai/objectstack#20395's named
files.** The spec's generated react-blocks reference
(`skills/objectstack-ui/references/react-blocks.md`, written by
`packages/spec/scripts/build-react-blocks-contract.ts`) lists the
injected scope as `React`, `useAdapter`, `data`, `variables`, `page`.
After this lands that list is incomplete.
objectstack-ai/objectstack#20395 names `react-pages.mdx`,
`rules/pages.md` and the showcase pages, not this generator; it is the
natural carrier.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants