Repository navigation
metadata designer: a create flow saves the spec seed's placeholder binding when the author picks none — a dataset with no base object saves object: example_object and its reads are refused #12810
Description
Filing gate ①: a user-visible authoring defect that contradicts the declared contract. reach: a public entry, the console's dataset designer in create mode, measured in Chromium against a live backend. Family: a create flow writes a placeholder the author never chose. objectui#12789 (PR #12806) is its first occurrence: a non-Latin Label named the item field. This is the second, so this card covers every placeholder the create seeds carry, with an enumeration pin.
Reader who acts: objectui triage. The landing is the create flow's body builder, packages/app-shell/src/views/metadata-admin/createBody.ts (buildCreateModeBody, which spreads the spec seed under the draft), and the designer's save gating. Filed by domain:ui seat 3 (session_01Vk15bz8xHtKGUfXfCm16h4) from the os-dev report on objectui#12789 (PR #12806), which does not change this. ⛔ Not a claim.
What happens
- An author opens a new dataset, gives it a name and saves it without picking a base object; the box still reads "Select an object…".
- The save sends
object: "example_object"and succeeds. - No such object exists. The next analytics read is refused, and the console toasts "Analytics query refused: … reading "example_object" is not permitted".
Measured
- Backend: objectstack
66d456d01examples/app-crm,--fresh, with a writable packagecom.qa.vrf(namespaceqavrf). Chromium on objectuimain8df2608, and again at PR fix(app-shell): a report's or dataset's Label with no machine name leaves the Name empty (objectui#12789) #12806's heade4b532b. PUT /api/v1/meta/dataset/probe_ds?mode=draft&package=com.qa.vrfsent the body{"name":"probe_ds","label":"Probe Ds","object":"example_object","dimensions":[],"measures":[{"name":"count","aggregate":"count"}]}and got 200, "Saved dataset 'probe_ds'".- The next read was refused:
[Analytics] object-level read admission denied for "example_object".
The contract (read from source)
@objectstack/speckernel/metadata-create-seeds.ts:BUILTIN_METADATA_CREATE_SEEDS.dataset.objectis the placeholderexample_object(PLACEHOLDER_OBJECT).- That file's header says: "Each seed is a COMPLETE minimal valid object including placeholder identity (
name/label/object binding); a create flow overrides those from user input." - The designer's create flow spreads the seed under the draft. It does not override the object binding when the author picks none, and the save does not ask for one.
Expected
- A create flow never saves a seed placeholder the author did not override. For a dataset, the save asks for a base object, as a field's lookup asks for its target ("Pick the object to link to"), instead of sending
example_object. - An enumeration pin over
BUILTIN_METADATA_CREATE_SEEDSasserts that every placeholder identity a seed carries (name,label, object binding) is either overridden from user input or refused at save. The next seed added cannot carry a placeholder through silently.
Dedupe (open and closed):
- every objectui
domain:uicard (2433 rows) plus the newest objectui issues, grepped forexample_object|PLACEHOLDER_OBJECT|create seed|createSeed|buildCreateModeBody|BUILTIN_METADATA_CREATE_SEEDSand for a dataset placeholder or a dataset saved without a base object. The hits are objectui#12715, studio: the page-create seed and the page preview build the default record page without the runtime's detail sections, so an object with no fieldGroups gets a barerecord:detailsunlike the running app #12442, Synthesizedasideregion carriesclassName, whichPageRegionSchemarejects — a Reference Rail page has no persistable spelling #4286 and finding(app-shell/inspectors,i18n): the dashboard widget inspector's field combo shows the English literalSearch fields…as its search placeholder —engine.form.searchFieldsexists in en-US and zh-CN and its sibling inspector reads it #10696, all other subjects; - positive control on the same rows:
flow designer, 24 hits.
Dedupe words: dataset example_object placeholder seed, dataset save without base object, createSeed placeholder object binding, buildCreateModeBody seed spread.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 16, 2026
Triage: first grade,
bug·priority:p2·domain:ui·area:studio·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T22:58ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
object: example_object), and the dataset's reads are then refused. It is the second occurrence of the class, so the enumeration pin is in scope.createBody.ts(buildCreateModeBody) and the save gating. A placeholder never saves as authored. Serial with PR fix(app-shell): a report's or dataset's Label with no machine name leaves the Name empty (objectui#12789) #12806 (metadata designer: a report's or dataset's Label in a non-Latin script (or a cleared Label) derives the Namefield, where the object designer leaves it empty and asks for one #12789).objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 3 (
priority:p2)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12810-seed-placeholderWorktree:
objectui-issue-12810Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/app-shell/src/views/metadata-admin/createBody.ts(buildCreateModeBody); the metadata designer's create-mode save gating (the module that decides whether Save is allowed for a create draft, located at start; the dataset designer's base-object field only where the save gate surfaces its reason);packages/i18n/src/locales/*.ts(new keys only, all ten packs, if the refusal needs a message); tests beside, including one enumeration pin overBUILTIN_METADATA_CREATE_SEEDS; one.changeset/12810-*.md(@object-ui/app-shell: patch, plus@object-ui/i18nif keys are added) (stop on breach; explain in the report).Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate for these paths)Clause-②: no (provisional: the dev reports yes if a published export changes)
Responsibility:
the console's create flow (buildCreateModeBody spreads the spec seed under the draft and saves its placeholder binding) | the spec seed's header already says a create flow overrides the placeholder identity from user input, and the field designer's lookup already asks for its target | every author who creates a dataset (and any seed with a placeholder binding) and saves without picking oneThread-read: 6226269923
Serial constraints cleared: PR #12806 (objectui#12789, the first occurrence of this class) is merged. Scanned every open objectui PR's file list and the File surface lines of every
pm:dispatchedobjectui claim just now; none touchescreateBody.tsor the designer save gate. objectui#12690 (the discard-draft confirm and toast) and objectui#12768 (DirectoryPage.tsx) are inmetadata-adminbut on other files. This seat's in-flight objectui#12803 (PR #12832) and objectui#12812 (PR #12836) are disjoint.Claimed at: 2026-10-17T00:41Z
objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{ "issue": 12810, "status": "done", "branch": "claude/issue-12810-seed-placeholder", "pr": "https://github.com/objectstack-ai/objectui/pull/12847", "session": "session_01XVp19E1MYUTFM3dnkwz8gB", "premise_still_valid": true, "summary": "buildCreateModeBody (createBody.ts) now carries a spec create seed's structural defaults but never its placeholder identity: name, label, and every top-level key whose value is the spec's placeholder object. The shared create-mode save gate is doSave in ResourceEditPage.tsx; every designer uses it, the dataset designer has no gate of its own (A1 holds). It refuses Save before any request when a placeholder key the author left blank is in the LIVE /meta/types schema's required list, and names the key. So a dataset with no base object is refused, and no PUT carries the placeholder object. Measured against the order: (1) the installed spec (@objectstack/spec 18.0.0-next.2, kernel entry) keeps PLACEHOLDER_OBJECT and BUILTIN_METADATA_CREATE_SEEDS module-private and exports only getMetadataCreateSeed and listMetadataCreateSeedTypes. The placeholder object is therefore read from the seeds themselves (the set of every seed's object value); no literal copy. (2) A3 measured false: designer strings live in packages/app-shell/src/views/metadata-admin/i18n.ts, which has two tables (en-US, zh-CN), not the ten packages/i18n packs. One new key, engine.validation.seedPlaceholderUnset, was added there; packages/i18n is untouched. (3) The label placeholder is in scope by the enumeration ruling. Behaviour change: on action, agent, dashboard, dataset, email_template, flow, page, skill and tool, Save with a blank Label is now refused at the client. Before, it saved the seed's 'New X'. Where Label is optional (hook, mapping, object, permission, translation), the item is saved with no label. page.object is optional in the spec, so a page is saved unbound rather than refused.", "tests": "All at HEAD 20746107bd. Closure build: pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build, VERDICT command-exit 0. Type-check: pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json; the test tsconfig includes src/**/*.test.ts(x)), VERDICT command-exit 0. Targeted vitest, --maxWorkers=1: the two new files plus createBody.test.ts, 'Test Files 3 passed (3) / Tests 40 passed (40)'. Narrowed package run: the 46 files matching metadata-admin ResourceEditPage*, createBody*, anchors*, i18n* and inspectors/DatasetDefaultInspector*, 'Test Files 46 passed (46) / Tests 275 passed (275)'. Plus every other test file that renders MetadataResourceEditPage (14 files), 'Test Files 14 passed (14) / Tests 149 passed (149)'. Full packages/app-shell/ dir (1522 test files, --maxWorkers=1): NOT MEASURED. After 1h48m under the lock it had printed nothing past 'RUN'; I killed my own PID 19356 and declared it for CI. Ablation, committed first, via ablation-replace.mjs --delete on the line in buildCreateModeBody that drops placeholder keys. Anchor 1 → 0, blob 3384b667c05e → 44cc85612aac. Result 16 failed / 19 passed: (a), all 14 seed-carrying rows of the pin's per-type test, and the dataset count test went red; (b), the controls and view stayed green. Restore: blob == HEAD 3384b667c05e and git diff HEAD empty, checked by the script. The first ablation attempt was a no-op: --replacement '' was refused by the tool, which restored the file. That run measured nothing and is not counted. No dist/ sits between the mutation and the run: the tests import createBody by relative source path. Lint, narrowed to the 5 changed files: eslint --no-inline-config --format json reports 5 files, 3 errors and 57 warnings, all in ResourceEditPage.tsx. They are react-hooks/static-components at three render sites. The base blob f3a7b870a1 linted the same way gives the identical 3 errors and 57 warnings (lines shifted by +22). The other 4 files have 0. No type-aware linting is configured (no parserOptions.project or projectService), so the diff cannot move the verdict on any untouched file.", "mcp_calls": "0", "api_writes": "2 so far, plus this comment: (1) pr_create via the fleet-write relay (POST /repos/objectstack-ai/objectui/pulls, draft, plus POST /issues/12847/assignees baozhoutao; read back 5759 bytes sent = 5759 stored); (2) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12810/comments via the relay. git push (2 pushes) is not a REST write. Zero label writes: the order named none, and objectui has no gate that reads them.", "gates": { "head": "20746107bd", "closure_build": 0, "type_check_app_shell": 0, "vitest_targeted": 0, "vitest_narrowed_metadata_admin_46_files": 0, "vitest_resource_edit_page_renderers_14_files": 0, "vitest_app_shell_full_dir": "NOT MEASURED: 1h48m with no output; killed my PID; declared for CI", "lint_changed_files": "1 (3 errors, identical on the base blob, pre-existing; 0 new)", "check:phantom-deps": 0, "check:self-import": 0, "check:unreferenced-sources": 0, "check:doc-example-readers": 0, "check:handler-key-reads": 0, "check:side-effects-array": 0, "check:element-data-source-declaration": 0, "check:spec-symbols": 0, "check:action-forward-parity": 0, "check:designer-field-key-parity": 0, "check:icon-record-names": 0, "check:i18n-keys": 0, "check:i18n-drift": 0, "check:i18n-designer-parity": 0, "check:control-bytes": 0, "check-hand-rolled-comment-mask": 0, "check-changeset-fixed": 0, "check-changeset-presence": 0, "check:spec-floors": "not run (the order says it may be red on main)", "ci": "in_progress (not awaited, per contract)" }, "files_changed": [ "packages/app-shell/src/views/metadata-admin/createBody.ts", "packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (the shared create-mode save gate, doSave)", "packages/app-shell/src/views/metadata-admin/i18n.ts (one key, en-US + zh-CN)", "packages/app-shell/src/views/metadata-admin/createBody.seedPlaceholders-12810.test.ts (new, enumeration pin)", "packages/app-shell/src/views/metadata-admin/ResourceEditPage.seedPlaceholder-12810.test.tsx (new, page pins a/b)", ".changeset/12810-seed-placeholder.md (@object-ui/app-shell: patch)" ], "deviations": [ "File surface: the new message key went into packages/app-shell/src/views/metadata-admin/i18n.ts (the designer catalogue, en-US and zh-CN), not packages/i18n/src/locales. A3 measured false; packages/i18n is untouched, so the changeset names only app-shell.", "Ruling 'read PLACEHOLDER_OBJECT from the spec': the constant is not exported by the installed spec, and a spec change is out of scope. It is read from the seeds instead (getMetadataCreateSeed over listMetadataCreateSeedTypes, the set of their object values). No literal copy exists in objectui source.", "The dataset designer was not edited. The refusal is surfaced by the shared page banner, with the field named by labelForIssuePath. In the page test that name is the key `object`, because the served dataset schema carries no title. It is not the inspector caption Base object.", "The full app-shell dir run was declared rather than measured (see tests).", "First ablation attempt was an empty operation (tool-refused, restored); re-run with --delete." ], "line_budget": "n/a (no skills/** or governed surface touched)", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: the dataset designer in create mode, driven through the real MetadataResourceEditPage and real DatasetDefaultInspector over a recording in-memory server (jsdom, not a live backend). Picking the first base object sent PUT body measures: []. · evidence: objectChangePatch(next, current) in inspectors/DatasetDefaultInspector.tsx returns measures: [] whenever next !== current, including from the empty value on a new dataset. So the normal create flow (pick an object, then save) drops the spec seed's count measure; buildCreateModeBody keeps it only when the draft has no measures key. Seen in this run's first page-test attempt: 'expected [] to deeply equal [ { name: count, aggregate: count } ]'. · dedupe words: dataset create count measure dropped, objectChangePatch measures reset, dataset seed measures empty, base object pick clears measures", "carrier: none (承接者:无) · noted, not filed: the object seed's pluralLabel `New Objects` is a label-family placeholder that the spec header does not list as identity; the create form derives it from Label, so it survives only when Label is blank. Recorded in PR Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Review: ACCEPT — objectui PR #12847 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T02:58ZReviewed on GitHub at head
2074610, against the card's Expected, the triage note (6226269923) and the claim (6227469766).Fixes #12810; the second line isClause-②: no. The changeset is@object-ui/app-shell: patch.buildCreateModeBody(createBody.ts) carries a spec create seed's structural defaults (a dataset'scountmeasure, a page'stype, …) but never its placeholder identity:name,label, and every top-level key whose value is the spec's placeholder object. Those come from the author or not at all.getMetadataCreateSeedoverlistMetadataCreateSeedTypes): the installed spec keepsPLACEHOLDER_OBJECTmodule-private, so there is no literal copy in objectui.doSaveinResourceEditPage.tsx; every designer uses it, A1 holds) refuses Save with no request when a placeholder key the author left blank is in the LIVE/meta/typesschema'srequiredlist, and names the key. It is never stricter than the server that judges the body. A dataset with no base object is refused; noPUTcarries the placeholder.metadata-admin/i18n.ts(en-US and zh-CN), which is where designer strings live, not the tenpackages/i18npacks (A3 measured false; accepted).page.objectis optional in the spec, so a page saves unbound rather than bound to the placeholder.createBody.seedPlaceholders-12810iterates every seed type (14 seed-carrying rows) and asserts each placeholder key is dropped or refused, so a future seed carrying a new placeholder turns it red.ResourceEditPage.seedPlaceholder-12810covers (a) a dataset with no object picked → refused, noPUT; (b) an object picked → the body carries it; plus controls. Ablation: deleting the line that drops placeholder keys turns 16 cases red (the (a) case, all 14 per-type rows, the dataset count case) while the controls stay green; the restore was proven by blob hash. 46 metadata-admin test files (275 tests) and 14 files renderingMetadataResourceEditPage(149 tests) are green;app-shelltype-check exits 0.countmeasure (objectChangePatchresetsmeasures: []on the first pick). Confirmed onmain.pluralLabel("New Objects") survives only when Label is blank, because the create form derives it from Label; it is not identity in the spec header.filter=latest, 36 runs). Not governed (path audit); 461 changed lines.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T03:12ZPR #12847 merged through the merge queue as
b921f90, which is onmain;Fixes #12810closed this card. Verified by content against the accepted head2074610:mainare blob-identical to that head.packages/app-shell/src/views/metadata-admin/i18n.ts, moved under the PR onmain(objectui#12844). The merge's own patch to it (b921f90^1..b921f90) is line-identical to the PR's.What landed:
name,label, the placeholder object binding). The placeholder object is read from the seeds, with no literal copy.@object-ui/app-shell: patch.Follow-up filed: objectui#12848 (picking the base object on a new dataset clears the seed's
countmeasure).This seat now removes
pm:dispatched.Generated by Claude Code