Repository navigation
list-view addRecord: the page host serves no onCreate, so a page node's Add button is withheld #13089
Description
Problem
A list-view page node's addRecord has no create path in the console. Two places show it:
- Page host.
packages/app-shell/src/views/pageRecordActions.tspageRelatedHandlersreturns the frozenNO_RELATED_HANDLERSwheneverinput.parentIdis empty (line 41 onmain), and under a parent it serves onlyonRowAction/onToolbarAction. So a pagelist-viewnode, which resolves with{ objectName }only, gets noonCreate. The objectui#12870 pin's CONTROL row asserts "no New" on a page list. - Console object list.
renderListViewpassesaddRecordwithoutonAddRecord. This is a source reading by the PR feat(plugin-list): page list-view honours requiredPermissions #13087 contract review and has not been measured.
objectui#13074 (PR #13087) publishes only requiredPermissions. addRecord stays unpublished, its parity row reworded to "the button is withheld on a page node". The block-level routing contract (useRelatedRecordActions().resolve({ objectName }).onCreate) is pinned by the block's own tests, so this card only has to make a host serve it.
Target
Product decision objectui#11111 decision 3 = B, and the target of objectui#13074: addRecord on a page node opens the create flow the object list uses.
servePageRecordActions/pageRelatedHandlersserve the bridge'sonCreate/createDisabledfor a list with no parent in scope.- Re-examine the objectui#12870 CONTROL row ("no New") as part of this change. That row was written deliberately, so the change it pins is a decision this card takes knowingly.
- Add a pin through the real
PageView, in the shape of the objectui#12870 file: clicking a pagelist-viewnode's Add button pushesform=new+formObject. - Then publish
list-view.addRecord(registry input, struck fromUNPUBLISHED_EXEMPTIONS, member pin, docs). - Measure item 2 and fix it in the same pass when it holds (same class of fault).
- Note: page
openChildFormignoreseditMode: 'page'; handle it onceonCreateis served.
Surface
packages/app-shell/src/views/pageRecordActions.ts, PageView tests, packages/plugin-list registration and README, apps/console/src/__tests__ parity, content/docs/plugins/plugin-list.mdx, and the console object-list render path for item 2.
Clause-②: yes (publishes addRecord).
Filed from the objectui#13074 contract review (PR #13087) by domain:ui seat 2 (session_01XVp19E1MYUTFM3dnkwz8gB).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 19, 2026
Triage: p2 ·
bug· lanedomain:ui·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T21:58ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
Filing-gate answers completed under ruling 「B」 (objectstack-ai/objectstack#24589).
domain:uiexecution seat, on the card's surface list.search_issues"page list-view node addRecord Add button withheld onCreate page host pageRelatedHandlers create flow", objectui, closed included: 6 hits.onCreateto a page node with no parent.Fresh read (objectui
origin/mainb0176214a9).pageRelatedHandlers(packages/app-shell/src/views/pageRecordActions.ts:41) returnsNO_RELATED_HANDLERSwheninput.parentIdis empty. Under a parent, it serves onlyonRowAction/onToolbarAction. The premise holds.Why p2. It follows the parent objectui#13074 (p2). The button is withheld rather than inert, so the failure is visible and nobody gets a wrong write.
Note. Item 2 (the console object list) is a source reading only, so measure it before widening. The objectui#12870 CONTROL row change is this card's to take knowingly, as the body says.
Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 14 (
priority:p2)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-13089-page-list-createWorktree:
objectui-issue-13089Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/app-shell/src/views/pageRecordActions.ts(pageRelatedHandlers/servePageRecordActions). A list with no parent in scope is served the bridge'sonCreate/createDisabled. Under a parent the served set is unchanged unless the dev measures that it must change.PageViewtests: the objectui#12870 pin file. Its CONTROL row ("no New") is re-examined knowingly, and a new pin goes through the realPageView: a pagelist-viewnode's Add pushesform=new+formObject.packages/plugin-list/src/index.tsx,ListViewBlock.tsxdocblocks,README.mdandcontent/docs/plugins/plugin-list.mdx: publishlist-view.addRecord.apps/console/src/__tests__/registry-inputs-spec-parity.test.ts: strike theaddRecordexemption and add its member pin.packages/app-shell/src/views/ObjectView.tsxrenderListView: only if the measured console object list draws a dead Add button (the card's item 2).openChildFormandeditMode: 'page': only if the measurement shows a page create ignores it where the object list honours it..changeset/13089-page-list-create.md.Stop on breach; explain in the report.
Container & model:
M,mode:subagent,model: opus(default tier; no path-derived mandate over this surface; the contract review runs at the review tier)Clause-②: yes (
list-view.addRecordis published on thelist-view/listregistrations)Responsibility: app-shell's page host (
pageRelatedHandlers) answers a parent-less list with no handlers | the related-records bridge already buildsonCreate(the global record-form overlay) | every pagelist-viewnode declaringaddRecord(the button is withheld today)Thread-read: 6278305198
Serial constraints cleared: objectui#13074 (
b017621) and objectui#13057 (cd4623d,ObjectView.tsx) landed. objectui#12870's pin is re-examined in this card by design. No open objectui PR touches these files (read at 22:24Z).Claimed at: 2026-10-19T22:30Z
Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 13089, "status": "needs_decision", "branch": "claude/issue-13089-page-list-create", "pr": null, "session": "session_01XVp19E1MYUTFM3dnkwz8gB", "premise_still_valid": true, "summary": "The card's premise holds on origin/main cd4623d063: pageRelatedHandlers returns NO_RELATED_HANDLERS for any resolve with an empty parentId, so a page list-view node's addRecord is withheld. Ruling 1's premise is false, so per that ruling nothing was widened and no PR was opened. Two page-path callers resolve without a parentId, not one: (1) ListViewBlock pageAddRecord resolves with objectName only (the intended target); (2) the record:related_list renderer (plugin-detail record-related-list.tsx) resolves with objectName, relationshipField and parentId null whenever its parent did not resolve. That is the 12870 file's case 'a list with no parent in scope'. It wires handlers.onCreate straight to RelatedList onNew, which draws the related-list-new button. I measured this in two legs through the real PageView with the 12870 harness. Leg 1 served the create pair parent-less (onCreate/createDisabled only): an unbound related list with no actions DRAWS related-list-new (probe green, 4/4). Leg 2 ran the origin/main pageRecordActions.ts, restored by git checkout HEAD with git diff HEAD empty: the same probe goes red (1 failed, 3 passed), so it draws no New today. Clicking that new button would open an unlinked create form (openChildForm without formLink) under a list whose parent failed to resolve. The probe was scratch only and is deleted; the worktree is clean at the base. Separately, the 12870 CONTROL row ('no New') covers a BOUND list, so under rulings 2 and 3 it would not need rewriting. Item 2 and ruling 6 were not measured because I stopped at the fork (see open_questions).", "tests": "Probe (scratch file PageView.probe13089.test.tsx = the 12870 harness plus one case: unbound record:related_list, no actions, waitFor related-list-new not null), run as pnpm exec vitest run --maxWorkers=1 under os-verify-lock (slot dev-13089). Leg 1, naive parent-less create-pair serving in pageRelatedHandlers: 'Tests 4 passed (4)', VERDICT command-exit 0, so New IS drawn. Leg 2, pageRecordActions.ts restored with git checkout HEAD (git diff HEAD on the path = 0 lines): 'x PROBE: an unbound related list (no parent in scope), no actions - does it draw New? 3021ms', 'Tests 1 failed | 3 passed (4)', VERDICT command-exit 1, so New is NOT drawn on main. No gate battery was run because no change is proposed for landing. NOT MEASURED: typecheck, lint and the check:* battery, reason: no diff.", "mcp_calls": "0", "api_writes": "1 - POST repos/objectstack-ai/objectui/issues/13089/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Also one git push creating the empty branch claude/issue-13089-page-list-create (a write-route probe, not REST).", "open_questions": [ { "question": "Ruling 1's fork: serving onCreate/createDisabled to every parent-less resolve also makes an unbound record:related_list on a page draw '+ New', which opens an unlinked child form. I measured this (see tests). How should the page host tell a list-view node from an unbound related list?", "options": [ "A - Key on the input shape: in pageRelatedHandlers, serve the create pair only when parentId is empty AND relationshipField is absent. A spec-valid record:related_list always resolves with relationshipField: the spec's RecordRelatedListProps declares relationshipField as a required z.string(). ListViewBlock resolves with objectName only. Declare that meaning on ResolveRelatedRecordActionsInput's docblock in @object-ui/react so it is a contract and not a coincidence. Cost: one branch plus the card's planned pins, plus a control that an unbound related list still draws no New. About the card's original effort. Four axes: real need (objectui#11111 decision 3 = B); long-term acceptable once the docblock declares it; AI-safety: a related list missing relationshipField is already refused at publish by the strict spec, so the branch cannot be reached by valid metadata; smallest surface, no new API.", "B - Add an explicit discriminator to the published context: a new ResolveRelatedRecordActionsInput member (for example a list-scope flag) or a dedicated create member on RelatedRecordActionsValue. ListViewBlock calls it and the page host serves it. Cost: a public @object-ui/react API change, a ListViewBlock code change outside the claimed file surface (needs a re-claim), and more pins. Four axes: same need; cleanest contract-first long-term; AI-safety is equal to A; but it grows a published surface in the startup phase.", "C - Serve the create pair to all parent-less lists and accept that an unbound related list draws New. Cost: the smallest code. Four axes: the New creates orphan children under a list whose parent failed to resolve, and it reverses the 12870 posture; not recommended.", "D - Serve parent-less, but have record-related-list withhold onNew when parentLinkValue is null. Four axes: this is consumer-side narrowing (AGENTS.md #0.1, contract-first) and it touches the record-page loading state as well; not recommended." ], "recommendation": "A. It covers the real need, adds no published surface (startup focus), cannot be reached by spec-valid metadata because the strict spec requires relationshipField on record:related_list, and becomes a declared contract through the input docblock. B is the fallback if the maintainer wants the discriminator to be explicit and not a shape reading." }, { "question": "Item 2 and ruling 6 were left unmeasured because I stopped at the fork. Should they ride the same re-dispatch?", "options": [ "A - Yes, the same card. Source reading only, NOT MEASURED at runtime: ListView draws Add whenever addRecord.enabled === true (toolbarFlags.showAddRecordTop/Bottom) and its onClick is props.onAddRecord?.(). ObjectView renderListView forwards addRecord (viewDef.addRecord ?? listSchema.addRecord) and passes no onAddRecord, so a saved view declaring addRecord.enabled probably draws a dead Add button. For ruling 6, RelatedRecordActionsBridge openChildForm always pushes form/formObject and never reads editMode.", "B - Split item 2 into its own card." ], "recommendation": "A. It is the same fault class, and the card already scopes it." } ], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Seat ruling on the dev's open questions (os-dev report
6278911130) ·domain:uiseat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)The dev stopped at the claim's premise gate and measured the fork. Through the real
PageView, serving the create pair to every parent-less resolve makes an unboundrecord:related_list(parent not resolved) draw+ New. That+ Newwould open an unlinked child form. Onmainit draws none.Q1: letter A. The page host tells a list-view node from an unbound related list by the input's shape, and that shape becomes a declared contract.
packages/spec/src/ui/component.zod.ts,RecordRelatedListProps, declaresrelationshipField: z.string()as required. A spec-valid related list therefore always resolves with arelationshipField, andListViewBlockresolves withobjectNamealone.pageRelatedHandlersservesonCreate/createDisabled(only that pair) whenparentIdis empty andrelationshipFieldis absent or empty. Every other parent-less input still getsNO_RELATED_HANDLERS. Under a parent, the served set is unchanged.ResolveRelatedRecordActionsInput's docblock inpackages/react/src/context/RelatedRecordActionsContext.tsxstates the meaning: an input with norelationshipFieldasks for an object-scoped list's actions, not a related list's, and a related list always names itsrelationshipField. This is a docblock only, with no type or member change. Claim amended: that file joins the file surface for the docblock alone. If the presence check requires it, add an@object-ui/reactpatchline to the changeset.relationshipField.relationshipField, its parent is unresolved) draws no+ New; this is the dev's probe, kept as a pin;relationshipFieldcondition, and the unbound-related-list control goes red.Q2: letter A. Item 2 (the console object list's
addRecordwithoutonAddRecord) and ruling 6 (openChildFormvseditMode: 'page') ride this same dispatch. Measure first, as the dispatch order says, and then fix or report.The rest of the dispatch order stands. The dev resumes on branch
claude/issue-13089-page-list-create.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 13089, "status": "done", "branch": "claude/issue-13089-page-list-create", "pr": "https://github.com/objectstack-ai/objectui/pull/13098", "session": "session_01XVp19E1MYUTFM3dnkwz8gB", "premise_still_valid": true, "summary": "Implemented under the seat ruling 6278934428 (Q1 = A, Q2 = A). The page host's pageRelatedHandlers serves only onCreate/createDisabled, and only when parentId is empty AND relationshipField is absent or empty (a page list-view node). An unbound related list still gets nothing, and under a parent the served set is unchanged. The ResolveRelatedRecordActionsInput docblock in @object-ui/react declares that shape and names its limit; no type change. Item 2 was measured red on base: a saved view's addRecord drew an Add button whose click did nothing, also for a viewer without a create grant. ObjectView renderListView now wires it to the header New's create (actions.create = onEdit(null)) and withholds it where the New is not offered or is greyed. Ruling 6 was measured red on base: a page create of an editMode 'page' object opened the overlay. openChildForm now has one branch: a create with no link goes through resolveRecordFormTarget and navigates to the full-page create route. list-view.addRecord is published: registration input (enabled and position read; mode and formView not), exemption row struck, MEMBER_PINS entry on the real-PageView pin, README, mdx and changeset. Two pre-existing pins asserted the old fact and were rewritten: the 11293 navigator row (resolve({objectName}) equals {}) and the 13074 registration row (addRecord not published).", "tests": "HEAD 1fbb3d66ac (base cd4623d063). New PageView.listViewAddRecord-13089 (7 tests) covers pins a, b and c, plus the probe-block served-set control and the unbound related-list no-New control. The 12870 file is unchanged and green (10/10 with the new pin). New ObjectView.addRecordOpensCreate-13089: on base 'Tests 2 failed | 1 passed (3)' (onEdit called 0 times; Add drawn without a create grant); after the fix 3/3. Ruling 6 was red before the openChildForm branch: \"expected '/apps/cloud/page/team' to be '/apps/cloud/sys_contract/new'\". Family runs (vitest --maxWorkers=1 under os-verify-lock): 24 files PageView/RelatedRecordActionsBridge/readers 148 passed and 1 failed, the 11293 row, rewritten, then 6/6; 72 ObjectView* files 633/633; 15 files (console registry-inputs-spec-parity, plugin-list addRecord readers, plugin-detail/plugin-form/components bridge readers) 445 passed and 1 failed, the 13074 registration row, rewritten, then 15/15. Ablations via objectstack scripts/ablation-replace.mjs, each mutation anchor x1 to x0 on disk, each restore blob 47fc21a275c6 equal to HEAD with git diff HEAD empty: (1) parent-less onCreate removed: 4 failed, including (a) and the controls; (2) every handler served parent-less: served-set control red, received onCreate,onDelete,onEdit,onRowAction,onToolbarAction,onView,rowActions,toolbarActions; (3) relationshipField condition dropped: 2 failed, the unbound related list draws + New and the probe reads onCreate. Type-check: closure build of '@object-ui/app-shell^...' then type-check for react, plugin-list and app-shell, VERDICT command-exit 0. apps/console tsc: NOT MEASURED, reason: its closure (app-shell dist and plugins) was not built (TS2307 cannot find module), a prerequisite failure and not a verdict; the only console file touched is a test, green under vitest. Narrowed eslint --no-inline-config over the 12 changed ts/tsx files: 0 errors, 203 warnings, all pre-existing apart from one no-explicit-any on the new objects.find lookup. Battery at HEAD, all exit 0: phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, new-line-citations, check-hand-rolled-comment-mask, check-changeset-fixed and check-changeset-presence. check:spec-floors exit 1, as on main, recorded and not fixed. Whole package directories (app-shell, plugin-list, react, apps/console) are declared to CI. CI: in_progress, not awaited.", "mcp_calls": "0", "api_writes": "5 REST writes across both rounds, all through the fleet-write relay: POST issues/13089/comments (round-1 os-dev-report 6278911130); POST pulls (draft PR 13098) plus POST issues/13098/assignees [baozhoutao] (one pr_create stroke, read back identical); POST issues/13089/comments (this os-dev-report). Git pushes: the empty branch plus four commits.", "open_questions": [], "out_of_scope_findings": [ "carrier: none (no carrier) - observation, unmeasured reach: the spec's AddRecordConfig defaults enabled to true and position to bottom, while ListView draws only an explicit enabled: true and puts an absent position at the top. The published description states the renderer's behaviour. Noted in the PR's Acceptance notes, not filed.", "carrier: PR 13098 - behaviour note: the openChildForm unlinked-create branch also covers a record page's related list whose parent link value is null; for an editMode 'page' object that create now navigates to the full-page route and not the overlay. In Acceptance notes." ], "deviations": "Files touched beyond the claim's list, all as test or comment updates forced by the changed fact: packages/app-shell/src/views/__tests__/PageView.recordNavigator-11293.test.tsx (one row rewritten), packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx (registration row and docblock), packages/plugin-list/src/__tests__/ListViewBlock.specInputs-13047.test.tsx (docblock and title only), and packages/app-shell/src/views/RelatedRecordActionsBridge.tsx (ruling 6, named by the claim). Round 1 returned needs_decision; this round resumed per the seat ruling. Changeset bumps app-shell, plugin-list and react as patch.", "files_changed": [".changeset/13089-page-list-create.md", "apps/console/src/__tests__/registry-inputs-spec-parity.test.ts", "content/docs/plugins/plugin-list.mdx", "packages/app-shell/src/views/ObjectView.tsx", "packages/app-shell/src/views/ObjectView.addRecordOpensCreate-13089.test.tsx", "packages/app-shell/src/views/RelatedRecordActionsBridge.tsx", "packages/app-shell/src/views/pageRecordActions.ts", "packages/app-shell/src/views/__tests__/PageView.listViewAddRecord-13089.test.tsx", "packages/app-shell/src/views/__tests__/PageView.recordNavigator-11293.test.tsx", "packages/plugin-list/README.md", "packages/plugin-list/src/ListViewBlock.tsx", "packages/plugin-list/src/index.tsx", "packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx", "packages/plugin-list/src/__tests__/ListViewBlock.specInputs-13047.test.tsx", "packages/react/src/context/RelatedRecordActionsContext.tsx"] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Contract review
Served-tier:
CONTRACT_REVIEW_TIERHead-sha:
1fbb3d66ac2bf3d7cc8e122032759b6122456630Local-runs: none
Inputs: card #13089 (body, triage
6278305198, claim6278806000, dev reports6278911130/6279685602, seat ruling6278934428), PR #13098 (body, 15 files, net diff of the head againstmain90518cdb84), the head's 35 check-runs, and read-only source reads of the base and head files the diff touches plus the spec source (@objectstack/specview.zod.tsAddRecordConfigSchema,component.zod.tsRecordRelatedListProps) and objectstack's metadata write door (protocol.tsprojectStorableViewBody). Nothing built, run or re-run.Check-runs on the head. 9 completed, all
successorskipped: Test (dist pins), Changeset Fixed Group Check, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, label. Still in progress at review time: all 16 Test shards, Type Check, Lint, Build & E2E, Build Docs, Repo Gates, Bundle Analysis, Live E2E. No run has failed. The derived gate families (vitest, tsc, eslint) therefore have no CI verdict yet; this record does not wait for them.① Derived judgments
Q1-A, the page host (
pageRecordActions.ts) — right.pageRelatedHandlersnow reads: norelated→ nothing;hasParent = parentId != null && parentId !== ''; parent-less ANDrelationshipFieldpresent and non-empty →NO_RELATED_HANDLERS; parent-less ANDrelationshipFieldabsent or''→{ onCreate, createDisabled? }picked out ofrelated.resolve(input), nothing else; under a parent the objectui#12870 executor branch is byte-for-byte the base's. That is the ruling's rule exactly: only the create pair, only for no parent and no relationship, the under-parent set unchanged, and an unboundrecord:related_list(which always resolves with itsrelationshipField,record-related-list.tsxrelatedActions?.resolve({ objectName, relationshipField: schema.relationshipField, parentId: parentLinkValue })) still gets no+ New. The spec premise holds:RecordRelatedListProps.relationshipFieldisz.string()with no.optional(), so a spec-valid related list cannot reach the create branch. The servedonCreateis the bridge's own closure, whosecanLinkis false for a{ objectName }input, so the create carries noformLink.The docblock contract (⚠️ paragraph names the exact limit the ruling asked for: a future parent-less, relationship-less consumer that must NOT be offered a create needs an explicit member, not a second reading of the shape. No type or member moved.
ResolveRelatedRecordActionsInput) — sound, and it names its limit. It states both halves (a related list always names itsrelationshipField; an input without one asks for an object-scoped list's handlers), says what a host may serve to the one and must not serve to the other, and theThe real-
PageViewpin (PageView.listViewAddRecord-13089.test.tsx) — right, and the ablations distinguish. It mounts the realPageView, the realplugin-listandplugin-detailregistrations and@object-ui/components, and reads the router's location: (a)form=new,formObject=sys_member, noformLink, pathname unchanged; theeditMode: 'page'object lands on/apps/cloud/sys_contract/newwith noformparam; no create grant and adisabledWhen: 'true'create both withhold the button; a node withoutaddRecorddraws none; the unbound related list beside a servedlist-viewdraws norelated-list-new; and a probe block reads the served key set per input shape (onCreatealone for{ objectName }and forrelationshipField: '', empty for the unbound related input,onRowAction,onToolbarActionbound). The three ablations the PR reports each go red on a different row: (1) no parent-less serving → (a), theeditModecase and both controls; (2) serve everything → the served-set probe; (3) drop therelationshipFieldcondition → the unbound control and the probe's unbound row. That is the seat's ablation plus two, each separable. Note the PR's measurements are the dev's claim, not re-run here; the test content supports them.Item 2 (
ObjectView.tsxrenderListView) — right.listAddRecordOpensCreate = objectCanCreate && createVisible && !createDisabledis derived from the SAME three values the header New reads (showCreate = objectCanCreate && createVisible,disabled={createDisabled},onClick={actions.create}), andonAddRecordisactions.create, which isuseObjectActions'screate = () => onEdit?.(null): the sameonEdit(null)the New calls, routed byAppContent.handleEditthroughresolveRecordFormTarget(overlay oreditMode: 'page'route). The pin asserts the two clicks make equalonEditcalls. Withholding where the New is greyed (rather than greying) is right for this surface:ListView's Add has no disabled affordance (onClick={() => props.onAddRecord?.()}, nodisabledprop), so the alternatives were a dead button or none; none matches the plugin-list half (pageAddRecordwithholds oncreateDisabled === true).addRecordis dropped from the schema in the same condition, so aposition: 'bottom'/'both'button is withheld too, not left dead. The memo dependency list gained the boolean (AGENTS.md #10 primitive). Measured red on base per the PR (onEditcalled 0 times; drawn for a viewer without the create grant) — consistent with the base source, which forwardedaddRecordwith noonAddRecord.Ruling 6 (
RelatedRecordActionsBridge.openChildForm) — sound; the null-parent-link side effect is acceptable, not a regression. The single branch fires only onrecordId == null && !opts.link, resolves the child object fromobjectsand calls the sameresolveRecordFormTargetAppContent.handleEdituses withrecord: null, so a non-pageobject falls through to the unchanged overlay push. The only other caller that can reach the branch is the bridge's ownhandlers.onCreatewithcanLinkfalse, which on a RECORD page happens exactly whenparentLinkValueis null (the parent still loading, or a non-idrelationshipValueFieldnot yet resolved). On base that click already opened an UNLINKED overlay create (noformLink); on this head aneditMode: 'page'child instead navigates to its unlinked full-page create. Both are the same orphan object-level create; the head's destination is the one the object declares. The cost is losing the mounted parent page under the overlay for that transient window; it is not a wrong write and not a widening. Acceptable, and the PR names it in its acceptance notes. Dependency list updated (objects, base, navigate).The rewritten rows — legitimate successors. 11293:
resolve({ objectName: 'account' })equalled{}because the page served nothing; the successor asserts{}for a related-list input (relationshipField,parentId: null) and exactly['onCreate']for the object-scoped input, which is the new fact, not a weakening. 13074:inputsnot containingaddRecordwas the deliberate hold-out that the card struck;toContain('addRecord')besidenot.toContain('sharing')keeps the control. The objectui#12870 CONTROL row ("no New") is on a BOUND list (mount(listBlock(BOUND))) and is untouched, as the ruling required; its "no parent in scope" row asserts no toolbar/row actions, which the head still satisfies (an unbound related list is served nothing).Publishing (
plugin-list/src/index.tsx, parity gate, README, mdx) — the words match the code, with one declared-default conflict (below).enabled === true && ua?.addRecordForm !== falseandresolveAddRecordPlacement(topfor absent/'top',bottom,both) are whatListView.tsxreads, and the description names all of it includinguserActions.addRecordForm: false.modeandformVieware read nowhere inListView.tsx, as stated. TheUNPUBLISHED_EXEMPTIONSrow is struck andMEMBER_PINS['list-view.addRecord']points at the real-PageViewfile. ThespecInputs-13047row keeps the block's half (no host create, no button) with its title de-carded. README and mdx state the console host's two destinations and the three withholding cases. TheListViewBlock.tsxdocblock no longer says the page host serves none.Spec defaults vs the renderer — measured from source, and it is a separate card, not this PR's. The spec's
AddRecordConfigSchemaisstrictObjectwithenabled: z.boolean().default(true),position: enum.default('bottom'),mode: enum.default('inline'),formViewoptional;addRecorditself is.optional()on the list view row (and onInterfacePageConfigSchema). Materialisation: (i) an ABSENTaddRecordstays absent after the parse (the outer.optional()has no default), so no page or view starts drawing Add by default under either reading; (ii) objectstack's write door stores a PAGE body verbatim after the gate ("every other body is judged as stored"), and a VIEW body throughprojectStorableViewBody, whosebuildStorableViewNodekeeps only keys the request carried and skips parsed-only keys on the first round, soaddRecord: {}is stored as{}with the nested defaults NOT materialised; (iii) objectui parses nothing on the client (safeParse/parseof the list-view schema occurs only in tests). Consequence: the renderer never sees the spec's defaults, so an authoredaddRecord: {}or{ position: 'top' }— spec-valid and, by the spec's declaration, an enabled button — draws nothing, and{ enabled: true }draws at the top where the spec declares bottom. This divergence predates the PR (ListViewhas readenabled === trueon the object list since before objectui#13047) and is shared by the object list, so honouring the spec's defaults is not this card's change: it would move every existing{ enabled: true }button from top to bottom and switch on partial objects on every saved view and page, andmode: 'inline''s default names an unimplemented behaviour that needs the ADR-0049 enforce-or-remove decision on the spec side. What this PR owes is that the published words not present the renderer'stopas THE default: the description and README say"top" (the toolbar, also when absent)and the mdx"top", the default here, with no mention that the spec row declaresenabled: trueandposition: 'bottom'as its defaults. Under "the spec row is the judge of values" (the registration's own docblock), that sentence must name the divergence and the card that owns it. Required fix, see ②/③.② Semver level
Clause-②: yes —
addRecordjoinsLIST_VIEW_INPUTS(both thelist-viewandlistregistrations), struck fromUNPUBLISHED_EXEMPTIONS, so the console parity gate's accept set widens by one key; theResolveRelatedRecordActionsInputdocblock is a published contract statement with no type change;openChildFormandrenderListViewchange console behaviour on three paths.The changeset declares
@object-ui/app-shell,@object-ui/plugin-listand@object-ui/reactallpatch. Judged levels:@object-ui/plugin-list:minor, notpatch. A registry input joininginputsis additive public surface (the parity gate's accept set, the registration consumers read), the same kind of change objectui#13074 (.changeset/13074-list-view-inert-keys.md) declaredminorforrequiredPermissions, and the PR's own type isfeat(…,plugin-list). No objectui gate scores the level (the four structural gates judge presence, no-major, fixed group and overwrite;check-changeset-no-major.mjsis the only level rule, and AGENTS.md §版本号策略 says breaking →minor, nevermajor), so this is a judgment, and the precedent in this card's own lineage isminor. In the fixed group the group bump is the highest declared level, so as written the whole group ships this feature as a patch release and the CHANGELOG files it under Patch Changes.@object-ui/app-shell:minoris the consistent reading,patchis defensible. The page host gains a served capability and a new navigation path (featby the PR's own title); the object-list half is a plain bug fix (a dead button). With plugin-list atminorthe group level is alreadyminor, so app-shell's own line does not move the version; it should match the PR's type.@object-ui/react:patch— right. A docblock only, but it ships in the.d.ts, so a declared line (not an empty frontmatter) is correct.The changeset body states what the diff does and nothing the diff does not; the Changeset Claim Re-read, Declaration and Bump Policy check-runs all passed on the head.
③ Boundary flags
enabled: true/position: 'bottom') vs the renderer (explicittrueonly / absent → top). Answered in ①: measured from the spec source and objectstack's write door — defaults are not materialised into a stored page or view and not applied on the client, so the renderer never sees them; an absent key is unaffected, a partial object diverges. Escalated as a separate card (declared-but-unenforcedAddRecordConfigdefaults andmode/formView, both repos, ADR-0049 enforce-or-remove), NOT fixed in this PR. What this PR must do: the registry description, README and mdx must say that the spec row declaresenableddefaulttrueandpositiondefaultbottom, that the renderer reads only an explicitenabled: trueand places an absentpositionat the top, and name the card that owns the gap — instead of presentingtopas the default.openChildFormbranch also covers a record page's related list whose parent link value is null. Answered in ①: acceptable. Base already opened an unlinked create there; the head sends aneditMode: 'page'child to its unlinked full-page create instead of the overlay. Same orphan create, destination now the object's declared one. No pin covers the record-page case; none is required for this card, but the follow-up card in flag 1 (or a record-page card) may want one.visibleWhenpredicate readingrecord.*fails closed on a page (no parent record). Consistent with the object list (useRowPredicate(…, null, { fallback: false })) and the objectui#3492 invariant. No action.open_questionson the card: round 1's two questions were ruled (6278934428, Q1 = A, Q2 = A) and the head implements both; round 2'sopen_questionsis empty. Nothing outstanding.RelatedRecordActionsBridge.tsxwas named by the claim under ruling 6): legitimate, each a successor of the fact the card changed.apps/consoletscNOT MEASURED locally by the dev (unbuilt closure) and declared to CI; Type Check, Lint and the Test shards are still in progress on the head. The adopting seat should not merge before they complete.Required before a PASS (two edits, no design change): (a)
.changeset/13089-page-list-create.md:@object-ui/plugin-list→minor(and@object-ui/app-shell→minorto match thefeattype); (b) theaddRecordinput description inpackages/plugin-list/src/index.tsx,packages/plugin-list/README.mdandcontent/docs/plugins/plugin-list.mdx: name the spec row's declared defaults (enabled: true,position: 'bottom') and that the renderer diverges from them, with the follow-up card number once filed, rather than stating"top"as the default.Implemented-by:
claude/issue-13089-page-list-createReviewed-by:
session_01XVp19E1MYUTFM3dnkwz8gBVERDICT: FAIL
Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Seat ruling on the contract review (FAIL) of PR #13098 ·
domain:uiseat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)The review (the record above) judged the design right throughout:
relationshipField, and the set under a parent is unchanged;PageViewpin has three separable ablations;onEdit(null);openChildFormbranch is sound, and the null-parent-link side effect is acceptable;It FAILs on two correctable points. Both are adopted:
Changeset levels.
@object-ui/plugin-list→minor, because a registry input joinsinputs, as objectui#13074 did forrequiredPermissions.@object-ui/app-shell→minor, to match thefeat.@object-ui/reactstayspatch(docblock only).Published words vs the spec's declared defaults. The spec row declares
enableddefaulttrueandpositiondefault'bottom'. The renderer reads only an explicitenabled: trueand places an absentpositionat the top. TheaddRecordinput description (packages/plugin-list/src/index.tsx),packages/plugin-list/README.mdandcontent/docs/plugins/plugin-list.mdxmust:"top"as THE default.The renderer behaviour is not changed in this PR. Enforcing the defaults would move every
{ enabled: true }button and switch on partial objects across saved views and pages, so it is the enforce-or-remove decision filed as objectui#13099 (both repositories, ADR-0049).Patch round on branch
claude/issue-13089-page-list-create: those two edits only, then targeted tests, the parity test, and push. The seat re-reviews the delta.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 13089, "status": "done", "branch": "claude/issue-13089-page-list-create", "pr": "https://github.com/objectstack-ai/objectui/pull/13098", "head": "3b8fab4ff1", "session": "session_01XVp19E1MYUTFM3dnkwz8gB", "premise_still_valid": true, "summary": "Patch round for contract review 6279828749 (FAIL) under seat ruling 6279838726, with only the two required edits and no renderer behaviour change. (1) .changeset/13089-page-list-create.md: @object-ui/plugin-list and @object-ui/app-shell go from patch to minor; @object-ui/react stays patch. (2) The addRecord description in packages/plugin-list/src/index.tsx, packages/plugin-list/README.md and content/docs/plugins/plugin-list.mdx now says three things. The spec row declares enabled default true and position default \"bottom\". The renderer does not apply those defaults: it draws the button only for an explicit enabled: true and places an absent position at the top. objectui#13099 owns that gap. \"top\" is no longer presented as the default. One commit, 3b8fab4ff1, pushed. The PR body was not edited (the seat PATCHes it).", "tests": "HEAD 3b8fab4ff1, under os-verify-lock with vitest --maxWorkers=1: registry-inputs-spec-parity, PageView.listViewAddRecord-13089, ObjectView.addRecordOpensCreate-13089, ListViewBlock.inertKeys-13074, ListViewBlock.specInputs-13047 and add-record-position-spec-parity gave 'Test Files 6 passed (6)', 'Tests 338 passed (338)'. Then the closure build of '@object-ui/plugin-list^...' and the @object-ui/plugin-list type-check (tsc --noEmit plus tsconfig.test.json): VERDICT command-exit 0. Lockless gates at HEAD, all exit 0: check:doc-example-readers, check:control-bytes, check:new-line-citations, check:spec-symbols, check-changeset-fixed, check-changeset-presence, check-changeset-no-major, and eslint --no-inline-config on packages/plugin-list/src/index.tsx. No ablation this round: no logic changed. CI: in_progress, not awaited.", "mcp_calls": "0", "api_writes": "6 REST writes across all rounds, all through the fleet-write relay. Earlier: comment 6278911130; pr_create 13098 (POST pulls plus POST issues/13098/assignees); comment 6279685602. This round: POST issues/13089/comments (this os-dev-report). Git pushes: the empty branch plus five commits.", "open_questions": [], "out_of_scope_findings": [], "deviations": "None this round. The four edited files are the ones the seat named.", "files_changed": [".changeset/13089-page-list-create.md", "content/docs/plugins/plugin-list.mdx", "packages/plugin-list/README.md", "packages/plugin-list/src/index.tsx"] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Contract review
Served-tier:
CONTRACT_REVIEW_TIERHead-sha:
3b8fab4ff1010b45ce8fba9192e4b4bad679d4deLocal-runs: none
Re-review of the patch round. Inputs: card #13089 (body, triage
6278305198, claim6278806000, dev reports6278911130/6279685602/6279903518, seat rulings6278934428/6279838726, the prior contract review6279828749at1fbb3d66ac), PR #13098 (body, 15 files, net diff of the head againstmain90518cdb84), the patch commit3b8fab4ff1read on its own (file list and patch), the head's 35 check-runs, and read-only source reads ofpackages/plugin-list/src/ListView.tsxat the head and of the spec sourcepackages/spec/src/ui/view.zod.tsAddRecordConfigSchema. Nothing built, run or re-run.Check-runs on the head. 12 completed
success: Build Docs, Build & E2E, Test (dist pins), Live E2E (informational), Changeset Fixed Group Check, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, label. 3skipped(coverage, coverage shard, dependabot). Still in progress at review time: all 16 Test shards, Type Check, Lint, Repo Gates, Bundle Analysis. No run has failed. This record does not wait for the in-progress ones.① Derived judgments
The design is not re-litigated; the prior record passed it and the ruling adopted that reading. This section judges the two required edits and the patch commit's reach.
Edit 1, the changeset levels — done exactly.
.changeset/13089-page-list-create.mdnow reads'@object-ui/app-shell': minor,'@object-ui/plugin-list': minor,'@object-ui/react': patch. The patch commit's hunk on this file is the two level lines only (2+/2-); the body is byte-for-byte the prior head's, and it still states what the diff does and nothing it does not.Edit 2, the
addRecorddescription in three places — done, and each sentence is true toListView.tsx's reads at this head. The checks, against the renderer source at3b8fab4ff1:enabled: true": line 1688,const addRecordEnabled = schema.addRecord?.enabled === true && ua?.addRecordForm !== false;. A strict=== trueread, so an absent or non-booleanenableddraws nothing. Right.positionat the top":resolveAddRecordPlacement(lines 717–727) maps'bottom'→ bottom,'both'→ both, and'top'anddefault→{ top: true, bottom: false }. Right. (The function's own docblock, lines 709–712, already records that the spec's default isbottomand that this renderer keeps its historicaltop; the published words now agree with it.)enableddefaulttrueandpositiondefault"bottom"":AddRecordConfigSchema(view.zod.ts1778–1786) isstrictObjectwithenabled: z.boolean().default(true),position: z.enum(['top','bottom','both']).default('bottom'),mode: z.enum([...]).default('inline'),formView: z.string().optional(). Right.modeandformVieware not read": no read ofaddRecord.modeoraddRecord.formViewanywhere inListView.tsx(the onlymode/formViewhits are unrelated keys and a comment). Right.userActions.addRecordForm: falsealso takes the button off" (registration description) and the placement vocabulary"top"(the toolbar, line 6060) /"bottom"(under the list, line 6342) /"both": both match the reads above. Right."top"is no longer presented as THE default: the prior wording"top" (the toolbar, also when absent)(index.tsx, README) and"top", the default here(mdx) is gone from all three; the surviving phrase is"top"(the toolbar) with the absent-position behaviour stated as a divergence from the spec's declared default. Right.The three texts are consistent with one another (same facts, same card cited). The README and mdx add "until it lands, write both keys": sound advice under the renderer as it stands, and not a contract claim. The index.tsx docblock change from the earlier round (
addRecordmoved out of the not-published list, with the pin named) is unchanged this round.Nothing else is changed in the patch commit.
3b8fab4ff1touches exactly the four files the ruling named — the changeset (2+/2-),content/docs/plugins/plugin-list.mdx(5+/2-),packages/plugin-list/README.md(7+/3-),packages/plugin-list/src/index.tsx(1+/1-, the description string only); 15+/8- in total. No code path, test, or type moved. The dev's "no ablation this round: no logic changed" is therefore correct.The prior record's ① stands for everything else. The PR's commit list is linear (
325cada8→ec3722e0→f54cbf27→1fbb3d66→3b8fab4f), and the head commit's file list is the four files above, so every other blob at3b8fab4ff1is identical to1fbb3d66ac:pageRecordActions.ts(create pair only for no parent and norelationshipField, under-parent set unchanged), theResolveRelatedRecordActionsInputdocblock,RelatedRecordActionsBridge.openChildForm,ObjectView.renderListView, the real-PageViewpin with its three separable ablations, the console parity row andMEMBER_PINSentry, and the rewritten 11293 / 13074 rows. The net diff of the head againstmainconfirms the same 15 files as the prior round. Those judgments are not re-opened.② Semver level
Clause-②: yes — unchanged from the prior record:
addRecordjoinsLIST_VIEW_INPUTSfor thelist-viewandlistregistrations and leavesUNPUBLISHED_EXEMPTIONS, widening the console parity gate's accept set by one key; the@object-ui/reactdocblock is a published contract statement with no type change; the console behaviour changes on three paths.The new levels are right:
@object-ui/plugin-list:minor— a registry input joininginputsis additive public surface; this is the same shape objectui#13074 declaredminorforrequiredPermissions, and matches the PR'sfeattype.@object-ui/app-shell:minor— the page host gains a served capability and a new navigation path; matches thefeattype and the group level.@object-ui/react:patch— a docblock shipped in the.d.ts, no member or type change; a declaredpatchline is correct.The fixed group ships this as a minor release, which is what the diff publishes. Changeset Fixed Group Check, Changeset Bump Policy, Changeset Declaration and Changeset Claim Re-read all passed on this head; the dev reports
check-changeset-no-majorexit 0.③ Boundary flags
openChildFormalso covers a record page's related list with a null parent link) — stands as answered: acceptable, no code moved this round.visibleWhenreadingrecord.*fails closed parent-less) — stands: observation only, no action.6279903518:open_questionsempty,out_of_scope_findingsempty,deviations"None this round" — verified: the four edited files are the ones the seat named, and nothing else is in the commit. Nothing outstanding.successon this head. Type Check, Lint, the 16 Test shards and Repo Gates are still in progress;apps/consoletscremains NOT MEASURED locally by the dev and is declared to CI. No run has failed. The adopting seat should not merge before the in-progress runs complete; their outcome is a CI fact, not a contract one, and the only change since the prior head is prose and two changeset levels.Both required edits are done exactly and truthfully; the patch commit changes nothing else; the prior ① stands on identical blobs.
Implemented-by:
claude/issue-13089-page-list-createReviewed-by:
session_01XVp19E1MYUTFM3dnkwz8gBVERDICT: PASS
Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
ACCEPT — PR #13098 @
3b8fab4ff1010b45ce8fba9192e4b4bad679d4de·domain:uiseat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)6278934428: Q1 = A (create pair only, for no parent AND norelationshipField, declared onResolveRelatedRecordActionsInput); Q2 = A.6279828749at1fbb3d66ac. The design was judged right, with two correctable points: the changeset levels, and the published words against the spec's declaredaddRecorddefaults. Ruling6279838726adopted both. The patch round (report6279903518) was re-reviewed: PASS,6279985585.list-viewnode's Add opens the object's create form (the overlay, or the full-page route foreditMode: 'page'), withheld without a create grant;list-view.addRecordis published;+ New;openChildFormhonourseditMode: 'page'for an unlinked create (measured red on base).@object-ui/plugin-listminor,@object-ui/app-shellminor,@object-ui/reactpatch. Clause-②: yes.AddRecordConfigdefaults (enabledtrue,position'bottom',mode'inline') are not applied by the renderer. It goes through enforce-or-remove (ADR-0049) across both repositories;editMode: 'page'child create to the full-page route. The review judged this acceptable: the same unlinked create, now going where the object declares;visibleWhenreadingrecord.*fails closed parent-less, as on the object list.Next: ready + auto-merge; landing is verified by content on
main.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gBmaind11174ad10.d11174ad10as at the accepted head3b8fab4ff1. The merge message carries no model identifier.list-viewnode's Add opens the object's create form (the overlay, or the full-page route foreditMode: 'page');list-view.addRecordis published;+ New;openChildFormhonourseditMode: 'page'for an unlinked create.addRecorddefaults against the renderer (enforce or remove).pm:dispatchedis removed.Generated by Claude Code