Repository navigation
studio: no draft save sends If-Match, so two editors (or two tabs) silently overwrite each other's metadata edits #11773
Description
Filing gate ① — product defect with a named location and a reproduction. reach: two admin sessions editing the same object in Studio: the later autosave replaced the earlier one's saved field, verified on GET /api/v1/meta/object/<name>?state=draft.
Who acts on it: objectui triage → the Studio / app-shell owner. ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.
What happens
Studio's draft saves are last-writer-wins on the whole document. Tab A saved Plural label = Repair Tickets (A2) (the server draft showed it). Tab B, opened before A's save, then changed Description; B's autosave sent its stale copy and the server draft afterwards had no pluralLabel. Neither tab saw a 409, banner or toast. Reproduced twice.
Reproduction
- Open
/studio/<writable pkg>/data?surface=object:<obj>in tab A and tab B; in both go Advanced → Settings. - Tab A: change Plural label, tab out, wait for "Saved".
GET /api/v1/meta/object/<obj>?state=draft&package=<pkg>shows the newpluralLabel. - Tab B: change Description, tab out, wait for "Saved".
- Same GET:
descriptionis B's value andpluralLabelis gone.
Expected
B's save is refused as a conflict (the draft changed since B read it) and B is offered reload / review / overwrite.
Where it comes from (read in source)
MetadataClient already supports optimistic concurrency — its save options carry ifMatch, sent as the If-Match header — and the framework honours If-Match. A search of packages/app-shell/src for ifMatch returns no hit: no Studio save path passes the version it read.
Suggested direction (triage to rule)
Thread the version from each read into every draft save (ifMatch), and on 409 show a conflict dialog. This is the multi-admin case Studio is built for, so it is worth doing before more autosave surfaces are added.
Environment
objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.
Duplicate check
- Semantic issue search on objectstack-ai/objectui (open and closed), studio concurrent edit lost update If-Match optimistic concurrency autosave overwrite: 6 hits (0 open, 6 closed).
- Semantic issue search on objectstack-ai/objectstack (open and closed), metadata draft save If-Match etag optimistic concurrency lost update two editors: 8 hits (0 open, 8 closed).
- Semantic issue search on objectstack-ai/objectui (open and closed), MetadataClient save ifMatch never passed by studio designer two tabs overwrite: 8 hits (0 open, 8 closed).
- Related, not the same defect:
- [console] 编辑保存不发 If-Match/If-Unmodified-Since,并发编辑静默后写覆盖(数据层 409 检测是好的) #3116 (closed) — same lost-update class but on console RECORD edits, not Studio metadata drafts
- finding(app-shell): Studio's shared draft autosave drops an edit made while a save is in flight on its other callers (Automations measured; Data, page inspector) — the family closure, fix in useDraftAutoSave #11204 (closed) — same useDraftAutoSave path; intra-tab in-flight race, not cross-editor OCC
- finding(app-shell): after a Studio switch, a pillar saves a buffer that is not the target's: the load window (3 pillars), Automations / Data across a package switch, the non-editable round trip (objectui#11232 family, slice 2) #11272 (closed) — autosave writes wrong buffer; neighbouring lost-write family
- finding(plugin-form): an edit form that stays mounted sends the first-read updated_at as ifMatch on its second save, so the OCC guard would answer 409 against the user's own first save #10565 (closed) — ifMatch plumbing on plugin-form record saves (shows client supports it)
- Positive control in the same run: related lists action id refusal notice shown to end users on objectstack-ai/objectui returned related lists: an action id the related object does not define draws a refusal notice above the list for whoever opens the record — an authoring fault shown to end users; scope it to authors/admins #11768: yes.
Dedupe words: concurrent edit lost update · If-Match not sent · autosave overwrite · optimistic concurrency studio
Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.
Restart-when: an @objectstack/* release that carries objectstack-ai/objectstack PR #22126 (8f2e808117) is published on npm, and objectui resolves it. The probe cd "$(mktemp -d)" && npm pack @objectstack/spec@latest --silent && tar -xzOf objectstack-spec-*.tgz --wildcards 'package/dist/*.d.ts' 'package/dist/**/*.d.ts' | grep -q MetadataConflictErrorSchema exits 0.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 7, 2026
Path: write metadata — two builders editing one item never silently overwrite each other | 缺项 | P1
Triage: first grade,
bug·priority:p1·domain:ui·area:studio·pm:queue. Direction: every draft save sends the version it read (ifMatch), and a 409 opens a conflict dialogTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T15:57Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in the Studio draft-save paths under
packages/app-shell/src(every caller of the metadata client's save) ⇒domain:ui; rationale: a Studio or console surface in objectui. Filed from the Studio browser QA pass of 2026-10-07 (objectstack879bd38c, objectui179f6fe9).ifMatch(the client already sends it asIf-Match)main(9990f9e122):git grep -l ifMatch -- packages/app-shell/srcprints nothing.Clause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 7, 2026
Claim: PM loop round 3
Session:
session_01CGZy1BGCjdN5cXqL9cnvB8Account:
os-support-aiBranch:
claude/issue-11773-studio-draft-save-if-matchWorktree:
objectui-issue-11773Domain:
domain:uiSeat:
domain:ui#3File surface: every Studio draft-save path in
packages/app-shell/src. Measured on9990f9e(git grep "\.save(", tests excluded), these are theMetadataClient.savecall sites:views/studio-design/StudioDesignSurface.tsx:1247,:2501,:2544,:3704,:3729,:3797,:4719,:4790,:4839,:5277;views/metadata-admin/ResourceEditPage.tsx:1494;views/metadata-admin/PermissionMatrixEditor.tsx:858;views/studio-design/ObjectHooksPanel.tsx:179,:206;views/studio-design/PackageOwdOverviewPanel.tsx:271;views/metadata-admin/EmbeddedItemEditor.tsx:128;views/runtime-metadata-persistence.ts:220,:328;views/metadata-admin/datasource/DatasourceResourcePage.tsx:473.Also on the surface: the reads that must supply the version token (
views/metadata-admin/useMetadata.tsand the surface's draft loaders); one new conflict dialog in app-shell and itsengine.*rows inviews/metadata-admin/i18n.ts; the tests beside all of these;.changeset/11773-*.md. ⛔packages/data-objectstack/src/metadata-client.ts(a published package) is not on it. Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report)Container & model:
L,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier).mode:cloudis the rule for an L card, but this container has nocreate_sessiontool, so the card runs as an in-session subagent.Clause-②: no
Responsibility:
objectui app-shell: no Studio draft save passes ifMatch (git grep ifMatch -- packages/app-shell/src prints nothing), so the later of two editors silently replaces the earlier one's draft | the platform path exists: MetadataClient.save sends ifMatch as If-Match, and the /meta PUT door threads If-Match as the ADR-0008 parentVersion (objectstack packages/rest/src/rest-server.ts) | every admin who edits one metadata item in two tabs or alongside another admin; measured by the filer: the second autosave dropped the first editor's pluralLabel, with no 409, banner or toastThread-read: 6041654008
Serial constraints cleared:
noneblocking. Noarea:studiocard is in flight and no open objectui PR touches the files above (read 2026-10-07T16:25Z; open: #11600, #11069).Fold-or-serial with the queued
area:studiocards that shareStudioDesignSurface.tsx: serial, none folded. Fold gate ① fails for each, because each is a different defect shape:Any of them whose fix touches a save call site above or the version plumbing waits for this card to land. One whose surface is disjoint from this list may run alongside it.
Why
Clause-②: no: app-shell callers pass an option the published client already accepts. The conflict dialog's strings go in app-shell's unexportedENGINE_STRINGS_EN/ENGINE_STRINGS_ZH. If the fix needs a published export, prop, type member or language-pack key, or any change to@object-ui/data-objectstack, the dev reports it before opening the PR, and the seat amends this line.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
os-dev-report
{
"issue": 11773,
"status": "done",
"branch": "claude/issue-11773-studio-draft-save-if-match",
"pr": "#11826",
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8 — subagent mode, the dispatching seat's id",
"premise_still_valid": true,
"summary": "The client half is done in draft PR objectui#11826, which opens with 'Part of #11773'; the card stays open for the server half. Every Studio draft save of an existing item now goes through one guard per editing buffer (useDraftSaveGuard in the new views/metadata-admin/DraftConflictDialog.tsx). The guard sends If-Match with the version the buffer's previous save receipt carried, holds each new receipt's version, and serializes saves through one buffer, so an autosave never conflicts with that buffer's own explicit save. A 409 METADATA_CONFLICT opens a dialog: reload the saved version, overwrite after a second confirmation (re-sent without If-Match), or keep editing (not saved, and the next save is refused again). The 409 DESTRUCTIVE_CHANGE flow is untouched and is told apart by code. Measured first on published @objectstack 17.7.0 running locally: the server DOES enforce If-Match on mode=draft writes, so the stop condition did not fire. But only the save receipt serves the version: the draft read (GET ?state=draft) serves none, as a body key or as an ETag. So each editor's first save after a load cannot be pinned, and the card's literal repro (two editors who each save once) is still last-writer-wins. What changes is that the earlier editor's next save is refused with the dialog, so the loss is no longer silent. That half needs the server (finding 1). Premise: the defect and the missing client half are confirmed. The direction's 'version from each read' is false on 17.7.0.",
"tests": "Shared verify lock, slot dev-11773. HEAD 16c92cf:
pnpm --filter @object-ui/app-shell type-checkechoed 'tsc --noEmit && tsc -p tsconfig.test.json', TYPECHECK_EXIT=0.pnpm exec vitest run packages/app-shell/ --maxWorkers=3: 'Test Files 1061 passed | 1 skipped (1062)', 'Tests 10383 passed | 9 skipped (10392)', VITEST_EXIT=0. The 3 new files (DraftConflictDialog.test.tsx 12, StudioDesignSurface.draftVersionConflict-11773.test.tsx 5, ResourceEditPage.draftVersionConflict-11773.test.tsx 3): 'Test Files 3 passed (3)', 'Tests 20 passed (20)'. The doubles answer as the measured 17.7.0 door: the unit suite runs the REAL MetadataClient over a fetch double, and the other two throw refusals parsed by the real client. Ablation, after the fix was committed at 16c92cf:node ../objectstack/scripts/ablation-replace.mjsreplaced 'pinned ? { ...options, ifMatch: pinned } : options' with '{ ...options } /* ABLATED-11773: ifMatch never sent /'. Tool evidence: anchor x1 -> x0, replacement x0 -> x1, blob 866eec3fa710 -> 5e0c9f9dfa9e. In-run counts: MARKER_COUNT=1 ANCHOR_COUNT=0. Result: 'Tests 16 failed | 4 passed (20)', red as expected; the two-editor pin times out waiting for the dialog, and the 4 that stayed green do not depend on a pin. The tool proved the restore: blob == HEAD (866eec3fa710) andgit diff HEADempty. A first ablation attempt was refused by the tool before running, because its replacement 'options' was a substring of the anchor; that attempt was a no-op, restored, and is not a reading. No dist rebuild was needed: the tests import app-shell source by relative path. eslint was run on the 9 touched .ts/.tsx files: 0 errors. Per-file warnings match base or are lower (StudioDesignSurface 17 to 14). The new file carries 3 react-refresh/only-export-components warnings. That narrowing is declared: the population is the 9 files from --format json; the config is not type-aware, so untouched files' verdicts cannot move. One lock attempt ended exit 99 (queue-timeout, NOT MEASURED) and was re-run on the same slot. The dependency closure was built withpnpm turbo run build --filter='@object-ui/app-shell^...' --concurrency=2(28/28 cached). CI at report time: 42 check runs on 16c92cf. Changeset, control-byte, line-citation, governed-surface and vi.mock checks are success; Lint, Type Check and Test shards 1-8 are in_progress.","mcp_calls": "0",
"api_writes": "3 — (1) pr_create via the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as POST /repos/objectstack-ai/objectui/pulls, PR 11826; read-back of 16818 bytes was identical); (2) label-write.mjs --assign os-support-ai via the relay (POST /repos//issues/11826/assignees; the read-back matches the target); (3) this os-dev-report comment via post-stamped.mjs (POST /repos//issues/11773/comments). git push is not counted.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public HTTP door, measured on a running @objectstack 17.7.0 (
objectstack dev --fresh -p 4773, writable package com.probe.studio): GET /api/v1/meta/object/pst_ticket?state=draft&package=com.probe.studio answers {type, name, sortability, item} with NO version key and NO ETag. Only the save receipt carriesversion(hmac-sha256:…), so no client can pin the first draft save after a read, and a PUT ?mode=draft without If-Match is last-writer-wins. Two more measured gaps on the same door: (i) any If-Match while no draft row exists answers 409 METADATA_CONFLICT 'current is null', and the header cannot express 'expect no draft', so creates and the first draft after a publish are unpinnable; (ii) the 409 METADATA_CONFLICT body is {error, code} only, with the current version only inside the sentence (the protocol's metadataConflictRefusal sets actualHead on the error, and the REST door does not serialize it). · Seam: spec:GetMetaItemResponseSchema (no version member) → runtime:rest-server GET /meta/:type/:name draft branch + PUT door error envelope · repo: objectstack-ai/objectstack · relation: the remaining half of objectui#11773 is Blocked-by this (a standalone card with a Blocked-by line, filed in objectstack) · dedupe words: meta item read version token · draft read ETag If-Match · If-None-Match expect no draft · METADATA_CONFLICT actualHead body","class: b · reach: exception: release-text — the docblock ships in @object-ui/data-objectstack's published declarations · evidence: MetadataClientSaveOptions.ifMatch says it is 'the
checksumreturned by the last read'. On 17.7.0 no /meta read returns a checksum (measured, see finding 1), and the door's own contract comment says callers set If-Match to 'the version token a receipt served'. An author following the docblock looks for a read key that does not exist · Seam: spec:SaveMetaItemResponse.version → runtime:MetadataClient.save (ifMatch docblock) · consumer: app-shell useDraftSaveGuard · the brief put data-objectstack off this surface, so it is untouched · dedupe words: ifMatch docblock checksum · MetadataClientSaveOptions ifMatch token","carrier: objectui#11773 itself, which stays open via 'Part of' · runtime-metadata-persistence persistRuntimeMetadata is a draft save of an existing view/report and is NOT guarded. Its buffers live in ObjectView's view-config Save and ReportView's Save, both outside this card's file surface. Same defect class, explicit-Save console editors, not Studio autosave. Noted in the PR's call-site table, not filed.",
"carrier: 承接者:无 · the Studio pillars read the draft with getDraft(type, name) and no packageId, while they save with the package. Not measured. Noted in PR Acceptance notes only."
],
"gates": [
{
"command": "pnpm --filter @object-ui/app-shell type-check",
"exit": 0,
"verdict": "TYPECHECK_EXIT=0 (tsc --noEmit && tsc -p tsconfig.test.json) at 16c92cf"
},
{
"command": "pnpm exec vitest run packages/app-shell/ --maxWorkers=3",
"exit": 0,
"verdict": "Test Files 1061 passed | 1 skipped (1062); Tests 10383 passed | 9 skipped (10392) at 16c92cf"
},
{
"command": "pnpm exec eslint (9 touched .ts/.tsx files)",
"exit": 0,
"verdict": "0 errors, 93 warnings; per-file at or below base, plus 3 react-refresh warnings on the new file"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7783 tracked text file(s); skipped 85 binary)."
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"verdict": "check-test-path-roots: OK"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"command": "pnpm check:i18n-keys",
"exit": 0,
"verdict": "Every in-scope call-site key resolves against the en pack (3297 keys) …"
},
{
"command": "pnpm check:i18n-drift",
"exit": 0,
"verdict": "No designer-table en value changed in this range. (10 key(s) added)"
},
{
"command": "pnpm check:i18n-designer-parity",
"exit": 0,
"verdict": "Every en row has a zh row, and every shared row carries the same placeholders."
},
{
"command": "pnpm check:i18n-dead-keys (report)",
"exit": 0,
"verdict": "no engine.draftConflict. key listed"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
},
{
"command": "pnpm check:vi-mock-specifiers",
"exit": 0,
"verdict": "check-vi-mock-specifiers: OK"
},
{
"command": "pnpm check:vi-mock-inherit",
"exit": 0,
"verdict": "check-vi-mock-inherit: OK"
},
{
"command": "pnpm check:vi-mock-override-shape",
"exit": 0,
"verdict": "check-vi-mock-override-shape: OK"
},
{
"command": "pnpm check:metadata-write-doors",
"exit": 0,
"verdict": "OK 17 metadata write door(s) derived (3 raw PUT, 14 SDK) …"
},
{
"command": "pnpm check:unreferenced-sources",
"exit": 0,
"verdict": "OK Every shipped source file in every covered package is reachable."
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "9 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11773-studio-draft-save-if-match.md."
},
{
"command": "check:readme-exports, check:i18n parity for locale packs",
"exit": null,
"verdict": "not applicable: no package export and no locale pack changed"
}
],
"deviations": [
"PR first line is 'Part of #11773', not the brief's closing line. The card's Expected (B's FIRST save refused) is unreachable client-side on 17.7.0, because the draft read serves no version (finding 1). If the seat wants a closing line, re-word the PR body; the dev writes it only once.",
"Measured against the PUBLISHED @objectstack/cli 17.7.0, installed with npm into a scratch directory and run with
objectstack dev --seed-admin --fresh --no-watch -p 4773. No objectstack checkout was built or edited. The probe server was stopped by its recorded PID, and the 614 MB install was removed.","ObjectHooksPanel gained an optional internal prop
publishNonce, so a package publish forgets the hook editor's version. The component is not exported from the @object-ui/app-shell entry, so the public surface is unchanged.","The guard logic sits in the one new file the claim allows (DraftConflictDialog.tsx) instead of a separate .ts module. That costs 3 react-refresh/only-export-components warnings and adds no file outside the claimed surface.",
"The dialog offers reload / overwrite / keep editing. The 'review' (diff) option in the card's Expected was not built; the triage direction ruled reload or overwrite.",
"Overwrite re-sends WITHOUT If-Match, not with 'the server's current token' (Zone 3). The 409 body names that token only in prose, and the guard does not parse prose.",
"git push answered 'remote rejected (Internal Server Error)' 5 times in a row for the first commit, then succeeded on a later retry. Read as a network or server error, not a 403.",
"The ablation's first attempt was a tool-refused no-op (see tests). The second is the reading."
],
"files_changed": [
".changeset/11773-studio-draft-save-if-match.md",
"packages/app-shell/src/views/metadata-admin/DraftConflictDialog.tsx",
"packages/app-shell/src/views/metadata-admin/DraftConflictDialog.test.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.draftVersionConflict-11773.test.tsx",
"packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.draftVersionConflict-11773.test.tsx",
"packages/app-shell/src/views/studio-design/ObjectHooksPanel.tsx"
],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
ACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-07T19:22Z. PR objectui#11826, head1d752bd.PR shape: draft against
main(9990f9e). First linePart of #11773, with no closing keyword anywhere in the body.Clause-②: noat line start.Why
Part of, accepted: the dev measured on published 17.7.0 that the/metadraft read serves no version, in neither the body nor anETag. Only the save receipt does. So the card's Expected (B's first save is refused) cannot be reached client-side, and triage's "thread the version from each read" is false on 17.7.0. The server half is filed as meta(/meta door): the draft read serves no version token, so no client can pin its first draft save with If-Match; the 409 METADATA_CONFLICT body names the current version only in prose objectstack#22114. This card stays open for it and moves topm:blockedwhen this PR merges.Scope: 10 files, +1430/−25, all on the claim's surface (
6042151480):DraftConflictDialog.tsx;StudioDesignSurface.tsx,ResourceEditPage.tsx,PermissionMatrixEditor.tsx,ObjectHooksPanel.tsx;engine.draftConflict.*rows (en, zh) ini18n.ts;packages/data-objectstackis untouched. Nothing is exported from@object-ui/app-shell:ObjectHooksPanel's new optionalpublishNonceprop is on a component the entry does not export. No governed path.Diff read (the seat's own):
DraftVersionGuard.runpinsIf-Matchto the version held for the same (type, name, package), and holds each receipt'sversion. Saves through one guard are chained, so an autosave sends the version the previous save received.409whose code isMETADATA_CONFLICT(read off the parsed error, never the prose) asks the author. Reload bumps an epoch, so a queued save of the replaced buffer is dropped. Overwrite re-sends unpinned. Keep editing throws "not saved" and keeps the stale version.DESTRUCTIVE_CHANGEand non-draft saves pass straight through.Ruling honoured (triage
6041654008):The PR's call-site table names every site in the claim with a decision. The unguarded sites have named reasons: creates;
EmbeddedItemEditorandDatasourceResourcePage, which are not draft saves;PackageOwdOverviewPanel, a read and write in one click; andpersistRuntimeMetadata, whose buffers live in console editors outside this surface.Deviations accepted:
If-Match, because the 409 body names the current version only in prose; the structured field is part of objectstack#22114;react-refreshwarnings.One seat fix round (prose): the first head's changeset carried the loop's internal
Clause-②label, which would ship in the CHANGELOG.1d752bdremoves it. The dev also corrected three sentences the diff did not support: the headline now promises protection from an editor's second save, "Every" is now "Each guarded", and the Interfaces autosave is named for every item it edits. The diff16c92cf..1d752bdis that changeset file only.Changeset sentences checked against the head:
patchon@object-ui/app-shell.Reverse verification (dev report
6043958901):If-Match, after a publish, destructive with and withoutforce, with request and answer shapes in the PR body.ifMatchnever sent) throughablation-replace.mjs, restored with the blob equal to HEAD: 16 red of 20. The two-editor pin times out waiting for the dialog.packages/app-shellrun on16c92cf: 10383 passed, 9 skipped. The pins run the realMetadataClientover a door double modelled on the measured answers.Gates: CI on
1d752bd(head_shaequals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template,dependabot), 0 failure.LintandType Checkare success. Mergeable stateclean.Contract review: not required (
Clause-②: no; no export and no language-pack key; the strings are rows of app-shell's unexported designer table).Findings:
/metaread serves no version token, "expect no draft" cannot be sent, and the 409 body has no structured current version (class a, measured on a running 17.7.0) → filed meta(/meta door): the draft read serves no version token, so no client can pin its first draft save with If-Match; the 409 METADATA_CONFLICT body names the current version only in prose objectstack#22114MetadataClientSaveOptions.ifMatch's docblock in the published@object-ui/data-objectstackcalls the token "thechecksumreturned by the last read", which no/metaread serves (release text) → carried by this card's remaining half: the token story is that half's subject, and the claim keptdata-objectstackoff this PR. Named in theRelease:linepersistRuntimeMetadata's callers (the console's view-config Save andReportView's Save) are explicit-Save console editors, not Studio autosave, and unguarded; not measured →Acceptance notes, named in theRelease:line for triagegetDraft(type, name)and nopackageIdbut save with one; not measured →Acceptance notesLanding: ready + auto-merge through the queue, now. On merge the card moves
pm:dispatched→pm:blockedwithBlocked-by: objectstack-ai/objectstack#22114in the body, and aRelease:line.Generated by Claude Code
2 remaining items
objectstack-fleet commentedon Oct 8, 2026
pm:blocked→pm:on-hold: the server half merged upstream, but no published release carries it yetdomain:uiseat 3 ·session_01CGZy1BGCjdN5cXqL9cnvB8· 2026-10-08T04:33Z. This is the unlock scan: the card'sBlocked-by: objectstack-ai/objectstack#22114closed at 2026-10-08T03:39Z. ⛔ Not a claim. The reason is the cross-repo unlock rule: the criterion is that the consumer can install the fix, not that it merged upstream. The source is this seat's release note6045522210, which wrote the same criterion.Restart-when: an
@objectstack/*release that carries objectstack-ai/objectstack PR #22126 (8f2e808117) is published on npm, and objectui resolves it. The probecd "$(mktemp -d)" && npm pack @objectstack/spec@latest --silent && tar -xzOf objectstack-spec-*.tgz --wildcards 'package/dist/*.d.ts' 'package/dist/**/*.d.ts' | grep -q MetadataConflictErrorSchemaexits 0.Upstream closed. meta(/meta door): the draft read serves no version token, so no client can pin its first draft save with If-Match; the 409 METADATA_CONFLICT body names the current version only in prose objectstack#22114 closed
completedwhen PR feat(meta): the /meta item read serves the version token, If-None-Match: * pins a first write, and the 409 carries currentVersion objectstack#22126 merged as8f2e808117(thedomain:specseat's landed note6051690870). That PR does three things:GET /meta/:type/:nameservesversion, the token a save to that address compares against;If-None-Match: *pins a first write;METADATA_CONFLICTbody carriescurrentVersion, exported asMetadataConflictErrorSchemafrom@objectstack/spec/api.Its own commit message says this card "is unlocked by the published release that carries this change, not by this merge".
Not installable yet. npm's
latestfor@objectstack/specis 17.7.0, published 2026-10-06. The probe above, run at this write against the 17.7.0 tarball, findsMetadataConflictErrorSchema0 times; the controlPublishPackageDraftsResponseSchemais found 4 times. The release is the maintainer's act. Re-derived, there is no other blocker.The dispatch shape on restart (the remainder in
6045522210, written now so the waking seat does not re-derive it):useDraftSaveGuardrecords the read'sversionat each load that today callsforget(). A create, with nothing read, sendsIf-None-Match: *. "Overwrite" in the conflict dialog re-sends pinned to the 409's structuredcurrentVersion, instead of an unpinned write.MetadataClientSaveOptions.ifMatchin@object-ui/data-objectstackstops calling the token "thechecksumreturned by the last read". That makespackages/data-objectstackpart of the surface. Any new client option forIf-None-Matchis a published prop, so the claim readsClause-②: yesif one is added.@objectstack/*resolution to the release that carries the fix, if the lockfile does not already resolve it, with the spec-range floor raised where a published objectui package imports a new symbol.If-Matchwith the read's version; a create sendsIf-None-Match: *; an overwrite after a 409 re-sends withcurrentVersion. Controls: the guarded sites from PR objectui#11826 keep their behaviour.persistRuntimeMetadata's callers), as noted in6045522210.Labels:
pm:blockedis replaced bypm:on-holdin one write. The body'sBlocked-by:line is replaced by theRestart-when:line above. No assignee.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Route note —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T06:14Z. ⛔ Not a claim; the hold and itsRestart-when:stay as written.This card's restart condition asks for an
@objectstack/*release carrying objectstack PR #22126, resolved by objectui. There will be no 17.x release: the maintainer, verbatim, 「17.x 不会发新版」. The maintainer then ruled 「让 objectui 提前改用 next 预发布版」, and the move is filed as objectui#12030.18.0.0-next.Nis on npm undernext.Validate the post-version tree, so #21988 never refreshes into 18.0.0-next.0 — wire the lane's major-only gates in, and stop prerelease cuts re-dating the last GA objectstack#22085 and spec(v18): move PROTOCOL_VERSION 17 → 18 in an ordinary pull request with full CI: regenerate spec-changes.json and the 17 → 18 upgrade-guide section, rewrite the in-repo ^17 handshakes, and give the lockstep test one pre-mode exception (#22085 Q1) objectstack#22130.This card's condition becomes satisfiable once objectui resolves such a prerelease. objectstack PR #22126 merged on objectstack
mainbefore pre mode was entered, so the firstnextprerelease carries it. The probe in theRestart-when:line still decides.Generated by Claude Code
fix(app-shell): Studio draft saves send the version they were built o…
feat(meta): the /meta item read serves the version token, If-None-Mat…
objectstack-fleet commentedon Oct 10, 2026
Relay from objectstack's
domain:engineseat 2 (seat post objectstack-ai/objectstack#20966) ·session_01Bw3y2DWhT9RPnrmDsNqEVG· 2026-10-10T07:16Z. ⛔ Not a claim; the hold and itsRestart-when:stay as written. This is a second reason for the create half of this card, under the cross-repo rule: the client half of a landed server change.What landed in objectstack: objectstack-ai/objectstack#22591, PR #22623, merged as
ad1495796e.403 NOT_OVERRIDABLE) now picks its remedy from what the caller did./metasave door a create is the first-write pin,If-None-Match: *(objectstackpackages/rest/src/meta-save-request.ts). An unpinnedPUTis read as an edit.What it means here.
ResourceEditPage.tsxdoSave(atmain12ff256) still sends a create with no precondition. Its comment reads "a create sends noIf-Match(the door cannot pin 'no row yet')"; that stopped being true when objectstack PR #22126 gave the doorIf-None-Match: *.manageris judged as an edit and still told "Edit the source artifact and redeploy". This was measured on the booted showcase at objectstack6499e49c0f.If-None-Match: *, as the save options' counterpart ofifMatch. The comment goes with it.If-None-Match: *;If-Match;The same pin also stops a create from overwriting a row that already exists (
412). That is this card's own concern, for the create half.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 5, restart. The card's restart condition is met, because objectui
mainc5406c2resolves@objectstack/*18.0.0-next.0(objectui#12030 landed). Its probe was measured passing on thenext.0tarball and the installed tree (MetadataConflictErrorSchemafound). It does not pass on17.7.0. The dispatch shape is the hold's own (6052303321), plus the engine relay6095036310:useDraftSaveGuardrecords the read'sversionat each load that today callsforget().If-None-Match: *, as the save options' counterpart ofifMatch. It is never sent together withIf-Match.currentVersion.MetadataClientSaveOptions.ifMatchstops calling the token "thechecksumreturned by the last read".Session:
session_01CsyMRsFJBKNEMa4oPY1b5zAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-11773-first-save-pinnedWorktree:
objectui-issue-11773Domain:
domain:uiSeat:
domain:ui#1File surface: Studio's draft-save guard and its callers, read on objectui
mainc5406c2at 2026-10-14T02:41Z:packages/app-shell/src/(useDraftSaveGuardand its five naming files from PR objectui#11826, andviews/metadata-admin/ResourceEditPage.tsxdoSave);packages/data-objectstack/src/metadata-client.ts(MetadataClientSaveOptions);persistRuntimeMetadata's callers (the console's view-config Save andReportView's Save) stay triage's (6045522210item 3), not this claim's.Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: yes
Clause-② reason: an
If-None-Matchcounterpart ofifMatchonMetadataClientSaveOptionsis a new published option of@object-ui/data-objectstack. A contract review is owed before the queue.Responsibility:
@object-ui/app-shell's Studio draft-save guard and @object-ui/data-objectstack's save options (the first save after a load is unpinned; a create is read as an edit) | none | two Studio editors (or tabs) on one item, and any Studio create under a sealed name; measured in a browser on 2026-10-07 and on the booted showcase (6095036310)Thread-read: 6095036310
Serial constraints cleared: read at 2026-10-14T02:41Z on objectui
mainc5406c2. No open PR touches these files. This seat dispatches objectui#12270 and [finding] A record:details section heading is static, so it cannot say how many values a field holds (「N 项功能需要更高套餐 · 查看」) #12192 in the same round on disjoint surfaces (plugin-list/plugin-grid/InterfaceListPage, andplugin-detail).Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 11773,
"status": "done",
"branch": "claude/issue-11773-first-save-pinned",
"pr": "#12351",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z — subagent mode, the dispatching seat's id",
"premise_still_valid": true,
"summary": "Draft PR objectui#12351 (head 47e7573, line 1 'Fixes #11773', line 2 'Clause-②: yes'). Each Studio draft-save load now reads the draft at the address its saves write (?package=) and records what that read served: If-Match with the draft's version, or If-None-Match: * when no draft is pending. A read that failed, or a server that serves no version, pins nothing. Every create in the guard's caller files sends If-None-Match: . The conflict dialog's Overwrite re-sends pinned to the 409's currentVersion, parsed by the spec's MetadataConflictErrorSchema. @object-ui/data-objectstack gains MetadataClientSaveOptions.ifNoneMatch?: '', never sent beside ifMatch (save() throws), and the ifMatch docblock no longer names 'the checksum returned by the last read'. Measured first on the published 18.0.0-next.0 server: a draft read that names no package serves a package-bound draft with version: null. That is why the Studio pillars, the list-view panel and the hooks panel now read at the package they save to: pinned with that null, the first save of every never-published item would be refused as a false conflict.",
"tests": "Heavy runs went through the shared verify lock, slot ui1-11773-dev, with exit codes captured before any pipe. At 47e7573:
pnpm --filter @object-ui/data-objectstack type-checkechoed 'tsc --noEmit', exit 0.pnpm --filter @object-ui/app-shell type-checkechoed 'tsc --noEmit && tsc -p tsconfig.test.json', exit 0.pnpm exec vitest run --maxWorkers=2 packages/app-shell/: 'Test Files 1332 passed | 1 skipped (1333)', 'Tests 12814 passed | 9 skipped (12823)', exit 0, and it held the lock 50m37s. The 13 app-shell test files this branch adds or edits: 'Test Files 13 passed (13)', 'Tests 158 passed (158)'. At cff84f1, with that package unchanged since:pnpm exec vitest run --maxWorkers=2 packages/data-objectstack/: 'Test Files 89 passed (89)', 'Tests 1141 passed (1141)'. New pins: DraftConflictDialog.test (rewritten against the 18 door), ResourceEditPage.draftVersionConflict-11773 (+3), ResourceEditPage.createFirstWritePin-11773 (real client, create mode), StudioDesignSurface.draftVersionConflict-11773 (+2: the card's race, a pending-draft load), PermissionMatrixEditor.firstSavePinned-11773, ObjectListViewInspector.firstSavePinned-11773, ObjectHooksPanel.firstSavePinned-11773, metadata-client.firstWritePin-11773. Six existing suites that asserted a create's or a first save's exact options now carry the pin. Ablation, after the fix was committed at cff84f1:node ../objectstack/scripts/ablation-replace.mjsreplaced the Data pillar load's "recordObjRead('object', current.name, packageId, draftRead.pin);" with "recordObjRead('object', current.name, packageId, null); /* ABLATED-11773 … /", which is what forget() did. Tool evidence: anchor x1 -> x0, blob 188311f94248 -> 5b81c98db7b2. Result: 'Tests 5 failed | 2 passed (7)', red as expected. The red five: one editor's first save, the card's race, the pending-draft load, the next save after reload, and the create's first edit after its load. The two that stayed green never depend on the load's pin. The tool proved the restore: blob == HEAD (188311f94248),git diff HEADempty, marker count 0 on disk. No dist rebuild was needed, because the suite imports app-shell source by relative path. The probe readings (published @objectstack/cli 18.0.0-next.0, curl against /api/v1/meta) are in the PR body's table.","mcp_calls": "0",
"api_writes": "2 relay strokes, both through scripts/pm write tools (fleet-write, executed as objectstack-fleet[bot]). (1) pr_create: POST /repos/objectstack-ai/objectstack/dispatches, executed as POST /repos/objectstack-ai/objectui/pulls (draft, PR 12351) and POST /repos//issues/12351/assignees. The read-back was 17254 bytes sent and 17254 stored, identical, with assignee zhuangjianguo. (2) This os-dev-report comment: post-stamped.mjs → POST /repos//issues/11773/comments. git push is not counted. No label write.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: the nightly 'Spec Range Floors' workflow (.github/workflows/spec-range-floors.yml, schedule) runs
pnpm check:spec-floors -- --cross-checkon main. Measuredpnpm check:spec-floorson this branch, whose manifests are byte-identical to main c5406c2 (no package.json in the diff): exit 1, 'The scan collapsed: 0 package(s) declare @objectstack/spec in a consumer-facing field, fewer than the 8 this repository has.' · evidence: effectiveFloor({dependencies:{'@objectstack/spec':'^18.0.0-next.0'}}) throws 'range "^18.0.0-next.0" contains "^18.0.0-next.0", which this gate does not read'. Every manifest has carried that range since objectui#12030, so each package lands in unreadable-range and floored stays 0. Control: '^17.7.0' reads as floor 17.7.0 · dedupe words: spec range floors prerelease · scan collapsed 0 packages · check-spec-range-floors next prerelease range · minVersionOf prerelease","class: b · reach: public HTTP door, measured on the published @objectstack 18.0.0-next.0 (objectstack dev --fresh). PUT /api/v1/meta/object/occ_ticket?mode=draft, where the object is shipped by the app's code package (layers: editable false, lock full). Unpinned, the answer is 403 NOT_OVERRIDABLE '… Edit the source artifact and redeploy.' With If-None-Match: , it is 403 NOT_OVERRIDABLE '… so its name is taken. To author your own object, choose a name no package or built-in holds …'. A refused first-write pin over an existing draft also answers '… has been modified since you loaded it. Expected parent null but current is …' · evidence: the spec's MetadataConflictErrorSchema docblock says currentVersion is "
nullwhen no row of the target lifecycle is there (a draft save after a publish drained the draft) — the stateIf-None-Match: *pins". metadata-protocol saveMetaItem's sealedWrite comment says "a presentnullparent is the first-write pin … the caller holds no row under the name and is writing one of its own, a create". The two readings disagree for the first draft over an existing item. The Studio editors gate sealed items read-only, so no first-party editor reaches the remedy today · Seam: spec:SaveMetaItemRequestSchema.parentVersion (null) / MetadataConflictErrorSchema → runtime:metadata-protocol saveMetaItem sealedWrite → refusePackagedBaseOverride / tenantAuthoredWriteRefusal · repo: objectstack-ai/objectstack · dedupe words: If-None-Match first draft create remedy · sealedWrite parentVersion null · NOT_OVERRIDABLE name taken draft after publish · first-write pin lifecycle vs name","carrier: this card's triage item 3 on persistRuntimeMetadata's callers (release 6045522210), which owns the file · runtime-metadata-persistence createRuntimeMetadata is a draft create that still sends no precondition. It sits outside this claim's surface, beside persistRuntimeMetadata · noted in PR Acceptance notes, not filed",
"carrier: 承接者:无 · ResourceEditPage doDiscardDraft / doReset leave the next save unpinned. No draft read follows them, and the discard names no package, so 'no draft row' is not known at the save's address · noted in PR Acceptance notes only"
],
"gates": [
{
"command": "pnpm exec turbo run build --filter=@object-ui/app-shell^... --filter=@object-ui/data-objectstack^... --concurrency=2",
"exit": 0,
"verdict": "Tasks: 28 successful, 28 total (9 cached); rebuilt data-objectstack dist/index.d.ts carries ifNoneMatch"
},
{
"command": "pnpm --filter @object-ui/data-objectstack type-check",
"exit": 0,
"verdict": "tsc --noEmit, at 47e7573"
},
{
"command": "pnpm --filter @object-ui/app-shell type-check",
"exit": 0,
"verdict": "tsc --noEmit && tsc -p tsconfig.test.json, at 47e7573"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 packages/app-shell/",
"exit": 0,
"verdict": "Test Files 1332 passed | 1 skipped (1333); Tests 12814 passed | 9 skipped (12823), at 47e7573"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 (the 13 app-shell test files this branch adds or edits)",
"exit": 0,
"verdict": "Test Files 13 passed (13); Tests 158 passed (158), at 47e7573"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 packages/data-objectstack/",
"exit": 0,
"verdict": "Test Files 89 passed (89); Tests 1141 passed (1141), at cff84f1 (package unchanged since)"
},
{
"command": "ablation-replace.mjs on the Data pillar load's recordObjRead -- vitest StudioDesignSurface.draftVersionConflict-11773",
"exit": 1,
"verdict": "Tests 5 failed | 2 passed (7) — the expected red; restore proven blob == HEAD 188311f94248"
},
{
"command": "pnpm exec eslint --format json (21 touched .ts/.tsx files)",
"exit": 0,
"verdict": "0 errors, 105 warnings. Per file, equal to base read via git show c5406c2:FILE | eslint --stdin, except DraftConflictDialog.tsx 3 -> 5 (react-refresh/only-export-components on draftPinOf, readDraftForSave). The config is not type-aware, so the narrowing moves no untouched file"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "21 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/11773-first-save-pinned.md."
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 8649 tracked text file(s); skipped 85 binary)."
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"verdict": "check-test-path-roots: OK"
},
{
"command": "pnpm check:vi-mock-specifiers",
"exit": 0,
"verdict": "check-vi-mock-specifiers: OK"
},
{
"command": "pnpm check:vi-mock-inherit",
"exit": 0,
"verdict": "check-vi-mock-inherit: OK"
},
{
"command": "pnpm check:vi-mock-override-shape",
"exit": 0,
"verdict": "check-vi-mock-override-shape: OK"
},
{
"command": "pnpm check:metadata-write-doors",
"exit": 0,
"verdict": "OK 15 metadata write door(s) derived (3 raw PUT, 12 SDK) …"
},
{
"command": "pnpm check:spec-symbols",
"exit": 0,
"verdict": "nothing cites a key its spec symbol does not declare"
},
{
"command": "pnpm check:unreferenced-sources",
"exit": 0,
"verdict": "OK Every shipped source file in every covered package is reachable."
},
{
"command": "pnpm check:doc-fences / check:doc-types / check:doc-json / check:doc-example-readers",
"exit": 0,
"verdict": "each OK"
},
{
"command": "pnpm check:doc-snippets",
"exit": 2,
"verdict": "NOT MEASURED: PRECONDITION NOT MET (packages outside this closure unbuilt). The new README block was type-checked with tsc --ignoreConfig --strict against the rebuilt .d.ts instead: exit 0, and the control ifNoneMatch: 'nope' gives exit 2 (TS2345)"
},
{
"command": "pnpm check:readme-exports",
"exit": 1,
"verdict": "NOT MEASURED: every refusal is a README of a package with no dist/ on disk ('run pnpm build first'). None names data-objectstack"
},
{
"command": "pnpm check:spec-floors",
"exit": 1,
"verdict": "NOT MEASURED: 'The scan collapsed' on main's prerelease ranges, with the input untouched by this diff (finding 1)"
},
{
"command": "check:i18n-",
"exit": null,
"verdict": "not applicable: no string added"
}
],
"deviations": [
"File surface: packages/data-objectstack/README.md gains a section on the two preconditions. AGENTS.md #2 asks for docs with a feature, and the claim's list did not name it. Every other path is on the claim: the guard file, its five caller files, metadata-client.ts, co-located tests, and the changeset.",
"The Studio pillars (object, flow, leaf, app nav), the list-view panel and the hooks panel now read the draft at ?package=, the package they save to. They read with no package before. This is a read-side change beyond 'record the read's version', and it is needed: measured, a package-less draft read of a package-bound draft serves version: null, which would turn every first save of a never-published item into a false conflict. Display changes only for an item whose only draft is bound to a third package.",
"The guard holds one pin per item (type, name, package) instead of a single one, so the hooks panel can keep each hook's pin. For every other editor, which holds one item at a time, behaviour is the same.",
"The hooks panel's list load now records pins. It never called forget() at a load, so the hold's shape did not name it; it is included under triage's 'every autosave surface takes it'.",
"Overwrite falls back to an unpinned re-send when the 409 body carries no currentVersion (MetadataConflictErrorSchema.safeParse fails), as before. There is no prose parser.",
"ResourceEditPage doDiscardDraft / doReset keep forget(): no draft read follows them (see out_of_scope_findings).",
"+2 react-refresh/only-export-components warnings on DraftConflictDialog.tsx, the same class round 1 accepted.",
"check:spec-floors, check:doc-snippets and check:readme-exports are NOT MEASURED, with reasons in gates.",
"Two lock attempts ended exit 99 queue-timeout (NOT MEASURED) and were re-run on the same slot. The final hold of the shared lock lasted 50m37s, for the full app-shell suite at --maxWorkers=2 on this 4-core box.",
"Measurement: the published @objectstack/cli 18.0.0-next.0 was installed with npm into the session scratch directory (658 MB) and booted on port 4773. It was stopped by its recorded PID, and the child was confirmed gone. The install is removed at cleanup. No objectstack checkout was built or edited."
],
"files_changed": [
".changeset/11773-first-save-pinned.md",
"packages/app-shell/src/views/metadata-admin/DraftConflictDialog.test.tsx",
"packages/app-shell/src/views/metadata-admin/DraftConflictDialog.tsx",
"packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.firstSavePinned-11773.test.tsx",
"packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.createFirstWritePin-11773.test.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.draftVersionConflict-11773.test.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx",
"packages/app-shell/src/views/studio-design/ObjectHooksPanel.designerRegistryMissing.test.tsx",
"packages/app-shell/src/views/studio-design/ObjectHooksPanel.firstSavePinned-11773.test.tsx",
"packages/app-shell/src/views/studio-design/ObjectHooksPanel.tsx",
"packages/app-shell/src/views/studio-design/ObjectListViewInspector.firstSavePinned-11773.test.tsx",
"packages/app-shell/src/views/studio-design/ObjectListViewInspector.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.createAppOpensInterfaces-11794.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.draftVersionConflict-11773.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacesCreate-11823.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacesCreatePage-11823.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.listViewInspector-11823.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.sharedSelect-11865.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
"packages/data-objectstack/README.md",
"packages/data-objectstack/src/metadata-client.firstWritePin-11773.test.ts",
"packages/data-objectstack/src/metadata-client.ts"
],
"line_budget": "not applicable (no skills/* surface)"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Landed: objectui PR #12351 →
mainb9814b3·domain:uiseat 1 ·session_01CsyMRsFJBKNEMa4oPY1b5z· 2026-10-14T05:21Zb434f5f47e. This card closedcompletedby the PR'sFixes #11773.mainb9814b334dare blob-identical to the accepted head47e7573.6161340314.6161321398, PASS, at opus under the maintainer's waiver6160996883.pm:dispatchedwas removed and read back.@object-ui/data-objectstackminorand@object-ui/app-shellpatch.main:If-Matchwith a pending draft's version, orIf-None-Match: *when none is pending. Each read goes to the address its saves write to (?package=).If-None-Match: *.currentVersion.MetadataClientSaveOptionsgainsifNoneMatch?: '*', which is never sent withifMatch.6045522210(persistRuntimeMetadata's callers, andcreateRuntimeMetadata), for triage;If-None-Match: *, from the dev's report6161169286, finding 2, to file in objectstack-ai/objectstack.Generated by Claude Code