Repository navigation
fix(app-shell,data-objectstack): concurrent identical Studio reads share one in-flight request (objectui#11797) - #11856
Merged
objectstack-fleet[bot] merged 4 commits intoOct 8, 2026
Conversation
…are one in-flight request (objectui#11797) fetchPackages, the _drafts ledger read and the MetadataClient list/get/ listDrafts/listTypes reads each sent their own request when several mounted readers asked the same question at once. A caller that arrives while an identical request is pending now waits for it. The entry is dropped when the request settles (no reuse window), a failure is not remembered, and the writes each module can see drop pending reads so a read asked after a write never joins one sent before it. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…dio entry (objectui#11797) Counted on the wire through held transports: packages, the _drafts ledger, MetadataClient list/get/listDrafts across two clients on one fetch, and the sys_user_preference loads (three keys are three questions; one key asked twice is one request). Pins no reuse window, unremembered failures, and the write and announcement paths that drop a pending read. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…rafts ledger once on entry (objectui#11797) Renders StudioDesignSurface on its Data tab with the real fetchPackages and usePendingDrafts over a held fetch, and counts requests on the wire. Adds the patch changeset for app-shell and data-objectstack. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
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
|
…longer holds the object joiners copy from (objectui#11797) shareRead returned the shared promise itself to the request's first caller, so a caller that awaited it with no hop in between ran ahead of the joiners' copies and could edit the object they were copied from. Every sharer is now handed its answer in its own reaction; the last one takes the parsed original and every earlier one a copy, so nothing is copied when nobody joined. Pins: the helper-level case (red on c3759d6), the public-method case in both join orders, and the app-shell parsers (fetchPackages, fetchPendingDrafts) already handing out primitive-only entries. README: the Shared In-Flight Reads bullet now covers MetadataClient. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
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
|
This was referenced Oct 8, 2026
objectstack-fleet
Bot
deleted the
claude/issue-11797-shared-inflight-reads
branch
October 8, 2026 03:38
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 #11797
Clause-②: no
What changes
Opening a Studio package mounts several readers that ask the same question at once, and each one sent its own request. A caller that arrives while an identical request is still pending now waits for that request instead of sending another:
fetchPackages(@object-ui/app-shell,studio-design/packages-io.ts): the package switcher, the writability courtesy gate and theDataPillarnamespace lookup share oneGET /api/v1/packages.fetchPendingDrafts(@object-ui/app-shell,preview/usePendingDrafts.ts): the hook's mount read and the surface's own mountrefresh()(Studio top bar, chat bar) share oneGET /api/v1/meta/_draftsper scope.MetadataClient(@object-ui/data-objectstack,metadata-client.ts):listTypes,list,listDraftsandget/getDraftshare one GET per method, URL and headers. The sharing works across every client built on the samefetch, because the console creates one client per component, all on one authenticated fetch. Every caller, the first one included, gets an answer object no other caller holds: the last caller to be handed the answer takes the parsed original and every earlier one astructuredClonecopy, so nothing is copied when nobody joined (patch round 1,bab3004).No call site changed. The
fetchPackagescall sites inStudioDesignSurface.tsxandBuilderLanding.tsxare untouched: the read is shared below them.Measured: the duplicates are concurrent mounts, and in-flight sharing alone removes them (H1)
Requests are counted on the wire, through transports that hold every request open until the test answers it. Before is the ablation run: the three production files checked out at the base
455c646, with the tests unchanged.StudioDesignSurfaceon the Data tab:GET /api/v1/packages_drafts?packageId=…_draftsMetadataClients on one fetch, each callinglistDrafts,listandgetfetchPackages()callsThe before counts match the card's "packages ×3" and "_drafts ×4" readings.
Design: in-flight sharing only, with no reuse window (H2)
I cannot name every write that must clear a cached answer. An agent creates packages and stages drafts on the server, and the client never sees those writes. So, per H2, this keeps only the in-flight sharing, with no reuse window:
MetadataClient: everysave,publishDraft,publishPackageDrafts,reset,publishandrollbackthrough a client on the samefetchdrops every pending read on thatfetch. The drop happens when the write's response arrives, whether the write succeeded or failed.duplicatePackage(the same module) and the existingobjectui:packages-changedannouncement (create and edit, fromPackageFormDialog).emitMetadataRefresh), which the module doc already names as the contract every publish path keeps. The drop runs before the hooks it wakes read again, so those hooks share one new request.PackagesPage, a draft save while a raw_draftsread is pending, any server-side agent write) can still be answered by a read that was already on the wire. The window is one round trip, and the same residual applies to the adapter's existingfindsharing. A read asked after that pending read settles always goes to the server.Failures are shared, not remembered (H3). A failed request rejects every caller that was waiting on it, and the next call retries. Tests cover an HTTP failure and a transport failure (
fetchrejecting) for the metadata client.No exported signature changes (H4). There is no new export, method, option or type.
shareRead,sendWrite,readPackagesPayloadandreadDraftsPayloadare all module-private orprivate.sys_user_preference: no duplicate to remove (the premise does not hold for this family)The card reported three
GET /api/v1/data/sys_user_preferencereads "with the identical filter" on Home → Studio. I measured this through the realObjectStackAdapterand client (userState.inflightShare-11797.test.ts).UserStateBridgeattaches three adapters, one each forui.favorites,ui.recentandui.flow.palette.recents, and each one loads its own row. The result is three requests whose filters differ only in thekeypredicate, which sits at the tail of a long encoded query string. They are three different questions, so one read per key is already met. The real duplicate case is two loads of one(user, key)at once, and the adapter's existingfindsharing already sends that as one request (pinned).userState.tsis not changed.Files
packages/app-shell/src/views/studio-design/packages-io.tspackages/app-shell/src/preview/usePendingDrafts.tspackages/data-objectstack/src/metadata-client.tspackages/data-objectstack/README.md: the Shared In-Flight Reads bullet now coversMetadataClient(patch round 1)packages-io.inflightShare-11797.test.tsx,StudioDesignSurface.entryReadsOnce-11797.test.tsx(a new test file besidepackages-io.ts;StudioDesignSurface.tsxitself is not edited),usePendingDrafts.inflightShare-11797.test.tsx,metadata-client.inflightShare-11797.test.ts,userState.inflightShare-11797.test.ts.changeset/11797-shared-inflight-reads.md: patch for@object-ui/app-shelland@object-ui/data-objectstackVerification
Round 0 ran at
c3759d6. Patch round 1 re-ran atbab3004(the final commit): new pins, 5 files / 32 tests passed; regression, the same 247-file set, 247 files / 2233 tests passed; app-shell and data-objectstack type-check exit 0. The figures below are round 0's.pnpm exec vitest runfrom the repo root):Test Files 5 passed (5),Tests 27 passed (27).455c646, and on-disk markers confirmed the swap: the hash equals the BASE blob for all 3 files.Test Files 4 failed | 1 passed (5),Tests 12 failed | 15 passed (27). Thesys_user_preferencefile stays green because it pins existing behaviour.git checkout HEAD --: the hash equals the HEAD blob for all 3 files, andgit diff HEADis empty. After the restore:Test Files 5 passed (5).MetadataClient, plus all ofpackages/data-objectstack/andStudioRoute. That is 247 files across app-shell, data-objectstack, plugin-designer, plugin-form, plugin-kanban, plugin-calendar, react and console.Test Files 247 passed (247),Tests 2228 passed (2228).pnpm testis left to CI.turbo run build --filter=@object-ui/app-shell^...: exit 0.pnpm --filter @object-ui/data-objectstack type-check: exit 0.pnpm --filter @object-ui/app-shell type-check, which runs bothtsc --noEmitandtsc -p tsconfig.test.json(the second includessrc/**/*.test.ts(x)): exit 0.pnpm exec eslinton the 8 touched source and test files: exit 0, with 0 errors and 9 warnings. All 9 warnings are on lines that were already there (anycasts inlistTypes,setStatein the effect).node scripts/check-changeset-presence.mjs:✅ 8 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s).pnpm check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s).pnpm check:control-bytes:✅ check-control-bytes: OK.check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:metadata-write-doors,check:changeset-claims,check:pending-changeset-literals,check:side-effects-array,check:unreferenced-sources,check:esm-specifiers,check:handler-key-reads,check:phantom-deps,check:self-import,scripts/check-type-check-coverage.mjs, andcheck-governed-queue-guard --test(NOT GOVERNED).check:published-dist: it builds every package including the console app, which is a repo-wide run that CI owns. A local attempt was stopped by a 300 s timeout before it printed a verdict.check:dist-completeness --allfor app-shell: app-shell itself was not built locally (only its dependency closure was). The gate reported only that unbuilt package, and no other.Acceptance notes (not fixed here, not filed)
bab3004, seat verdict6050853625). The data-objectstack README bullet "Shared In-Flight Reads" describes only the adapter'sfindandfindOne. A sentence aboutMetadataClientsharing is owed under AGENTS.md Add automated testing infrastructure and CI/CD workflows #2, but the README is outside this card's claimed file surface, so I left it for the seat.findis not dropped by writes.ObjectStackAdapter.emitMutationdrops the pendingfindOnereads of the resource it writes, but not the pendingfindreads. A list refetch asked after a write can therefore join afindthat was sent before the write. This was found by reading the code, with no public-door reproduction.ContextSelectors(the app top bar) reads its options endpoint with its ownfetch, separately fromfetchPackages. It is not part of the Studio entry path.ConnectedShelland so re-attaches the user-state bridge. That would explain why the three preference reads repeat on that navigation, but they are still one read per key.find/findOnesharing.?state=draftprobe. This PR changes only the transport underreadItemResponse(now wrapped inshareRead); the probe logic is unchanged.The implementing session is
https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU(seatdomain:ui#2).Generated by Claude Code