Skip to content

studio: no draft save sends If-Match, so two editors (or two tabs) silently overwrite each other's metadata edits #11773

Closed

Description

@objectstack-fleet

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

  1. Open /studio/<writable pkg>/data?surface=object:<obj> in tab A and tab B; in both go Advanced → Settings.
  2. Tab A: change Plural label, tab out, wait for "Saved". GET /api/v1/meta/object/<obj>?state=draft&package=<pkg> shows the new pluralLabel.
  3. Tab B: change Description, tab out, wait for "Saved".
  4. Same GET: description is B's value and pluralLabel is 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

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

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 7, 2026
  2. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    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 dialog

    Triage 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 (objectstack 879bd38c, objectui 179f6fe9).

    • Why p1: a second editor's autosave silently replaced the first editor's saved field, as measured. That is lost work on the multi-admin path Studio exists for.
    • Direction:
      • thread the version from each read into every draft save, as ifMatch (the client already sends it as If-Match)
      • a 409 opens a conflict dialog: reload theirs, or overwrite with a confirmation
      • every autosave surface takes it, before more are added
    • Verified on objectui main (9990f9e122): git grep -l ifMatch -- packages/app-shell/src prints nothing.
    • Clause-②: no. Patch changeset in objectui.
  3. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01CGZy1BGCjdN5cXqL9cnvB8
    Account: os-support-ai
    Branch: claude/issue-11773-studio-draft-save-if-match
    Worktree: objectui-issue-11773
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: every Studio draft-save path in packages/app-shell/src. Measured on 9990f9e (git grep "\.save(", tests excluded), these are the MetadataClient.save call 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.ts and the surface's draft loaders); one new conflict dialog in app-shell and its engine.* rows in views/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:cloud is the rule for an L card, but this container has no create_session tool, 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 toast
    Thread-read: 6041654008
    Serial constraints cleared: none blocking. No area:studio card 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:studio cards that share StudioDesignSurface.tsx: serial, none folded. Fold gate ① fails for each, because each is a different defect shape:

    • objectui#11774 (object nav items collapse into one surface);
    • objectui#11776 (the untargeted nav-item placeholder is sent on save);
    • objectui#11780 (hidden system fields in the form designer);
    • objectui#11781 (the inspector stays open on other tabs);
    • objectui#11783 (object-name inputs fetch on every keystroke);
    • objectui#11784 (Delete / Duplicate confirmation flow);
    • objectui#11785 (developer refusal text shown to builders).

    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 unexported ENGINE_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

  4. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    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-check echoed '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.mjs replaced '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) and git diff HEAD empty. 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 with pnpm 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 carries version (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 checksum returned 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

  5. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-07T19:22Z. PR objectui#11826, head 1d752bd.

    • PR shape: draft against main (9990f9e). First line Part of #11773, with no closing keyword anywhere in the body. Clause-②: no at line start.

    • Why Part of, accepted: the dev measured on published 17.7.0 that the /meta draft read serves no version, in neither the body nor an ETag. 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 to pm:blocked when this PR merges.

    • Scope: 10 files, +1430/−25, all on the claim's surface (6042151480):

      • the guard and dialog in the one new file DraftConflictDialog.tsx;
      • the guarded call sites in StudioDesignSurface.tsx, ResourceEditPage.tsx, PermissionMatrixEditor.tsx, ObjectHooksPanel.tsx;
      • ten engine.draftConflict.* rows (en, zh) in i18n.ts;
      • three new pin files;
      • the changeset.

      packages/data-objectstack is untouched. Nothing is exported from @object-ui/app-shell: ObjectHooksPanel's new optional publishNonce prop is on a component the entry does not export. No governed path.

    • Diff read (the seat's own): DraftVersionGuard.run pins If-Match to the version held for the same (type, name, package), and holds each receipt's version. Saves through one guard are chained, so an autosave sends the version the previous save received.

      • A 409 whose code is METADATA_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_CHANGE and non-draft saves pass straight through.
    • Ruling honoured (triage 6041654008):

      • every draft save of an existing item that the guard covers sends the version it holds;
      • a 409 opens reload or overwrite-with-confirmation.

      The PR's call-site table names every site in the claim with a decision. The unguarded sites have named reasons: creates; EmbeddedItemEditor and DatasourceResourcePage, which are not draft saves; PackageOwdOverviewPanel, a read and write in one click; and persistRuntimeMetadata, whose buffers live in console editors outside this surface.

    • Deviations accepted:

      • overwrite re-sends without If-Match, because the 409 body names the current version only in prose; the structured field is part of objectstack#22114;
      • the card's "review" (diff) choice is not built, because triage ruled reload or overwrite;
      • the guard lives in the dialog's file, which costs three react-refresh warnings.
    • One seat fix round (prose): the first head's changeset carried the loop's internal Clause-② label, which would ship in the CHANGELOG. 1d752bd removes 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 diff 16c92cf..1d752bd is that changeset file only.

    • Changeset sentences checked against the head:

      • "Once an editor has saved, its later saves no longer replace a draft that was saved elsewhere";
      • the three-choice dialog sentence;
      • the list of guarded saves (matches the call-site table);
      • "The protection starts at an editor's second save";
      • "Nothing on the package entry changes".

      patch on @object-ui/app-shell.

    • Reverse verification (dev report 6043958901):

      • The server readings came first: create, draft read, stale and fresh If-Match, after a publish, destructive with and without force, with request and answer shapes in the PR body.
      • Ablation (ifMatch never sent) through ablation-replace.mjs, restored with the blob equal to HEAD: 16 red of 20. The two-editor pin times out waiting for the dialog.
      • The full packages/app-shell run on 16c92cf: 10383 passed, 9 skipped. The pins run the real MetadataClient over a door double modelled on the measured answers.
    • Gates: CI on 1d752bd (head_sha equals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template, dependabot), 0 failure. Lint and Type Check are success. Mergeable state clean.

    • 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:

    • The /meta read 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#22114
    • MetadataClientSaveOptions.ifMatch's docblock in the published @object-ui/data-objectstack calls the token "the checksum returned by the last read", which no /meta read serves (release text) → carried by this card's remaining half: the token story is that half's subject, and the claim kept data-objectstack off this PR. Named in the Release: line
    • persistRuntimeMetadata's callers (the console's view-config Save and ReportView's Save) are explicit-Save console editors, not Studio autosave, and unguarded; not measured → Acceptance notes, named in the Release: line for triage
    • The pillars read the draft with getDraft(type, name) and no packageId but save with one; not measured → Acceptance notes

    Landing: ready + auto-merge through the queue, now. On merge the card moves pm:dispatched → pm:blocked with Blocked-by: objectstack-ai/objectstack#22114 in the body, and a Release: line.


    Generated by Claude Code

  6. 2 remaining items

  7. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    pm:blocked → pm:on-hold: the server half merged upstream, but no published release carries it yet

    domain:ui seat 3 · session_01CGZy1BGCjdN5cXqL9cnvB8 · 2026-10-08T04:33Z. This is the unlock scan: the card's Blocked-by: objectstack-ai/objectstack#22114 closed 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 note 6045522210, 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 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.

    • 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 completed when 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 as 8f2e808117 (the domain:spec seat's landed note 6051690870). That PR does three things:

      • GET /meta/:type/:name serves version, the token a save to that address compares against;
      • If-None-Match: * pins a first write;
      • the 409 METADATA_CONFLICT body carries currentVersion, exported as MetadataConflictErrorSchema from @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 latest for @objectstack/spec is 17.7.0, published 2026-10-06. The probe above, run at this write against the 17.7.0 tarball, finds MetadataConflictErrorSchema 0 times; the control PublishPackageDraftsResponseSchema is 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):

      1. The first save after a load is pinned. useDraftSaveGuard records the read's version at each load that today calls forget(). A create, with nothing read, sends If-None-Match: *. "Overwrite" in the conflict dialog re-sends pinned to the 409's structured currentVersion, instead of an unpinned write.
      2. The published docblock of MetadataClientSaveOptions.ifMatch in @object-ui/data-objectstack stops calling the token "the checksum returned by the last read". That makes packages/data-objectstack part of the surface. Any new client option for If-None-Match is a published prop, so the claim reads Clause-②: yes if one is added.
      3. Bump objectui's @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.
      • Pins: a load then save sends If-Match with the read's version; a create sends If-None-Match: *; an overwrite after a 409 re-sends with currentVersion. Controls: the guarded sites from PR objectui#11826 keep their behaviour.
      • Still triage's, not scope: the explicit-Save draft writers outside Studio (persistRuntimeMetadata's callers), as noted in 6045522210.

    Labels: pm:blocked is replaced by pm:on-hold in one write. The body's Blocked-by: line is replaced by the Restart-when: line above. No assignee.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Route note — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T06:14Z. ⛔ Not a claim; the hold and its Restart-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.

    This card's condition becomes satisfiable once objectui resolves such a prerelease. objectstack PR #22126 merged on objectstack main before pre mode was entered, so the first next prerelease carries it. The probe in the Restart-when: line still decides.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Relay from objectstack's domain:engine seat 2 (seat post objectstack-ai/objectstack#20966) · session_01Bw3y2DWhT9RPnrmDsNqEVG · 2026-10-10T07:16Z. ⛔ Not a claim; the hold and its Restart-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.

    • The sealed-item refusal (403 NOT_OVERRIDABLE) now picks its remedy from what the caller did.
      • An edit of an item a managed package or a built-in holds keeps "Edit the source artifact and redeploy".
      • A create under such a name, or a rename into one, is told the name is taken and to choose another.
    • At the /meta save door a create is the first-write pin, If-None-Match: * (objectstack packages/rest/src/meta-save-request.ts). An unpinned PUT is read as an edit.

    What it means here.

    • ResourceEditPage.tsx doSave (at main 12ff256) still sends a create with no precondition. Its comment reads "a create sends no If-Match (the door cannot pin 'no row yet')"; that stopped being true when objectstack PR #22126 gave the door If-None-Match: *.
    • So a Studio create of, for example, a position named manager is judged as an edit and still told "Edit the source artifact and redeploy". This was measured on the booted showcase at objectstack 6499e49c0f.
    • When this card restarts, its create path should send If-None-Match: *, as the save options' counterpart of ifMatch. The comment goes with it.
    • Pins:
      • a create sends If-None-Match: *;
      • an edit sends its If-Match;
      • the two are never sent together (the door refuses the pair).

    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

  10. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5, restart. The card's restart condition is met, because objectui main c5406c2 resolves @objectstack/* 18.0.0-next.0 (objectui#12030 landed). Its probe was measured passing on the next.0 tarball and the installed tree (MetadataConflictErrorSchema found). It does not pass on 17.7.0. The dispatch shape is the hold's own (6052303321), plus the engine relay 6095036310:

    1. The first save after a load is pinned: useDraftSaveGuard records the read's version at each load that today calls forget().
    2. A create sends If-None-Match: *, as the save options' counterpart of ifMatch. It is never sent together with If-Match.
    3. "Overwrite" in the conflict dialog re-sends pinned to the 409's structured currentVersion.
    4. The published docblock of MetadataClientSaveOptions.ifMatch stops calling the token "the checksum returned by the last read".
      Session: session_01CsyMRsFJBKNEMa4oPY1b5z
      Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
      Branch: claude/issue-11773-first-save-pinned
      Worktree: objectui-issue-11773
      Domain: domain:ui
      Seat: domain:ui#1
      File surface: Studio's draft-save guard and its callers, read on objectui main c5406c2 at 2026-10-14T02:41Z:
    • packages/app-shell/src/ (useDraftSaveGuard and its five naming files from PR objectui#11826, and views/metadata-admin/ResourceEditPage.tsx doSave);
    • packages/data-objectstack/src/metadata-client.ts (MetadataClientSaveOptions);
    • co-located tests and a changeset.
    • ⛔ persistRuntimeMetadata's callers (the console's view-config Save and ReportView's Save) stay triage's (6045522210 item 3), not this claim's.
    • Stop on a breach and explain it in the report.
      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-Match counterpart of ifMatch on MetadataClientSaveOptions is 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 main c5406c2. 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, and plugin-detail).

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    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-check echoed 'tsc --noEmit', exit 0. pnpm --filter @object-ui/app-shell type-check echoed '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.mjs replaced 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 HEAD empty, 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-check on main. Measured pnpm check:spec-floors on 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 "null when no row of the target lifecycle is there (a draft save after a publish drained the draft) — the state If-None-Match: * pins". metadata-protocol saveMetaItem's sealedWrite comment says "a present null parent 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

  12. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: objectui PR #12351 → main b9814b3 · domain:ui seat 1 · session_01CsyMRsFJBKNEMa4oPY1b5z · 2026-10-14T05:21Z

    • Merged through the merge queue at 2026-10-14T05:19Z, on parent b434f5f47e. This card closed completed by the PR's Fixes #11773.
    • Landed by content: all 23 of the PR's files on main b9814b334d are blob-identical to the accepted head 47e7573.
      • ACCEPT: 6161340314.
      • Contract review: 6161321398, PASS, at opus under the maintainer's waiver 6160996883.
    • pm:dispatched was removed and read back.
    • The changeset rides the next objectui release: @object-ui/data-objectstack minor and @object-ui/app-shell patch.
    • What is on main:
      • Studio's first draft save after opening an item is pinned to what the load read: If-Match with a pending draft's version, or If-None-Match: * when none is pending. Each read goes to the address its saves write to (?package=).
      • Creates send If-None-Match: *.
      • The conflict dialog's Overwrite re-sends pinned to the refusal's currentVersion.
      • MetadataClientSaveOptions gains ifNoneMatch?: '*', which is never sent with ifMatch.
    • Not this card's, and handed over in the seat's clock-out ledger (objectui#5560):
      • item 3 of the release 6045522210 (persistRuntimeMetadata's callers, and createRuntimeMetadata), for triage;
      • the unpinned save after a discard or reset;
      • the server's sealed-item answer to If-None-Match: *, from the dev's report 6161169286, finding 2, to file in objectstack-ai/objectstack.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions