Repository navigation
fix(data-objectstack,app-shell): one meta/view read per open: a settled view list that refresh and every console view write drop (objectui#13057) - #13092
Merged
objectstack-fleet[bot] merged 4 commits intoOct 19, 2026
Conversation
…n an open, dropped by refresh and every console view write (objectui#13057) The adapter stores the answer to GET /api/v1/meta/view under list:view and answers listViews / listViewOverrides from it, so the console order (eager read, then the object page's two readers) reads the URL once. The client read still always asks the server and stores what it got. Every path that should see a fresh list drops it first: - ObjectView refreshData drops it before re-reading the saved views; - useMetadataClient drops it after each MetadataClient write door that can change a published view (item writes typed view, package-wide publishes); - PackagesPage publishDrafts emits the metadata-refresh pulse the other publish paths emit; - org and session switches keep clearing the adapter cache. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…ctui#13057) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
Contributor
✅ Console Performance Budget
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
Size Limits
|
objectstack-fleet
Bot
deleted the
claude/issue-13057-settled-view-list
branch
October 19, 2026 22:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13057
Clause-②: no
What changes
One object-page open now reads
GET /api/v1/meta/viewonce (was 3 before objectui#13044, 2 after PR #13053). This completes objectui#13044 ruling6268839381(a) to (c).The adapter keeps a settled list (
@object-ui/data-objectstack).MetadataCache.sharestill joins a request in flight and is never answered from a settled entry. It now also stores its landing. A request detached byinvalidate/clearstill stores nothing.installViewListShare) is unchanged otherwise. A caller ofgetClient().meta.getItems('view')(the provider's eager read) always asks the server, and its answer is stored underlist:view.readViewList(the source forlistViewsandlistViewOverrides) answers from the stored list while it holds one. Otherwise it reads through the client wrap. Every caller gets its ownstructuredClone.Every path that should see a fresh list drops it first.
ObjectView'srefreshDatacallsdropPublishedViewList(dataSource)before it bumps the counter. The saved-views read it re-runs asks the server again. That covers realtime, server actions, imports and the refresh action. Provider re-reads (refresh('view')) go through the client, so they always ask.invalidateViewKeys) and the adapter's client write wrap already dropped the key. Now that the key holds a settled list, they drop it.MetadataClient.useMetadataClient(the hook every app-shell write path takes its client from, as it does for the advisory sink) wraps the client's write doors throughinstallViewListDrop(views/metadata-admin/viewListDrop.ts). Eachsave/publish/publishDraft/reset/rollbackwhose type isview, and eachpublishPackageDrafts, drops the list once the write settles, whether it landed or was refused. It drops through the adapter's existing publicinvalidateCache(key).clearCache). It also re-reads every loaded provider type, so firing it on every Studio draft autosave would be a refetch storm. Package-wide publishes get both.PackagesPagepublishDraftsnow callsemitMetadataRefresh()after a 2xx batch, as Studio's publish-all, Home's and the chat bar's do. It is sent before thefailed[]verdict, as Studio sends it.MetadataProviderdropAdapterWorldclears the whole adapter cache. It now has a settled-list pin.No new export, no new public member.
MetadataCacheis package-internal.viewListDrop.tsis module-internal: no barrel re-exports it, andmetadata-admin/index.tsand the packageindex.tsare untouched. The one cross-package spelling is the key literallist:viewinviewListDrop.ts. The provider and refresh pins drive a real adapter, so if the key is renamed they go red.The writers, enumerated (and pinned so a new one cannot be silent)
The enumeration is pinned at the door, not by call-site list. Every console
MetadataClientis built byuseMetadataClient: the provider's draft-preview client is the read-only exception, andnew MetadataClient(was already ratcheted. Every door ofMetadataClientthat sends a write is wrapped.viewListDrop-13057.test.tsxderives the write doors fromMetadataClient.prototype: every method whose comment-masked body callsthis.sendWrite(. It asserts that set equalsITEM_WRITE_DOORSplusPACKAGE_WRITE_DOORS.src(comment-masked withscripts/js-comment-mask.mjs) forcreateConsoleMetadataClient(call sites. It asserts they are exactly the hook andMetadataProvider.Call sites under
views/metadata-admin/**andviews/studio-design/**that can write aviewrow, from a grep of the write doors:ResourceEditPage: create (save, live or draft), the guarded draftsave,publish,reset(delete/reset and discard draft). The type is generic, soviewincluded.ResourceHistoryPage:rollback, generic type.EmbeddedItemEditor:save(parentType, …).DraftConflictDialog: the guardedsaveused byResourceEditPageand Studio'sObjectListViewInspector(view drafts).PackagesPageandStudioDesignSurface:publishPackageDrafts. Studio also haspublishDraft('flow', …)and typed saves.The other writes in those directories are typed
object/hook/flow/permission, and they keep the list (control pin). All of these take their client fromuseMetadataClient.Pins
MetadataProvider.oneMetadataRead-13044.test.tsx: the console order (eager read, thenlistViewspluslistViewOverrides, plus a second overrides read) is 1 request (was 2). The comment in the test gives the reason.listViewsreads are settled. A providerrefresh('view')asks again, and the next list read sees what that refresh read (a row added server-side appears only after it).ObjectView.refreshRereadsViews-13057.test.tsxuses the real page, a real adapter and a realtime refresh. The open reads once;refreshDatare-asks (1 then 2). Control: a re-render with no refresh reads nothing.viewListDrop-13057.test.tsxhas one leg per writer class: save (draft), save (live), publish, publishDraft, reset, rollback, publishPackageDrafts. Each leg settles the list, writes, then the next read re-asks exactly once. A refused view write drops it too. Controls: a write of another type keeps the list, the write's answer passes through untouched, and with no adapter in context the writes still land.oneMetadataRead:invalidateViewKeys, and asaveItem('view')straight through the adapter's client, each drop the settled list.PackagesPage.publishPulse-13057.test.tsxuses the real sheet anduseMetadataClient. Publish app emits the pulse once, and the nextlistViewsre-asks. A batch answered withfailed[]still emits it.MetadataProvider.switchDropsAdapter-13044.test.tsx: an org switch and a session switch each re-ask after a settled read.oneMetadataRead: a draft-previewlistViewsis its own?preview=draftrequest and never fillslist:view(getCached('list:view')stays undefined, and the published read still asks once).viewCacheInvalidation.pin.test.tsstays truthful:listViewsconsults exactlylist:view(settled within an open), else the transport, and no per-object key. Its header says the named writers drop it.MetadataCache.share-13044.test.ts:shareis never answered from a settled entry and stores its landing. A request detached byinvalidatestores nothing.Ablations
Each was run through
ablation-replace.mjs(anchor must hit; restore proven by blob hash equal to HEAD and an emptygit diff HEAD). The subject resolves through the vitest alias tosrc/, so no dist build was involved.readViewList: drop the settled read (if (settled !== undefined) return …replaced withvoid settled;)expected 2 to be 1, plus the two settled legsindex.ts9ef65591060binstallViewListDrop: the package door without.finally(drop)publishPackageDraftsleg (expected 1 to be 2) and (d)'s re-read leg; the other 6 writer legs stayed greenviewListDrop.ts286c0f6df1fainstallViewListDrop: the item doors without the view dropviewListDrop.ts286c0f6df1farefreshDatawithoutdropPublishedViewListexpected 1 to be 2ObjectView.tsx6cc2e5cc98d1publishDraftswithoutemitMetadataRefresh()expected +0 to be 1PackagesPage.tsx98ee9953967fGates
The numbers below are for head
ca09edc959. Each exit code was captured before any pipe.turbo run build --filter='@object-ui/app-shell^...', 28/28).@object-ui/data-objectstacktype-check: 0. Its--listFilesincludes both edited tests.@object-ui/app-shelltype-check(tsc --noEmit && tsc -p tsconfig.test.json): 0.--listFilesincludes all five new or edited app-shell tests.--maxWorkers=1, under the verify lock): the 7 new or edited files, then the families of the touched files, all green.MetadataCache, everyObjectView*test, everyPackagesPage*andMetadataProvider*test, and every app-shell test importingmetadata-admin/useMetadata, which takes in 129 studio-design files.pnpm lint, narrowed to the 13 changed.ts/.tsxfiles: 0 errors.--format jsonoutput.eslint.config.jssets noparserOptions.project/projectService, so linting is not type-aware, and this diff cannot move a verdict on an untouched file.tRefones inuseMetadataClient.check:phantom-deps,self-import,unreferenced-sources,doc-example-readers,handler-key-reads,side-effects-array,element-data-source-declaration,spec-symbols,action-forward-parity,designer-field-key-parity,icon-record-names,i18n-keys,i18n-drift,i18n-designer-parity,control-bytes,scripts/check-hand-rolled-comment-mask.mjs. Alsocheck:test-path-rootsandcheck:new-line-citations: 0.node scripts/check-changeset-fixed.mjs: 0.check-changeset-presence: one changeset declared,.changeset/13057-settled-view-list.md(patchfor both packages).check:spec-floors. It exits 1 withno-artifact, because it needs the whole workspace built; that is not a verdict on this diff.packages/app-shell/andpackages/data-objectstack/directories. They are declared to CI per the dispatch (no whole-package runs on the shared box).Acceptance notes (seen, not filed: static reading, no public-door reproduction)
PackagesPagelifecycle actions emit no metadata-refresh pulse either: registry publish, revert, delete, enable/disable and adopt-orphans all go throughapiJson. Each can change live metadata, views included. Ruling 4 named onlypublishDrafts. Carrier: none.MetadataClientview write dropslist:view, but not the adapter's per-objectview-overrides:OBJECT/view:OBJECT:NAMEentries. Those need the object name, whichreset/rollback/publishdo not carry. This was already the case before this PR, and the stored override map can stay stale for the cache TTL after a metadata-admin view publish. Carrier: none.Session
https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.Generated by Claude Code