You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
studio(flow designer): the entry-condition builder reads the object's published metadata, so for a draft-only object it offers no fields — only previous #11895
Filing gate ① — product defect with a named location and a reproduction. reach: measured in the Studio task benchmark baseline (objectstack-ai/objectstack#22180, step 5): with the object repair_ticket still an unpublished draft, Studio → Automations → Start node Record updated on it → Entry condition builder lists only previous as a subject; the network log shows GET /api/v1/meta/object/repair_ticket → 404 while the draft exists. The author had to switch to Expression mode and type CEL (baseline code-shaped entry 8).
Who acts on it: objectui triage → the Studio owner. ⛔ Not a claim. Found by the cloud executor of objectstack-ai/objectstack#22137 and filed by the dispatching seat from its out_of_scope_findings after an objectui dedupe the executor could not run.
What happens
Building an app in Studio naturally means authoring the automation before the first publish. At that point every field the author just created is invisible to the flow's condition builder, because the builder asks for the published object.
Where it comes from (read in source)
useObjectFields (app-shell/src/views/metadata-admin/previews/useObjectFields.ts) loads fields with client.get('object', objectName) — no draft state, no package — and both ConditionBuilder and FlowNodeConfigField read their field lists through it. A draft-only object answers 404, so the list is empty (objectui main82500a7).
Expected
Authoring surfaces read the draft overlay of the package being edited (the state=draft + package read the Data pillar already uses), so a draft object's fields are offered before its first publish.
Studio task benchmark baseline run, 2026-10-08: objectstack 8fc50b76, objectui a58626c8 (console built at the .objectui-sha pin), fresh showcase boot, Chromium 141 at 1440×900; source re-read on objectui main82500a7.
Semantic issue search on objectstack-ai/objectui (open and closed), Studio flow designer reads the published object not the draft, condition fields missing for unpublished object: 16 hits; none describes the condition builder's field source.
Dedupe words: flow entry condition builder draft object fields · condition subject list only previous · flow designer reads published object not draft
Evidence moved here from objectstack-ai/objectstack#22191. The maintainer asked for this defect to be filed (useObjectFields reads only the published object). The session that filed it could not search this repository. objectstack-ai/objectstack#22191 is closed as a duplicate of this card. What it adds:
The gap is sharper on main than at the pin. objectui#11783 (57dbe34) made the object picker read GET /api/v1/meta/object?preview=draft. An author can now choose the draft object and still gets no fields for it.
The path.useFlowScope calls useObjectFields(scope.trigger?.objectName). On the 404 the hook answers fields: [] with notFound: true. That leaves only previous and the whole-record record ref (objectui#11789) in scope.
The same hook serves other Studio consumers on main:
FlowReferenceField and FlowNodeConfigField's record-node field pickers
Each is presumably field-blind for a draft-only object too. That was measured only on the flow Start node, so the fix belongs in the hook, not in one caller.
The read to reuse. The validations panel on the same screen already reads the draft: GET /api/v1/meta/object/repair_ticket?state=draft&package=PKG answers 200 with the fields.
Still on main: unchanged on objectui mainb6f8fc87 (2026-10-08).
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T05:57Z. ⛔ Not a claim, ⛔ not a dispatch.
packages/app-shell/src/views/metadata-admin/previews/useObjectFields.ts: its object read (useObjectFields, about :49–:125). The read becomes the draft-overlaid object, falling back to the published one, per triage's direction 6053408714. The hook's signature and its result shape are unchanged.
The tests beside it, and .changeset/11895-*.md.
⛔ Not on it:
ConditionBuilder's operators and compile path (seat 3's objectui#11894, p1). Its claim declares this hook an adjacent region, and whoever lands second merges main.
The hook's consumers: useFlowScope, FlowNodeConfigField, FlowReferenceField, ObjectFieldInspector, AppNavInspector, ViewColumnInspector, ViewVariantInspector, ObjectListViewInspector (this seat's objectui#11799 remainder), PageBlockInspector and ActionDefaultInspector. They change nothing. If one needs a change, the dev reports it.
metadata-client.ts, packages/i18n/** and packages/components/src/ui/**.
Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over this path: no path-derived mandate; default tier)
Clause-②: no
Responsibility: objectui app-shell Studio: the flow entry-condition builder and every field picker reading useObjectFields offer no fields for a draft-only object, so an author must type CEL | the platform path is right: the draft-overlaid read (?preview=draft, objectui#11783's picker) answers the draft's fields; the published read 404s by design | every author building automations before the first publish
Thread-read: 6053408714
Serial constraints cleared: none blocking.
area:workflow in flight: none on the board. objectui#11905 (p2, area:workflow) is queued, not in flight.
Adjacent in-flight claim: seat 3's objectui#11894 fences useObjectFields out of its surface and names this card as its owner (its claim's ⛔ Not on it line).
Open objectui PRs: none touches useObjectFields.ts. Read 2026-10-08T06:31Z.
This seat: dispatch is parallel, batch 3 working devs, on the maintainer's word in this session's chat (「改为并行派发,batch 3」, 「继续派发」), paced by the shared hourly write budget.
objectui#11862's dev has finished (PR objectui#11906), which freed this slot.
Working with this card: objectui#11872's dev and objectui#11799's (remainder).
Why Clause-②: no: a client read inside one hook. Its exports, signature and result shape stay as they are, and the server's answers are unchanged.
os-dev-report
{
"issue": 11895,
"status": "blocked",
"branch": "claude/issue-11895-object-fields-draft",
"pr": null,
"session": "session_01DrKzdPdyLLBW3qpZ4vtk7z (subagent mode: the parent's id, as the claim names it)",
"premise_still_valid": true,
"summary": "The in-fence fix is done and pushed (branch tip 3f01d03, no PR): useObjectFields now reads through client.withPreviewDrafts(true).get('object', name), one request, with the signature, the override short-circuit, the result shape and the not-found sentence unchanged. There is a new pin file beside the hook (7 tests) and the changeset .changeset/11895-object-fields-draft.md (@object-ui/app-shell patch). BLOCKED on a fence breach, which the claim says to report before opening the PR. 39 existing test files outside the fence go red with the fix: their metadata-client test doubles define only get (37 files) or a withPreviewDrafts that returns only a list (2 files), so the hook throws 'client.withPreviewDrafts is not a function' inside its effect and the mounted tree unmounts. All 39 are green with the fix ablated and red with it in place, so the doubles are the only cause. 9 of them are ConditionBuilder..test.tsx, which sit inside objectui#11894's claimed surface ('the tests beside them'). The one thing only the PM can resolve: extend this claim's fence to those 39 test doubles (one mechanical change each, below) and sequence it with objectui#11894. Nothing outside the fence was edited.",
"tests": {
"head": "Gate union on local HEAD 3f01d03 (git rev-parse --short HEAD after merging origin/main; the remote tip is 3f01d03). The family sweep before the merge ran on a9ac12d, which holds the same hook and pin blobs.",
"gates": [
"os-verify-lock -c 'pnpm --workspace-concurrency=2 --filter @object-ui/app-shell^... build' (pre-merge tree) -> exit 0 -> 'VERDICT command-exit 0'",
"pnpm --filter @object-ui/app-shell type-check (echoes 'tsc --noEmit && tsc -p tsconfig.test.json') @3f01d03 -> exit 0 -> 'VERDICT command-exit 0'; tsc -p tsconfig.test.json --listFilesOnly counts the new pin file (1 hit)",
"pin: vitest run previews/useObjectFields.draftOverlay-11895.test.tsx -> exit 0 -> 'Test Files 1 passed (1)', 'Tests 7 passed (7)'",
"family sweep: every test file that statically imports useObjectFields.ts, transitively (489 files on a9ac12d; a reverse import-graph walk over packages/ and apps/, scratchpad tool, with app aliases resolved to src) in 4 lock holds -> exit 0 / 1 / 1 / 1 -> 'Test Files 148 passed (148)'; 'Test Files 3 failed | 117 passed (120)'; 'Test Files 25 failed | 95 passed (120)'; 'Test Files 9 failed | 92 passed (101)' = 37 red files, every one a test double (see the reverse check)",
"merge delta @3f01d03: the 26 related test files main added or changed since the base, plus viewCacheInvalidation.guard.test.tsx and the pin -> exit 1 -> 'Test Files 2 failed | 26 passed (28)': HookDefaultInspector.reach-11820 and ObjectHooksPanel.newHookTarget-11820, the same double shape ('client.withPreviewDrafts is not a function')",
"guards: the order's glob ('.guard.test.*' under metadata-admin and studio-design) matches 0 files; control: git ls-files over app-shell lists 1156 test files, and the view-cache guard lives at packages/app-shell/src/views/viewCacheInvalidation.guard.test.tsx. It ran in the merge-delta hold and passed, as did StudioDesignSurface.accessGuard and pillarNavGuard",
"pnpm check:control-bytes -> exit 0 -> 'check-control-bytes: OK (scanned 7980 tracked text file(s); skipped 85 binary)'",
"pnpm check:new-line-citations -> exit 0 -> 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'",
"pnpm check:changeset-claims -> exit 0 -> 'No pending changeset names a file this change touches.'",
"pnpm check:pending-changeset-literals -> exit 0 -> 'No test source names a pending changeset.'",
"re-derived from the actual diff (a new vi.mock test file, a src change and a changeset): pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape / check:test-path-roots / check:metadata-write-doors / check:unreferenced-sources -> all exit 0 -> each prints its OK line; node scripts/check-changeset-presence.mjs -> exit 0 -> '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'",
"lint, narrowed: eslint --format json over the 2 touched ts/tsx files from packages/app-shell, as its lint script runs (eslint .) -> exit 0 -> 2 files, 0 errors, 2 warnings (react-hooks/set-state-in-effect and no-explicit-any). Linting the base blob through stdin gives the same 2 rule ids, so the change adds no lint findings. Population: the 2 files, both reported in the JSON. Invariance: eslint.config.js declares no parserOptions.project or projectService, so the linting is not type-aware and this diff cannot move the verdict on any untouched file"
],
"reverse_check": "Run with the fix committed (a9ac12d), through objectstack scripts/ablation-replace.mjs in wrap mode. The anchor 'client / .withPreviewDrafts(true) / .get' was replaced by the published-only 'client / .get'. Anchor x1 -> x0, replacement x0 -> x1, blob f6b903e3 -> 72d5be5f, on-disk grep count 0. Expected beforehand: the 3 draft pins red, the 4 controls green, all 37 double files green. Observed: 'Test Files 1 failed | 37 passed (38)', 'Tests 3 failed | 294 passed (297)'. The red ones were the draft-only hook pin, the overlay pin and the flow entry-condition pin; the published control, the not-found pin, the override pin and the flow published control stayed green. Restore proven: blob after restore f6b903e3 == HEAD blob, git diff HEAD empty. A second leg @3f01d03 over the 2 merge-delta files ran under the same mutation: 'Test Files 2 passed (2)', restored to the same blob, git diff HEAD empty. ablation-dist-preflight does not apply, because the pins and consumers import the hook by relative path from src.",
"deviations": [
"No draft PR was opened. The fence breach (39 test files) stops it, per the claim's 'Any file outside this list: the dev reports it before opening the PR'. git push ran (the branch is the hand-off); pr_create was not spent.",
"The order's gate lead 'git grep -l useObjectFields -- .test.' (96 files) misses most of the breakage. The red files mount a consumer with a client double and never name the hook. So the family was re-derived as transitive importers of the hook (489 files), not that grep.",
"The closure build ran on the pre-merge tree. The merge moved plugin-grid sources (ImportWizard, import coercion) inside the closure, and they were not rebuilt. type-check @3f01d03 read their pre-merge dist. Declared narrowing; CI rebuilds.",
"The 37-file sweep ran before the merge (a9ac12d and its pre-commit equivalent). After the merge, only the files main added or changed were re-run (the merge delta). The 37 broken files are untouched by main (intersection 0)."
],
"files_changed": [
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.ts (the object read only: withPreviewDrafts(true) plus a comment naming objectui#11895; header doc paragraph)",
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.draftOverlay-11895.test.tsx (new; 7 pins over the REAL MetadataClient on a stub fetch that answers like the framework's item read)",
".changeset/11895-object-fields-draft.md (@object-ui/app-shell patch)"
],
"hypotheses": {
"H1": "HOLDS. At @objectstack 17.7.0 the runtime dispatcher's object item read sets protocolFirst for an admitted ?preview=draft. It calls protocol.getMetaItem({ previewDrafts: true }), and when that has no item it falls back to the registry, which is the published object. RestServer's uncached arm passes previewDrafts to getMetaItem as well. Its previewDrafts arm serves the draft row when one exists and otherwise falls through to the active read ('the render path must degrade to the published value, not error'), and a name with nothing behind it is a 404. Pinned in objectstack packages/objectql/src/protocol-meta.test.ts: 'returns the draft when previewDrafts and a draft exists' and 'falls back to active when previewDrafts but no draft exists (no no_draft 404)'. So there is no separate fallback read. The control pin counts 1 request for a published object, 1 for a draft-only object and 1 for a name with neither.",
"H2": "HOLDS. Callers without override: ActionDefaultInspector, AppNavInspector, ConditionBuilder, FlowNodeConfigField (FieldValueMapField), FlowReferenceField, ObjectFieldInspector (2 calls), PageBlockInspector, ViewColumnInspector, useFlowScope, ObjectListViewInspector, plus ViewVariantInspector and FieldsListEditor, where override is optional. All of them live under views/metadata-admin or views/studio-design. The one runtime host is ViewConfigPanel. It always passes objectFieldsOverride (mapObjectFields(objectDef)) to ViewVariantInspector, which hands it to the hook and to FieldsListEditor. Its ConditionWidget mounts ConditionBuilder with no objectName, so the hook stays idle. A second layer on the server: mayReadPendingDrafts admits only a system caller or a holder of studio.access, setup.access or manage_metadata. Anyone else gets the published answer as if the switch had not been sent, so an end user cannot see draft fields through this read.",
"H3": "HOLDS. Before the fix a draft-only object reached notFound, because the 404 maps to null; the ablation shows this as red draft pins. After the fix, the pin 'a name with neither a draft nor a published object is still not found' asserts fields [], the error equal to t('engine.form.objectNotFound', locale) and 1 request. The draft-only pin asserts error null and the draft's fields.",
"H4": "FALSIFIED, and in the easy direction: preview=draft needs no package. In getMetaItem the previewDrafts arm calls findServedOverlayRow. With no packageId, servedOverlayRowCandidates yields one candidate with no package filter ('with none, any row'), so a package-bound draft is found. The validations panel's ?state=draft&package=PKG is the strict draft read, a different door. The hook has no package in hand and needs none."
}
},
"mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/11895/comments (this report, through scripts/pm/post-stamped.mjs and the fleet-write relay). Not a REST write: git push of claude/issue-11895-object-fields-draft (the empty probe, then 3683591..a9ac12d, then a9ac12d..3f01d03). Reads: REST GETs of the issue and comments of objectui#11895, objectui#11894 (its claim) and objectui#11862 (report format only).",
"open_questions": [
{
"question": "The fix needs 39 test files outside the fence: their metadata-client doubles predate a client read other than get('object', name). Extend the fence to them?",
"options": [
"A. Extend this claim's surface to the 39 doubles. Shape 1 (37 files): add withPreviewDrafts returning the same double, so the test's own get stub keeps answering. Shape 2 (FlowReferenceField.objectPicker-11783 and ObjectFieldInspector.objectPicker-11783): the object their withPreviewDrafts returns also carries get. No assertion changes. 9 of the files (ConditionBuilder.test.tsx, .celGate, .clientMountRoots, .contextSubjects, .mountRoots, .mountScope, .placeholderRoots, .referenceValue, .subjectVocabulary) sit inside objectui#11894's 'tests beside them', so whoever lands second merges main, or this waits for objectui#11894.",
"B. Make the hook tolerate a client without the method (call withPreviewDrafts only when it exists, otherwise read the published object). No test files change.",
"C. Hold this card until objectui#11894 lands, then do A without the overlap."
],
"recommendation": "A (or C if the seat prefers no overlap with objectui#11894). B is a consumer-side tolerance that exists only for incomplete doubles. MetadataClient declares withPreviewDrafts, and a silent published fallback would hide exactly this defect from any real client that lost the method (AGENTS.md #0.1, contract-first). The full list of 39 files is in this report's out_of_scope_findings carrier line; the per-file doubles are each test's own vi.mock of ../useMetadata."
}
],
"out_of_scope_findings": [
"carrier: this claim, if the fence is extended (open_questions A). The 39 files whose doubles need the change: metadata-admin ResourceEditPage.defaultGate, ResourceEditPage.requiredInGatedSection-6900, capabilityGateChannel-7234; inspectors ConditionBuilder (.test, .celGate, .clientMountRoots, .contextSubjects, .mountRoots, .mountScope, .placeholderRoots, .referenceValue, .subjectVocabulary), FlowReferenceField.objectPicker-11783, HookDefaultInspector.flag.i18n-10448, HookDefaultInspector.rosterFailure, HookDefaultInspector.reach-11820, ObjectFieldInspector.objectPicker-11783, PageBlockInspector (.colorLabelling, .definitionListItems-8279, .i18n, .pageHeaderBreadcrumb-11173, .retiredBlockProps, .sectionName), ViewColumnInspector.rosterFailure, ViewColumnInspector.rosterPending, ViewVariantInspector (.celGate, .homeGate, .requiredInGatedSection-6900), unknownValueFlag.i18n-9652; previews flowFieldChrome.i18n-10862-s4; studio-design DataPillar.designerRegistryPopulated, ObjectHooksPanel.celGate, ObjectHooksPanel.newHookTarget-11820, StudioDesignSurface (.autosaveSwitch-11232, .listViewInspector-11823, .navEntryIdentity-11774, .selectionLeafScope, .studioCanvasLeaf, .zhPillarNames-11801). Noted, not filed: it is this card's own collateral, not a finding.",
"carrier: 承接者:无 · noted, not filed. ConditionBuilder calls useObjectFields(objectName) even when a fields prop is supplied (fields = fieldsProp ?? hookFields), so a mount that passes both sends a field read whose answer is discarded. This is an observation (a wasted read, no wrong answer), outside classes a/b/c; objectui#11894's fence excludes the field source."
]
}
Seat ruling on the dev's open question (report 6055355527) · claim amendment
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · read on maind52d908, 2026-10-08T07:54Z.
The question: the fix turns 39 existing test files red, and they are outside the fence. Ruled A
Measured: each of the 39 files mocks ../useMetadata with a metadata-client double that has no withPreviewDrafts (37 files), or one whose withPreviewDrafts returns only list (2 files). The hook then throws client.withPreviewDrafts is not a function. All 39 are green with the fix ablated and red with it in place, so the doubles are the only cause.
Why A:
The doubles predate the client's declared withPreviewDrafts. Completing them is the honest fix, one mechanical change per file with no assertion changes.
B (the hook calls the method only if it exists, else reads the published object) is a consumer-side tolerance that exists only for incomplete doubles. It would silently hide this card's defect from any real client that lost the method.
C (hold the card until objectui#11894 lands) idles a finished fix for an unknown time, for an overlap that is additive.
Claim amendment
The file surface of claim 6053914459 gains the 39 test files the report lists (its carrier line), with only their metadata-client doubles changed:
Shape 1 (37 files): add withPreviewDrafts returning the same double, so the test's own get stub keeps answering.
Shape 2 (FlowReferenceField.objectPicker-11783, ObjectFieldInspector.objectPicker-11783): the object their withPreviewDrafts returns also carries get.
⛔ No assertion, fixture or test name changes. The rest of the claim stands, Clause-②: no included.
The overlap with seat 3's objectui#11894 (p1, in flight, no PR yet)
Nine of the files (ConditionBuilder.test.tsx and its .celGate, .clientMountRoots, .contextSubjects, .mountRoots, .mountScope, .placeholderRoots, .referenceValue and .subjectVocabulary siblings) are among objectui#11894's "tests beside them".
This card's edit there is additive, one property in each file's vi.mock double. Whoever lands second merges main.
A note goes on objectui#11894 in this pass, so its dev expects it.
os-dev-report
{
"issue": 11895,
"status": "done",
"branch": "claude/issue-11895-object-fields-draft",
"pr": "#11952",
"session": "session_01DrKzdPdyLLBW3qpZ4vtk7z (subagent mode: the parent's id, as the claim names it)",
"premise_still_valid": true,
"summary": "Draft PR objectui#11952 (first line Fixes #11895, then the Clause-② line; head 811fef2) lands seat ruling A on claim amendment 6055387753. useObjectFields reads the draft-overlaid object through client.withPreviewDrafts(true).get('object', name), one request, with the signature, the override short-circuit, the result shape and the not-found sentence unchanged, so a draft-only object's fields reach every picker, the flow entry-condition builder included. The 39 metadata-client test doubles the report listed each gain the one member they lacked. 37 have withPreviewDrafts returning the same double; in StudioDesignSurface.autosaveSwitch-11232 it is a vi.fn, because its beforeEach calls mockClear on every member. The 2 objectPicker-11783 doubles carry get on the object withPreviewDrafts returns. No assertion, fixture or test name changed; two double comments that named the read were updated. objectui#11894 had not landed at the last merge, and no new ConditionBuilder test appeared. The 9 ConditionBuilder.*.test.tsx edits are one additive property each, which the PR body names.",
"tests": {
"head": "Gate union on local HEAD 811fef2 (git rev-parse --short HEAD after merging origin/main f1781be; the pushed tip and the PR head are the same sha).",
"gates": [
"os-verify-lock -c "pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build" @811fef2 -> exit 0 -> 'VERDICT command-exit 0'",
"pnpm --filter @object-ui/app-shell type-check (echoes 'tsc --noEmit && tsc -p tsconfig.test.json') @811fef2, after that rebuild -> exit 0 -> 'VERDICT command-exit 0'; --listFilesOnly counts the pin file in the test program",
"family sweep @811fef2: every test file importing useObjectFields.ts directly or transitively, re-derived on this HEAD by a reverse import-graph walk (504 files), in 6 lock holds -> exit 0 x6 -> 'Test Files 84 passed (84)' x6; Tests 713 + 712 + 615 + 1017 + 983 + 482 = 4522 passed. It includes the 39 doubles, the pin, FlowNodeConfigField.entryCondition, FlowNodeInspector.entryCondition, StudioDesignSurface.accessGuard and pillarNavGuard",
"view-cache guard + pin @811fef2: vitest run viewCacheInvalidation.guard.test.tsx useObjectFields.draftOverlay-11895.test.tsx -> exit 0 -> 'Test Files 2 passed (2)', 'Tests 16 passed (16)'",
"pnpm check:control-bytes -> exit 0 -> 'check-control-bytes: OK (scanned 7993 tracked text file(s); skipped 85 binary)'",
"pnpm check:new-line-citations -> exit 0 -> 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'",
"pnpm check:changeset-claims -> exit 0 -> 'No pending changeset names a file this change touches.'",
"pnpm check:pending-changeset-literals -> exit 0 -> 'No test source names a pending changeset.'",
"re-derived from the diff (41 test/src files that carry vi.mock doubles, plus a changeset): pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape / check:test-path-roots / check:metadata-write-doors / check:unreferenced-sources -> all exit 0 -> each prints its OK line; node scripts/check-changeset-presence.mjs -> exit 0 -> '41 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'",
"lint, narrowed: eslint --format json over the 41 touched ts/tsx files, from packages/app-shell as its lint script runs -> exit 0 -> 41 files, 0 errors, 5 warnings. Every pre-existing file has the same error/warning counts as its merge-base blob (f1781be, linted through stdin); the new pin file has 0/0. Population: the 41 files, all reported in the JSON. Invariance: eslint.config.js declares no parserOptions.project or projectService, so the linting is not type-aware",
"after the union: main moved to f3a0488 (no ConditionBuilder or useObjectFields file touched). Its 6 new or changed test files (StudioDesignSurface.interfacesCreatePage-11823, interfaceCreate, ObjectValidationsPanel x4) ran in an uncommitted merge, which was then aborted -> exit 0 -> 'Test Files 6 passed (6)'. HEAD stayed at 811fef2 and the tree is clean",
"CI on 811fef2 at report time: 42 check runs, 9 success, 3 skipped, 30 in_progress, 0 failed (in_progress)"
],
"reverse_check": "@811fef2, with the fix committed, through objectstack scripts/ablation-replace.mjs in wrap mode. The anchor 'client / .withPreviewDrafts(true) / .get' was replaced by the published-only 'client / .get'. Anchor x1 -> x0, blob f6b903e3 -> 72d5be5f, on-disk grep count 0. Expected beforehand: 3 red, 4 green. Observed: 'Tests 3 failed | 4 passed (7)'. The red ones were the draft-only hook pin, the overlay pin and the flow entry-condition pin; the published control, the not-found pin, the override pin and the flow published control stayed green. Restore proven: blob after restore == HEAD blob f6b903e3, git diff HEAD empty. The first round's leg (a9ac12d) ran the 39 double files under the same mutation, and all were green. The doubles themselves need no reverse check; they are test-only.",
"deviations": [
"The double member is a this-returning method (withPreviewDrafts() { return this; }), not an arrow that names the double. A self-reference inside a const initializer is TS7022 under strict, and the method also follows a test that reassigns get on the same object (ViewVariantInspector.requiredInGatedSection-6900). In StudioDesignSurface.autosaveSwitch-11232 it is vi.fn((): unknown => mockClient), because that suite mockClears every member. The first sweep caught this ('fn.mockClear is not a function'), and a follow-up commit fixed it.",
"Two double comments ('useObjectFields -> client.get' in DataPillar.designerRegistryPopulated and ObjectHooksPanel.celGate) now name the read as client.withPreviewDrafts(true).get. They sit inside the doubles; no assertion is touched.",
"main was merged twice in this round: f7f8e2b, before the double edits, and 811fef2, before the final union. The union ran on 811fef2. main's later move (f3a0488) was probed, not merged, so the union HEAD is the PR head.",
"The gate lead 'git grep -l useObjectFields -- .test.' was replaced by the transitive-importer family, as in the first report."
],
"files_changed": [
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.ts (the object read only)",
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.draftOverlay-11895.test.tsx (new, 7 pins)",
".changeset/11895-object-fields-draft.md (@object-ui/app-shell patch)",
"39 test doubles under packages/app-shell/src/views/: metadata-admin ResourceEditPage.defaultGate, ResourceEditPage.requiredInGatedSection-6900, capabilityGateChannel-7234; inspectors ConditionBuilder (.test, .celGate, .clientMountRoots, .contextSubjects, .mountRoots, .mountScope, .placeholderRoots, .referenceValue, .subjectVocabulary), FlowReferenceField.objectPicker-11783, HookDefaultInspector.flag.i18n-10448, .rosterFailure, .reach-11820, ObjectFieldInspector.objectPicker-11783, PageBlockInspector (.colorLabelling, .definitionListItems-8279, .i18n, .pageHeaderBreadcrumb-11173, .retiredBlockProps, .sectionName), ViewColumnInspector.rosterFailure, .rosterPending, ViewVariantInspector (.celGate, .homeGate, .requiredInGatedSection-6900), unknownValueFlag.i18n-9652; previews flowFieldChrome.i18n-10862-s4; studio-design DataPillar.designerRegistryPopulated, ObjectHooksPanel.celGate, ObjectHooksPanel.newHookTarget-11820, StudioDesignSurface (.autosaveSwitch-11232, .listViewInspector-11823, .navEntryIdentity-11774, .selectionLeafScope, .studioCanvasLeaf, .zhPillarNames-11801). git diff over them: 39 files, one member each (plus the 2 comments)"
],
"hypotheses": "Unchanged from report 6055355527. H1 holds: one request, and the framework's getMetaItem previewDrafts arm falls back to the active object. H2 holds: no runtime caller reaches the fetch, and the server answers non-authoring callers the published object. H3 holds and is pinned both ways. H4 is falsified in the easy direction: preview=draft needs no package."
},
"mcp_calls": "0",
"api_writes": "3 relay writes in all, 2 in this round: POST /repos/objectstack-ai/objectui/pulls (pr_create, draft forced, objectui#11952, read back 9490 bytes identical) and POST /repos//issues/11895/comments (this report). The first round's report comment 6055355527 was the third. Not a REST write: git push of claude/issue-11895-object-fields-draft (3f01d03..82f85ab, then 82f85ab..811fef2, through 4ffbafe). Reads: REST GETs of comment 6055387753, PR 11952 and its check runs.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. ConditionBuilder calls useObjectFields(objectName) even when a fields prop is supplied, so a mount that passes both sends a field read whose answer it discards. It is a wasted read, not a wrong answer, outside classes a/b/c. It is recorded in the PR's Acceptance notes."
]
}
Review: ACCEPT — PR objectui#11952 (head 811fef2) · queueing held for a red main
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · reviewed against the PR's file list and diff hunks, git merge-tree, its check runs and the dev's reports 6055355527 (round 1) and 6057121776 (round 2), read 2026-10-08T09:44Z.
Shape: draft → main. The first line is Fixes #11895, and Clause-②: no starts the next line.
Scope: 42 files, +303/−22, inside claim 6053914459 as amended by the ruling 6055387753.
useObjectFields.ts: the object read goes through client.withPreviewDrafts(true).get('object', name), one request. The signature, the override short-circuit, the result shape and the not-found sentence are unchanged.
A new pin file (7 tests) and an @object-ui/app-shell patch changeset.
The 39 test doubles: each gains only the member it lacked. Read on the diff: no test file deletes more than one line, and that line is a one-line double rewritten with the member. No assertion, fixture or test name changes. Two double comments that named the read are updated.
Clause-② re-read: no +export line, and no prop, type member or accepted input changes. no holds.
H1–H4, as measured:
H1:preview=draft answers the draft when one exists and the published object otherwise, in one request (pinned in the framework's own test).
H2: no runtime caller reaches the fetch, and the server answers a non-authoring caller with the published object anyway.
H3: not-found now fires only for a name with neither a draft nor a published object.
H4: no package is needed.
Tests:
Local union on 811fef2 (with mainf1781be merged in): the full transitive family of the hook, re-derived on this HEAD, is 504 files and 4,522 tests, green across 6 lock holds. That includes the 39 doubles, the pin and the flow entry-condition suites. The type-check, the view-cache guard and the derived check:* set are green.
main's later move to f3a0488 (6 new or changed test files) was probed in an uncommitted merge and is green.
Reverse check: with the published-only read restored, 3 failed and 4 passed, as predicted. The draft-only, overlay and flow entry-condition pins went red, and the published, not-found and override controls stayed green. The restore was proven by blob hash.
Overlap: an in-memory git merge-tree against mainf3a0488 is clean. objectui#11894 (seat 3) had not landed. The 9 ConditionBuilder double edits are one additive property each, as the notice on that card says.
CI at 811fef2:Bundle Analysis is red, and it is not this PR's failure. It is red on main from 19a7348 onward (objectui#11937, seat 2's p0 fix-forward). The rest is converging.
Queueing: held until Bundle Analysis is green on main again. The hook's module may sit on an eager path (the runtime view-config panel imports an inspector that uses it), and this seat does not stack bytes onto a red base. Then CI is re-read on a fresh merge ref, and the PR is queued with its assignee in one relay write.
Noted, not filed:ConditionBuilder calls the hook even when a fields prop is supplied, so a mount that passes both sends a read whose answer it discards. That is in the PR's Acceptance notes.
Merged through the merge queue at 2026-10-08T10:53:18Z.
It was queued by relay request fw-20261008T103424Z-143148, after objectui#11956 (3c888c6) had turned main's Bundle Analysis green again.
CI on head 811fef2: 40 of 44 check runs passed, and the 3 skipped are the expected ones. The one red is Bundle Analysis: the base-branch failure named in ACCEPT 6057148685. That check does not run in the merge group.
check-governed-merges.mjs, re-run on the final file list: not governed, +303/−22.
Content check:
2fec2e0 is one squash commit on parent 077d198 (objectui#11938, queued just ahead of it). It changes 42 files, +303/−22, the same as the PR.
All 42 files are blob-identical to the reviewed head 811fef2, and the whole diff's -U0 patch-id matches (67bf5bec…).
Card: closed completed by the PR's Fixes. pm:dispatched comes off right after this note, and is read back.
On record:
Studio's field pickers read the draft-overlaid object in one request. So an object that exists only as a draft offers its fields, and the flow Start node's entry-condition builder lists them as well as previous.
"Not found" now fires only for a name with neither a draft nor a published object.
Not filed: ConditionBuilder sends a read it discards when a fields prop is also supplied. That is in the PR's Acceptance notes.
Activity
objectstack-fleet commentedon Oct 8, 2026
Evidence moved here from objectstack-ai/objectstack#22191. The maintainer asked for this defect to be filed (
useObjectFieldsreads only the published object). The session that filed it could not search this repository. objectstack-ai/objectstack#22191 is closed as a duplicate of this card. What it adds:The gap is sharper on
mainthan at the pin. objectui#11783 (57dbe34) made the object picker readGET /api/v1/meta/object?preview=draft. An author can now choose the draft object and still gets no fields for it.The path.
useFlowScopecallsuseObjectFields(scope.trigger?.objectName). On the 404 the hook answersfields: []withnotFound: true. That leaves onlypreviousand the whole-recordrecordref (objectui#11789) in scope.The same hook serves other Studio consumers on
main:FlowReferenceFieldandFlowNodeConfigField's record-node field pickersObjectFieldInspector,AppNavInspectorViewColumnInspector,ViewVariantInspector,ObjectListViewInspectorPageBlockInspector,ActionDefaultInspectorEach is presumably field-blind for a draft-only object too. That was measured only on the flow Start node, so the fix belongs in the hook, not in one caller.
The read to reuse. The validations panel on the same screen already reads the draft:
GET /api/v1/meta/object/repair_ticket?state=draft&package=PKGanswers 200 with the fields.Still on
main: unchanged on objectuimainb6f8fc87(2026-10-08).Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Grade (triage):
pm:queueadded. p2 stands: a draft-only trigger object offers no fields, so an author has to type CEL (measured).useObjectFieldsreads the draft-overlaid object (?preview=draft, as studio: object-name inputs (Lookup Related object, flow trigger Object) fetch /meta/object/<prefix> on every keystroke and list internal sys_* tables #11783's picker already does) and falls back to the published one.previousis offered), because useObjectFields reads the published object objectstack#22191, closed into this card.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T05:57Z. ⛔ Not a claim, ⛔ not a dispatch.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 9
Session:
session_01DrKzdPdyLLBW3qpZ4vtk7zAccount:
huangyiireneBranch:
claude/issue-11895-object-fields-draftWorktree:
objectui-issue-11895Domain:
domain:uiSeat:
domain:ui#1File surface (line numbers on
main3683591, read 2026-10-08T06:31Z):packages/app-shell/src/views/metadata-admin/previews/useObjectFields.ts: its object read (useObjectFields, about:49–:125). The read becomes the draft-overlaid object, falling back to the published one, per triage's direction6053408714. The hook's signature and its result shape are unchanged..changeset/11895-*.md.⛔ Not on it:
ConditionBuilder's operators and compile path (seat 3's objectui#11894, p1). Its claim declares this hook an adjacent region, and whoever lands second mergesmain.useFlowScope,FlowNodeConfigField,FlowReferenceField,ObjectFieldInspector,AppNavInspector,ViewColumnInspector,ViewVariantInspector,ObjectListViewInspector(this seat's objectui#11799 remainder),PageBlockInspectorandActionDefaultInspector. They change nothing. If one needs a change, the dev reports it.metadata-client.ts,packages/i18n/**andpackages/components/src/ui/**.Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectuiover this path: no path-derived mandate; default tier)Clause-②: no
Responsibility:
objectui app-shell Studio: the flow entry-condition builder and every field picker reading useObjectFields offer no fields for a draft-only object, so an author must type CEL | the platform path is right: the draft-overlaid read (?preview=draft, objectui#11783's picker) answers the draft's fields; the published read 404s by design | every author building automations before the first publishThread-read:
6053408714Serial constraints cleared:
noneblocking.area:workflowin flight: none on the board. objectui#11905 (p2,area:workflow) is queued, not in flight.useObjectFieldsout of its surface and names this card as its owner (its claim's⛔ Not on itline).useObjectFields.ts. Read 2026-10-08T06:31Z.batch3 working devs, on the maintainer's word in this session's chat (「改为并行派发,batch 3」, 「继续派发」), paced by the shared hourly write budget.Why
Clause-②: no: a client read inside one hook. Its exports, signature and result shape stay as they are, and the server's answers are unchanged.objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 11895,
"status": "blocked",
"branch": "claude/issue-11895-object-fields-draft",
"pr": null,
"session": "session_01DrKzdPdyLLBW3qpZ4vtk7z (subagent mode: the parent's id, as the claim names it)",
"premise_still_valid": true,
"summary": "The in-fence fix is done and pushed (branch tip 3f01d03, no PR): useObjectFields now reads through client.withPreviewDrafts(true).get('object', name), one request, with the signature, the override short-circuit, the result shape and the not-found sentence unchanged. There is a new pin file beside the hook (7 tests) and the changeset .changeset/11895-object-fields-draft.md (@object-ui/app-shell patch). BLOCKED on a fence breach, which the claim says to report before opening the PR. 39 existing test files outside the fence go red with the fix: their metadata-client test doubles define only get (37 files) or a withPreviewDrafts that returns only a list (2 files), so the hook throws 'client.withPreviewDrafts is not a function' inside its effect and the mounted tree unmounts. All 39 are green with the fix ablated and red with it in place, so the doubles are the only cause. 9 of them are ConditionBuilder..test.tsx, which sit inside objectui#11894's claimed surface ('the tests beside them'). The one thing only the PM can resolve: extend this claim's fence to those 39 test doubles (one mechanical change each, below) and sequence it with objectui#11894. Nothing outside the fence was edited.",
"tests": {
"head": "Gate union on local HEAD 3f01d03 (git rev-parse --short HEAD after merging origin/main; the remote tip is 3f01d03). The family sweep before the merge ran on a9ac12d, which holds the same hook and pin blobs.",
"gates": [
"os-verify-lock -c 'pnpm --workspace-concurrency=2 --filter @object-ui/app-shell^... build' (pre-merge tree) -> exit 0 -> 'VERDICT command-exit 0'",
"pnpm --filter @object-ui/app-shell type-check (echoes 'tsc --noEmit && tsc -p tsconfig.test.json') @3f01d03 -> exit 0 -> 'VERDICT command-exit 0'; tsc -p tsconfig.test.json --listFilesOnly counts the new pin file (1 hit)",
"pin: vitest run previews/useObjectFields.draftOverlay-11895.test.tsx -> exit 0 -> 'Test Files 1 passed (1)', 'Tests 7 passed (7)'",
"family sweep: every test file that statically imports useObjectFields.ts, transitively (489 files on a9ac12d; a reverse import-graph walk over packages/ and apps/, scratchpad tool, with app aliases resolved to src) in 4 lock holds -> exit 0 / 1 / 1 / 1 -> 'Test Files 148 passed (148)'; 'Test Files 3 failed | 117 passed (120)'; 'Test Files 25 failed | 95 passed (120)'; 'Test Files 9 failed | 92 passed (101)' = 37 red files, every one a test double (see the reverse check)",
"merge delta @3f01d03: the 26 related test files main added or changed since the base, plus viewCacheInvalidation.guard.test.tsx and the pin -> exit 1 -> 'Test Files 2 failed | 26 passed (28)': HookDefaultInspector.reach-11820 and ObjectHooksPanel.newHookTarget-11820, the same double shape ('client.withPreviewDrafts is not a function')",
"guards: the order's glob ('.guard.test.*' under metadata-admin and studio-design) matches 0 files; control: git ls-files over app-shell lists 1156 test files, and the view-cache guard lives at packages/app-shell/src/views/viewCacheInvalidation.guard.test.tsx. It ran in the merge-delta hold and passed, as did StudioDesignSurface.accessGuard and pillarNavGuard",
"pnpm check:control-bytes -> exit 0 -> 'check-control-bytes: OK (scanned 7980 tracked text file(s); skipped 85 binary)'",
"pnpm check:new-line-citations -> exit 0 -> 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'",
"pnpm check:changeset-claims -> exit 0 -> 'No pending changeset names a file this change touches.'",
"pnpm check:pending-changeset-literals -> exit 0 -> 'No test source names a pending changeset.'",
"re-derived from the actual diff (a new vi.mock test file, a src change and a changeset): pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape / check:test-path-roots / check:metadata-write-doors / check:unreferenced-sources -> all exit 0 -> each prints its OK line; node scripts/check-changeset-presence.mjs -> exit 0 -> '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'",
"lint, narrowed: eslint --format json over the 2 touched ts/tsx files from packages/app-shell, as its lint script runs (eslint .) -> exit 0 -> 2 files, 0 errors, 2 warnings (react-hooks/set-state-in-effect and no-explicit-any). Linting the base blob through stdin gives the same 2 rule ids, so the change adds no lint findings. Population: the 2 files, both reported in the JSON. Invariance: eslint.config.js declares no parserOptions.project or projectService, so the linting is not type-aware and this diff cannot move the verdict on any untouched file"
],
"reverse_check": "Run with the fix committed (a9ac12d), through objectstack scripts/ablation-replace.mjs in wrap mode. The anchor 'client / .withPreviewDrafts(true) / .get' was replaced by the published-only 'client / .get'. Anchor x1 -> x0, replacement x0 -> x1, blob f6b903e3 -> 72d5be5f, on-disk grep count 0. Expected beforehand: the 3 draft pins red, the 4 controls green, all 37 double files green. Observed: 'Test Files 1 failed | 37 passed (38)', 'Tests 3 failed | 294 passed (297)'. The red ones were the draft-only hook pin, the overlay pin and the flow entry-condition pin; the published control, the not-found pin, the override pin and the flow published control stayed green. Restore proven: blob after restore f6b903e3 == HEAD blob, git diff HEAD empty. A second leg @3f01d03 over the 2 merge-delta files ran under the same mutation: 'Test Files 2 passed (2)', restored to the same blob, git diff HEAD empty. ablation-dist-preflight does not apply, because the pins and consumers import the hook by relative path from src.",
"deviations": [
"No draft PR was opened. The fence breach (39 test files) stops it, per the claim's 'Any file outside this list: the dev reports it before opening the PR'. git push ran (the branch is the hand-off); pr_create was not spent.",
"The order's gate lead 'git grep -l useObjectFields -- .test.' (96 files) misses most of the breakage. The red files mount a consumer with a client double and never name the hook. So the family was re-derived as transitive importers of the hook (489 files), not that grep.",
"The closure build ran on the pre-merge tree. The merge moved plugin-grid sources (ImportWizard, import coercion) inside the closure, and they were not rebuilt. type-check @3f01d03 read their pre-merge dist. Declared narrowing; CI rebuilds.",
"The 37-file sweep ran before the merge (a9ac12d and its pre-commit equivalent). After the merge, only the files main added or changed were re-run (the merge delta). The 37 broken files are untouched by main (intersection 0)."
],
"files_changed": [
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.ts (the object read only: withPreviewDrafts(true) plus a comment naming objectui#11895; header doc paragraph)",
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.draftOverlay-11895.test.tsx (new; 7 pins over the REAL MetadataClient on a stub fetch that answers like the framework's item read)",
".changeset/11895-object-fields-draft.md (@object-ui/app-shell patch)"
],
"hypotheses": {
"H1": "HOLDS. At @objectstack 17.7.0 the runtime dispatcher's object item read sets protocolFirst for an admitted ?preview=draft. It calls protocol.getMetaItem({ previewDrafts: true }), and when that has no item it falls back to the registry, which is the published object. RestServer's uncached arm passes previewDrafts to getMetaItem as well. Its previewDrafts arm serves the draft row when one exists and otherwise falls through to the active read ('the render path must degrade to the published value, not error'), and a name with nothing behind it is a 404. Pinned in objectstack packages/objectql/src/protocol-meta.test.ts: 'returns the draft when previewDrafts and a draft exists' and 'falls back to active when previewDrafts but no draft exists (no no_draft 404)'. So there is no separate fallback read. The control pin counts 1 request for a published object, 1 for a draft-only object and 1 for a name with neither.",
"H2": "HOLDS. Callers without override: ActionDefaultInspector, AppNavInspector, ConditionBuilder, FlowNodeConfigField (FieldValueMapField), FlowReferenceField, ObjectFieldInspector (2 calls), PageBlockInspector, ViewColumnInspector, useFlowScope, ObjectListViewInspector, plus ViewVariantInspector and FieldsListEditor, where override is optional. All of them live under views/metadata-admin or views/studio-design. The one runtime host is ViewConfigPanel. It always passes objectFieldsOverride (mapObjectFields(objectDef)) to ViewVariantInspector, which hands it to the hook and to FieldsListEditor. Its ConditionWidget mounts ConditionBuilder with no objectName, so the hook stays idle. A second layer on the server: mayReadPendingDrafts admits only a system caller or a holder of studio.access, setup.access or manage_metadata. Anyone else gets the published answer as if the switch had not been sent, so an end user cannot see draft fields through this read.",
"H3": "HOLDS. Before the fix a draft-only object reached notFound, because the 404 maps to null; the ablation shows this as red draft pins. After the fix, the pin 'a name with neither a draft nor a published object is still not found' asserts fields [], the error equal to t('engine.form.objectNotFound', locale) and 1 request. The draft-only pin asserts error null and the draft's fields.",
"H4": "FALSIFIED, and in the easy direction: preview=draft needs no package. In getMetaItem the previewDrafts arm calls findServedOverlayRow. With no packageId, servedOverlayRowCandidates yields one candidate with no package filter ('with none, any row'), so a package-bound draft is found. The validations panel's ?state=draft&package=PKG is the strict draft read, a different door. The hook has no package in hand and needs none."
}
},
"mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/11895/comments (this report, through scripts/pm/post-stamped.mjs and the fleet-write relay). Not a REST write: git push of claude/issue-11895-object-fields-draft (the empty probe, then 3683591..a9ac12d, then a9ac12d..3f01d03). Reads: REST GETs of the issue and comments of objectui#11895, objectui#11894 (its claim) and objectui#11862 (report format only).",
"open_questions": [
{
"question": "The fix needs 39 test files outside the fence: their metadata-client doubles predate a client read other than get('object', name). Extend the fence to them?",
"options": [
"A. Extend this claim's surface to the 39 doubles. Shape 1 (37 files): add withPreviewDrafts returning the same double, so the test's own get stub keeps answering. Shape 2 (FlowReferenceField.objectPicker-11783 and ObjectFieldInspector.objectPicker-11783): the object their withPreviewDrafts returns also carries get. No assertion changes. 9 of the files (ConditionBuilder.test.tsx, .celGate, .clientMountRoots, .contextSubjects, .mountRoots, .mountScope, .placeholderRoots, .referenceValue, .subjectVocabulary) sit inside objectui#11894's 'tests beside them', so whoever lands second merges main, or this waits for objectui#11894.",
"B. Make the hook tolerate a client without the method (call withPreviewDrafts only when it exists, otherwise read the published object). No test files change.",
"C. Hold this card until objectui#11894 lands, then do A without the overlap."
],
"recommendation": "A (or C if the seat prefers no overlap with objectui#11894). B is a consumer-side tolerance that exists only for incomplete doubles. MetadataClient declares withPreviewDrafts, and a silent published fallback would hide exactly this defect from any real client that lost the method (AGENTS.md #0.1, contract-first). The full list of 39 files is in this report's out_of_scope_findings carrier line; the per-file doubles are each test's own vi.mock of ../useMetadata."
}
],
"out_of_scope_findings": [
"carrier: this claim, if the fence is extended (open_questions A). The 39 files whose doubles need the change: metadata-admin ResourceEditPage.defaultGate, ResourceEditPage.requiredInGatedSection-6900, capabilityGateChannel-7234; inspectors ConditionBuilder (.test, .celGate, .clientMountRoots, .contextSubjects, .mountRoots, .mountScope, .placeholderRoots, .referenceValue, .subjectVocabulary), FlowReferenceField.objectPicker-11783, HookDefaultInspector.flag.i18n-10448, HookDefaultInspector.rosterFailure, HookDefaultInspector.reach-11820, ObjectFieldInspector.objectPicker-11783, PageBlockInspector (.colorLabelling, .definitionListItems-8279, .i18n, .pageHeaderBreadcrumb-11173, .retiredBlockProps, .sectionName), ViewColumnInspector.rosterFailure, ViewColumnInspector.rosterPending, ViewVariantInspector (.celGate, .homeGate, .requiredInGatedSection-6900), unknownValueFlag.i18n-9652; previews flowFieldChrome.i18n-10862-s4; studio-design DataPillar.designerRegistryPopulated, ObjectHooksPanel.celGate, ObjectHooksPanel.newHookTarget-11820, StudioDesignSurface (.autosaveSwitch-11232, .listViewInspector-11823, .navEntryIdentity-11774, .selectionLeafScope, .studioCanvasLeaf, .zhPillarNames-11801). Noted, not filed: it is this card's own collateral, not a finding.",
"carrier: 承接者:无 · noted, not filed. ConditionBuilder calls useObjectFields(objectName) even when a fields prop is supplied (fields = fieldsProp ?? hookFields), so a mount that passes both sends a field read whose answer is discarded. This is an observation (a wasted read, no wrong answer), outside classes a/b/c; objectui#11894's fence excludes the field source."
]
}
objectstack-fleet commentedon Oct 8, 2026
Seat ruling on the dev's open question (report
6055355527) · claim amendmentdomain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· read onmaind52d908, 2026-10-08T07:54Z.The question: the fix turns 39 existing test files red, and they are outside the fence. Ruled A
../useMetadatawith a metadata-client double that has nowithPreviewDrafts(37 files), or one whosewithPreviewDraftsreturns onlylist(2 files). The hook then throwsclient.withPreviewDrafts is not a function. All 39 are green with the fix ablated and red with it in place, so the doubles are the only cause.withPreviewDrafts. Completing them is the honest fix, one mechanical change per file with no assertion changes.Claim amendment
The file surface of claim
6053914459gains the 39 test files the report lists (its carrier line), with only their metadata-client doubles changed:withPreviewDraftsreturning the same double, so the test's owngetstub keeps answering.FlowReferenceField.objectPicker-11783,ObjectFieldInspector.objectPicker-11783): the object theirwithPreviewDraftsreturns also carriesget.⛔ No assertion, fixture or test name changes. The rest of the claim stands,
Clause-②: noincluded.The overlap with seat 3's objectui#11894 (p1, in flight, no PR yet)
ConditionBuilder.test.tsxand its.celGate,.clientMountRoots,.contextSubjects,.mountRoots,.mountScope,.placeholderRoots,.referenceValueand.subjectVocabularysiblings) are among objectui#11894's "tests beside them".vi.mockdouble. Whoever lands second mergesmain.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 11895,
"status": "done",
"branch": "claude/issue-11895-object-fields-draft",
"pr": "#11952",
"session": "session_01DrKzdPdyLLBW3qpZ4vtk7z (subagent mode: the parent's id, as the claim names it)",
"premise_still_valid": true,
"summary": "Draft PR objectui#11952 (first line Fixes #11895, then the Clause-② line; head 811fef2) lands seat ruling A on claim amendment 6055387753. useObjectFields reads the draft-overlaid object through client.withPreviewDrafts(true).get('object', name), one request, with the signature, the override short-circuit, the result shape and the not-found sentence unchanged, so a draft-only object's fields reach every picker, the flow entry-condition builder included. The 39 metadata-client test doubles the report listed each gain the one member they lacked. 37 have withPreviewDrafts returning the same double; in StudioDesignSurface.autosaveSwitch-11232 it is a vi.fn, because its beforeEach calls mockClear on every member. The 2 objectPicker-11783 doubles carry get on the object withPreviewDrafts returns. No assertion, fixture or test name changed; two double comments that named the read were updated. objectui#11894 had not landed at the last merge, and no new ConditionBuilder test appeared. The 9 ConditionBuilder.*.test.tsx edits are one additive property each, which the PR body names.",
"tests": {
"head": "Gate union on local HEAD 811fef2 (git rev-parse --short HEAD after merging origin/main f1781be; the pushed tip and the PR head are the same sha).",
"gates": [
"os-verify-lock -c "pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build" @811fef2 -> exit 0 -> 'VERDICT command-exit 0'",
"pnpm --filter @object-ui/app-shell type-check (echoes 'tsc --noEmit && tsc -p tsconfig.test.json') @811fef2, after that rebuild -> exit 0 -> 'VERDICT command-exit 0'; --listFilesOnly counts the pin file in the test program",
"family sweep @811fef2: every test file importing useObjectFields.ts directly or transitively, re-derived on this HEAD by a reverse import-graph walk (504 files), in 6 lock holds -> exit 0 x6 -> 'Test Files 84 passed (84)' x6; Tests 713 + 712 + 615 + 1017 + 983 + 482 = 4522 passed. It includes the 39 doubles, the pin, FlowNodeConfigField.entryCondition, FlowNodeInspector.entryCondition, StudioDesignSurface.accessGuard and pillarNavGuard",
"view-cache guard + pin @811fef2: vitest run viewCacheInvalidation.guard.test.tsx useObjectFields.draftOverlay-11895.test.tsx -> exit 0 -> 'Test Files 2 passed (2)', 'Tests 16 passed (16)'",
"pnpm check:control-bytes -> exit 0 -> 'check-control-bytes: OK (scanned 7993 tracked text file(s); skipped 85 binary)'",
"pnpm check:new-line-citations -> exit 0 -> 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'",
"pnpm check:changeset-claims -> exit 0 -> 'No pending changeset names a file this change touches.'",
"pnpm check:pending-changeset-literals -> exit 0 -> 'No test source names a pending changeset.'",
"re-derived from the diff (41 test/src files that carry vi.mock doubles, plus a changeset): pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape / check:test-path-roots / check:metadata-write-doors / check:unreferenced-sources -> all exit 0 -> each prints its OK line; node scripts/check-changeset-presence.mjs -> exit 0 -> '41 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'",
"lint, narrowed: eslint --format json over the 41 touched ts/tsx files, from packages/app-shell as its lint script runs -> exit 0 -> 41 files, 0 errors, 5 warnings. Every pre-existing file has the same error/warning counts as its merge-base blob (f1781be, linted through stdin); the new pin file has 0/0. Population: the 41 files, all reported in the JSON. Invariance: eslint.config.js declares no parserOptions.project or projectService, so the linting is not type-aware",
"after the union: main moved to f3a0488 (no ConditionBuilder or useObjectFields file touched). Its 6 new or changed test files (StudioDesignSurface.interfacesCreatePage-11823, interfaceCreate, ObjectValidationsPanel x4) ran in an uncommitted merge, which was then aborted -> exit 0 -> 'Test Files 6 passed (6)'. HEAD stayed at 811fef2 and the tree is clean",
"CI on 811fef2 at report time: 42 check runs, 9 success, 3 skipped, 30 in_progress, 0 failed (in_progress)"
],
"reverse_check": "@811fef2, with the fix committed, through objectstack scripts/ablation-replace.mjs in wrap mode. The anchor 'client / .withPreviewDrafts(true) / .get' was replaced by the published-only 'client / .get'. Anchor x1 -> x0, blob f6b903e3 -> 72d5be5f, on-disk grep count 0. Expected beforehand: 3 red, 4 green. Observed: 'Tests 3 failed | 4 passed (7)'. The red ones were the draft-only hook pin, the overlay pin and the flow entry-condition pin; the published control, the not-found pin, the override pin and the flow published control stayed green. Restore proven: blob after restore == HEAD blob f6b903e3, git diff HEAD empty. The first round's leg (a9ac12d) ran the 39 double files under the same mutation, and all were green. The doubles themselves need no reverse check; they are test-only.",
"deviations": [
"The double member is a this-returning method (withPreviewDrafts() { return this; }), not an arrow that names the double. A self-reference inside a const initializer is TS7022 under strict, and the method also follows a test that reassigns get on the same object (ViewVariantInspector.requiredInGatedSection-6900). In StudioDesignSurface.autosaveSwitch-11232 it is vi.fn((): unknown => mockClient), because that suite mockClears every member. The first sweep caught this ('fn.mockClear is not a function'), and a follow-up commit fixed it.",
"Two double comments ('useObjectFields -> client.get' in DataPillar.designerRegistryPopulated and ObjectHooksPanel.celGate) now name the read as client.withPreviewDrafts(true).get. They sit inside the doubles; no assertion is touched.",
"main was merged twice in this round: f7f8e2b, before the double edits, and 811fef2, before the final union. The union ran on 811fef2. main's later move (f3a0488) was probed, not merged, so the union HEAD is the PR head.",
"The gate lead 'git grep -l useObjectFields -- .test.' was replaced by the transitive-importer family, as in the first report."
],
"files_changed": [
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.ts (the object read only)",
"packages/app-shell/src/views/metadata-admin/previews/useObjectFields.draftOverlay-11895.test.tsx (new, 7 pins)",
".changeset/11895-object-fields-draft.md (@object-ui/app-shell patch)",
"39 test doubles under packages/app-shell/src/views/: metadata-admin ResourceEditPage.defaultGate, ResourceEditPage.requiredInGatedSection-6900, capabilityGateChannel-7234; inspectors ConditionBuilder (.test, .celGate, .clientMountRoots, .contextSubjects, .mountRoots, .mountScope, .placeholderRoots, .referenceValue, .subjectVocabulary), FlowReferenceField.objectPicker-11783, HookDefaultInspector.flag.i18n-10448, .rosterFailure, .reach-11820, ObjectFieldInspector.objectPicker-11783, PageBlockInspector (.colorLabelling, .definitionListItems-8279, .i18n, .pageHeaderBreadcrumb-11173, .retiredBlockProps, .sectionName), ViewColumnInspector.rosterFailure, .rosterPending, ViewVariantInspector (.celGate, .homeGate, .requiredInGatedSection-6900), unknownValueFlag.i18n-9652; previews flowFieldChrome.i18n-10862-s4; studio-design DataPillar.designerRegistryPopulated, ObjectHooksPanel.celGate, ObjectHooksPanel.newHookTarget-11820, StudioDesignSurface (.autosaveSwitch-11232, .listViewInspector-11823, .navEntryIdentity-11774, .selectionLeafScope, .studioCanvasLeaf, .zhPillarNames-11801). git diff over them: 39 files, one member each (plus the 2 comments)"
],
"hypotheses": "Unchanged from report 6055355527. H1 holds: one request, and the framework's getMetaItem previewDrafts arm falls back to the active object. H2 holds: no runtime caller reaches the fetch, and the server answers non-authoring callers the published object. H3 holds and is pinned both ways. H4 is falsified in the easy direction: preview=draft needs no package."
},
"mcp_calls": "0",
"api_writes": "3 relay writes in all, 2 in this round: POST /repos/objectstack-ai/objectui/pulls (pr_create, draft forced, objectui#11952, read back 9490 bytes identical) and POST /repos//issues/11895/comments (this report). The first round's report comment 6055355527 was the third. Not a REST write: git push of claude/issue-11895-object-fields-draft (3f01d03..82f85ab, then 82f85ab..811fef2, through 4ffbafe). Reads: REST GETs of comment 6055387753, PR 11952 and its check runs.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. ConditionBuilder calls useObjectFields(objectName) even when a fields prop is supplied, so a mount that passes both sends a field read whose answer it discards. It is a wasted read, not a wrong answer, outside classes a/b/c. It is recorded in the PR's Acceptance notes."
]
}
objectstack-fleet commentedon Oct 8, 2026
Review: ACCEPT — PR objectui#11952 (head
811fef2) · queueing held for a redmaindomain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· reviewed against the PR's file list and diff hunks,git merge-tree, its check runs and the dev's reports6055355527(round 1) and6057121776(round 2), read 2026-10-08T09:44Z.main. The first line isFixes #11895, andClause-②: nostarts the next line.6053914459as amended by the ruling6055387753.useObjectFields.ts: the object read goes throughclient.withPreviewDrafts(true).get('object', name), one request. The signature, theoverrideshort-circuit, the result shape and the not-found sentence are unchanged.@object-ui/app-shellpatch changeset.+exportline, and no prop, type member or accepted input changes.noholds.preview=draftanswers the draft when one exists and the published object otherwise, in one request (pinned in the framework's own test).811fef2(withmainf1781bemerged in): the full transitive family of the hook, re-derived on this HEAD, is 504 files and 4,522 tests, green across 6 lock holds. That includes the 39 doubles, the pin and the flow entry-condition suites. The type-check, the view-cache guard and the derivedcheck:*set are green.main's later move tof3a0488(6 new or changed test files) was probed in an uncommitted merge and is green.git merge-treeagainstmainf3a0488is clean. objectui#11894 (seat 3) had not landed. The 9ConditionBuilderdouble edits are one additive property each, as the notice on that card says.811fef2:Bundle Analysisis red, and it is not this PR's failure. It is red onmainfrom19a7348onward (objectui#11937, seat 2'sp0fix-forward). The rest is converging.Bundle Analysisis green onmainagain. The hook's module may sit on an eager path (the runtime view-config panel imports an inspector that uses it), and this seat does not stack bytes onto a red base. Then CI is re-read on a fresh merge ref, and the PR is queued with its assignee in one relay write.ConditionBuildercalls the hook even when afieldsprop is supplied, so a mount that passes both sends a read whose answer it discards. That is in the PR's Acceptance notes.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Landed: PR objectui#11952 →
main2fec2e0domain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· verified 2026-10-08T10:57Z.fw-20261008T103424Z-143148, after objectui#11956 (3c888c6) had turnedmain'sBundle Analysisgreen again.811fef2: 40 of 44 check runs passed, and the 3 skipped are the expected ones. The one red isBundle Analysis: the base-branch failure named in ACCEPT6057148685. That check does not run in the merge group.check-governed-merges.mjs, re-run on the final file list: not governed, +303/−22.2fec2e0is one squash commit on parent077d198(objectui#11938, queued just ahead of it). It changes 42 files, +303/−22, the same as the PR.811fef2, and the whole diff's-U0patch-id matches (67bf5bec…).completedby the PR'sFixes.pm:dispatchedcomes off right after this note, and is read back.previous.ConditionBuildersends a read it discards when afieldsprop is also supplied. That is in the PR's Acceptance notes.Generated by Claude Code
fix(app-shell): Studio's field pickers read the draft-overlaid object…