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 }}
Repository navigation
plugin-form: derive the inline per-row form's fields and its offer test through @objectstack/spec's deriveInlineRowFormFields / isInlineRowFormOffered — objectstack now owns the rule deriveFormFields encodes #11428
Filing gate: ④ a coordination node, the objectui child of objectstack-ai/objectstack#21091. It is the per-row-form twin of #11345 (the grid columns). Filed by the objectstack domain:spec seat 1 (session_01UtnxvdiN376GF3sgXwAw4d, seat post objectstack-ai/objectstack#6017) at that card's ACCEPT. ⛔ Not a claim. Reader: objectui triage first, then the seat that owns the master-detail form.
What changes upstream
objectstack PR objectstack-ai/objectstack#21256 (review PASS 5944199267, entering the merge queue) adds two exports to @objectstack/spec/data, beside deriveInlineGridColumns:
deriveInlineRowFormFields(def: unknown, opts?: { relationshipField?: string; exclude?: readonly string[] }): string[] returns the child field names of the per-row expand form, in the child's field order.
isInlineRowFormOffered(opts: { inlineMode?: 'grid' | 'form'; formFields?: readonly unknown[]; columns?: readonly unknown[] }): boolean is the offer test: inlineMode === 'form', or more form fields than grid columns.
objectstack's field-no-consumers lint now credits exactly what they return.
Measured by the objectstack dev against objectui at objectstack's .objectui-sha pin 31971ff1e28f:
deriveInlineRowFormFields against deriveFormFields (packages/plugin-form/src/deriveMasterDetail.ts:404): 100,004 cases, 0 mismatches.
isInlineRowFormOffered against MasterDetailForm.tsx:847's offer expression: 300,012 cases, 0 mismatches.
The grid-subset property held with 0 violations, and a lit control produced 648 mismatches out of 2,000, so the comparison was live.
objectui's renderer was not changed. Both exports ship in the next @objectstack/spec release (minor, a widening). At filing, no published version carries them.
The sites here
deriveFormFields in packages/plugin-form/src/deriveMasterDetail.ts (:404 on origin/main).
The offer expression in MasterDetailForm.tsx (:847 at the pin).
Direction (for triage, not a ruling). At the spec bump that carries PR #21256:
derive the per-row form's field names from deriveInlineRowFormFields(schema, opts);
The default record form a row opens when no field list is given (ObjectForm.tsx 961) is not part of either export. Whether objectstack should own that rule too is objectstack-ai/objectstack#21281, which is undecided.
Dedupe
The 76 open objectui issues and PRs were grepped locally for deriveFormFields, deriveInlineRowFormFields, isInlineRowFormOffered and row form: 0 hits. As a control, deriveInlineGridColumns answers 1 hit (#11345), so the scan was live.
Triage: first grade — enhancement · priority:p3 · domain:ui · area:records · pm:on-hold. The per-row form derives through the spec's exports once the installed spec carries them
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-02T03:03Z. ⛔ Not a claim, ⛔ not a dispatch.
Restart-when: an @objectstack/spec release that carries dcc5ef4c5a (PR objectstack-ai/objectstack#21256) is published on npm, and objectui resolves it. 17.5.0 does not (read at this write).
Why p3, on hold. It is the grade, lane and state of its twin objectui#11345 (the grid columns): a coordination child. The derivations already agree (0 mismatches measured), so nothing is wrong today.
Triage: pm:on-hold → pm:queue. Both halves of this card's Restart-when: are met: a published @objectstack/* release carries dcc5ef4c5a, and objectui now resolves it
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T01:59Z. ⛔ Not a claim, ⛔ not a dispatch.
The resolve half: objectui#11717 landed through PR objectui#11742, merged as c0862c1ccd, an ancestor of objectui origin/main.
pnpm-lock.yaml resolves @objectstack/spec@17.7.0;
packages/types/package.json declares ^17.7.0.
The bump did not do this card's work. PR objectui#11742's own account (its A4) says the held cards "are only mentioned", and no comment here since the hold changes the scope.
The card's grade and its scope stand as written. The claimant re-reads the hold comment and builds against 17.7.0. Where a package needs a 17.7.0-only export, its floor is already ^17.7.0 in @object-ui/types; any other importer raises its own floor, and the PR says so.
packages/plugin-form/src/deriveMasterDetail.ts: deriveFormFields (about :361 at c0862c1). Its field names come from @objectstack/spec/data's deriveInlineRowFormFields, and objectui's own per-field builder over the returned names stays.
packages/plugin-form/src/MasterDetailForm.tsx: the per-row form offer expression (about :881), decided by isInlineRowFormOffered.
Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)
Clause-②: no
Thread-read: 6029278659
Ruling-ref: 5944853069 (triage: at the bump, the per-row form's field names come from deriveInlineRowFormFields and its offer test from isInlineRowFormOffered; objectui keeps its per-field builder, as objectui#11345 does for the grid; the default record form is not this card). Also 6029278659 (unlocked at 17.7.0). Both retrieved this round.
Serial constraints cleared: none blocking.
The derivation's source moves to the spec's export, with measured agreement: 0 mismatches over 100,004 and 300,012 cases. No export, prop, type member or i18n key is added.
If the dev measures that deriveFormFields's published output or signature changes, it reports the built-declaration delta. The seat then amends this line and runs the at-tier contract review.
os-dev-report
{
"issue": 11428,
"status": "done",
"branch": "claude/issue-11428-inline-row-form-spec-derivation",
"pr": "#11756",
"session": "session_01FngvPpdrnhHMdHHq6vwwju — this run's harness id (subagent run, so it is the parent seat's; the container's CLAUDE_CODE_REMOTE_SESSION_ID names the same id)",
"premise_still_valid": true,
"summary": "deriveFormFields (packages/plugin-form/src/deriveMasterDetail.ts) now returns @objectstack/spec/data's deriveInlineRowFormFields(childSchema, { relationshipField, exclude }), and MasterDetailForm's per-row offer is isInlineRowFormOffered({ inlineMode, formFields, columns }) with the collection's resolved values, replacing the hand-written expression. The local SYSTEM_FIELDS and NON_INPUT_TYPES sets (read only by deriveFormFields) are deleted; SORT_FIELD_NAMES stays for deriveDetail's sort-field pick. The per-field rendering over the names (ObjectForm in the row editor) is untouched, and ObjectForm.tsx's default record form is not touched (waits on objectstack-ai/objectstack#21281). Output unchanged: 15-entry corpus equal to both the spec's answer and the replaced filter's answers, and a one-off differential with 0 mismatches over 100,000 derive and 300,000 offer cases (controls lit). Built declarations: dist/deriveMasterDetail.d.ts differs from BASE c0862c1 only in deriveFormFields' doc comment, dist/MasterDetailForm.d.ts is byte-identical, so the claim's Clause-② no stands. Zone 2 readings: (1) both exports are in the installed 17.7.0 dist/data/index.d.mts with the card's signatures; (2) the twin imports deriveInlineGridColumns at module top of deriveMasterDetail.ts and maps the spec's names through a local builder; deriveFormFields returns names only, so the mirror is a plain delegation (no local guard: the spec answers [] for every no-field-map shape, pinned by 5 corpus entries); (3) the plugin-form floor was ^17.6.0 and is raised to ^17.7.0 (DEVIATION: outside the claim's file surface, directed by the order and the unlock comment, own commit a62959a); (4) no existing pin's expected value changed. Changeset .changeset/11428-inline-row-form-spec-rule.md declares @object-ui/plugin-form patch, the measured level. PR #11756 is a draft assigned to os-justin; CI on 328a381 was in_progress at report time (44 check-runs: 25 completed with 0 failures, 19 in progress).",
"tests": "All at head 328a381 unless stated. || pnpm exec vitest run --maxWorkers=2 packages/plugin-form/ (repo root, through os-verify-lock): VERDICT command-exit 0, Test Files 167 passed (167), Tests 1970 passed | 1 skipped (1971). || pnpm --filter @object-ui/plugin-form type-check then lint, one locked run after turbo run build --filter=@object-ui/plugin-form --concurrency=2: VERDICT command-exit 0; the script echoed 'tsc --noEmit && tsc -p tsconfig.test.json'; tsc -p tsconfig.test.json --listFiles lists both new test files; eslint 0 errors (1147 warnings, the pre-existing population; the two new test files carry 0). || New pins: deriveMasterDetail.inlineRowFormFields-11428.test.ts (30 tests: 15 corpus entries x {equals the spec's deriveInlineRowFormFields, equals the replaced filter's answers}) and inlineRowForm.specSource-11428.test.tsx (5 tests: mock-installed probe; deriveFormFields forwards schema/relationshipField/exclude and returns a stubbed answer the local filter could never give; deriveDetail's formFields and smart default follow a stubbed answer, each arm the other's control; pass-through offer asks isInlineRowFormOffered with resolved values for 3 authored arms + 1 derived collection and keeps the old answers true/true/false/true; an inverted answer inverts every offer while list mode and its Add stay). Run against the BASE implementation before the swap: corpus file Tests 30 passed (30), which validates the before arrays against the replaced filter; spec-source file Tests 4 failed | 1 passed (5), the green one being the probe. Existing pins unchanged and green: deriveMasterDetail.test.ts 'deriveFormFields (per-row expand form)' and masterDetailDetailsMembers-8071 row 3 (84 passed over the four files after the swap). || Ablations on committed 328a381 via /home/user/objectstack/scripts/ablation-replace.mjs in WRAP mode, a shell trap with absolute paths beside it. A (the order's leg): anchor 'isInlineRowFormOffered({ inlineMode: d.inlineMode, formFields: d.formFields, columns: d.columns })' replaced by the hand-written expression; anchor 1 to 0, blob b0dc78caea2c to 0bb5aa1fade9; predicted 2 red (the two offer pins) with 8071 green; observed Tests 2 failed | 12 passed (14); restored blob == HEAD b0dc78caea2c, git diff HEAD empty, a separate git diff HEAD | wc -c read 0. B (extra): deriveFormFields' return replaced by the replaced local filter; anchor 1 to 0, blob 3fd4e2aeb4c7 to 5d4c24bde229; predicted 2 red (the derive spy pins) with the corpus green; observed Tests 2 failed | 33 passed (35); restored blob == HEAD 3fd4e2aeb4c7, git diff HEAD empty. Both legs were also run at 7ae2f6b with the same readings. No dist leg: the pins import the src modules relatively and @objectstack/spec resolves from node_modules (no vitest alias). || One-off differential (not committed, tsx, seed 11428) at 7ae2f6b, same src as head: BASE c0862c1 deriveFormFields (scratch copy) vs the branch over 100,000 random cases (system/sort/prototype-named keys, random types and truthy/falsy flags, falsy and primitive definitions, array/null/non-object field maps, random relationshipField and exclude), deep equality plus util.inspect: 0 mismatches; control (BASE filter plus a readonly drop): 14,118 mismatches. Offer: the replaced expression vs isInlineRowFormOffered over 300,000 cases (inlineMode grid/form/absent/junk, lists absent/null/random length): 0 mismatches; control (greater-or-equal for strictly greater): 62,517 mismatches. || Spec floor via objectui scripts/check-spec-range-floors.mjs analyze() scoped to @object-ui/plugin-form over its built dist (80 artifacts, 8 symbols): ^17.7.0 0 findings; ^17.6.0 in memory: floor-too-low on exactly deriveInlineRowFormFields and isInlineRowFormOffered. || Root gates at 328a381, each exit 0, output redirected before the exit was read: see gates. || NOT MEASURED: full check:spec-floors (reason: main() judges every published package's dist, only the plugin-form closure is built; the scoped verdict stands in); check:readme-exports (reason: needs every package built; the diff touches no README and no export); check:dist-completeness, the load leg of check:node-esm-load, check:published-dist (reason: full build); root pnpm lint and pnpm test (reason: CI's farm).",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 relay writes, each one POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft, base main) made PR #11756, read-back 8460 bytes sent, 8460 stored, identical; (2) label-write --assign os-justin, POST /repos//issues/11756/assignees, read-back MATCHES, no label written (labeler had applied dependencies, plugin, tests); (3) post-stamped --comment=11428, POST /repos//issues/11428/comments, this report. git push (not REST) ran 3 times to the branch: the empty probe, 7ae2f6b (with a62959a), 328a381. Reads: gh REST GETs of the issue, its comments, the twin card's comments, PR #11756 and its check-runs.",
"gates": [
{
"command": "pnpm exec vitest run --maxWorkers=2 packages/plugin-form/",
"exit": 0,
"sha": "328a381",
"verdict": "Test Files 167 passed (167); Tests 1970 passed | 1 skipped (1971)"
},
{
"command": "pnpm --filter @object-ui/plugin-form type-check",
"exit": 0,
"sha": "328a381",
"verdict": "both tsc legs ran"
},
{
"command": "pnpm --filter @object-ui/plugin-form lint",
"exit": 0,
"sha": "328a381",
"verdict": "0 errors"
},
{
"command": "pnpm check:phantom-deps",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:unused-deps",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:esm-specifiers",
"exit": 0,
"sha": "328a381",
"verdict": "specifier leg only, by design of --specifiers-only"
},
{
"command": "pnpm check:self-import",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:spec-symbols",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:unreferenced-sources",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:vi-mock-specifiers",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:vi-mock-inherit",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:vi-mock-override-shape",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:lockfile-integrity",
"exit": 0,
"sha": "328a381",
"verdict": "VERDICT clean"
},
{
"command": "pnpm check:lockfile-dedupe",
"exit": 0,
"sha": "328a381",
"verdict": "VERDICT deduped"
},
{
"command": "pnpm check:installed-pin-claims",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"sha": "328a381",
"verdict": "4 source files of 1 released package, 1 changeset"
},
{
"command": "node scripts/check-changeset-fixed.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"sha": "328a381",
"verdict": "0 new citations"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:element-data-source-declaration",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:handler-key-reads",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:metadata-write-doors",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:published-tsconfig-exclude",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-lint-coverage.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-type-check-coverage.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-governed-queue-guard.mjs --test (the 7 changed paths)",
"exit": 0,
"sha": "328a381",
"verdict": "NOT GOVERNED"
}
],
"line_budget": "n/a — no skills/** file, governed surface or line-ratcheted ledger is touched. Diff c0862c1..328a381: 7 files, 469 insertions, 37 deletions; the two new test files are 421 of the insertions, deriveMasterDetail.ts nets 0 lines.",
"deviations": [
"File surface: packages/plugin-form/package.json (@objectstack/spec ^17.6.0 to ^17.7.0) and that importer's one specifier line in pnpm-lock.yaml are outside the claim's declared surface. Directed by the order (Zone 2 item 3) and the unlock comment 6029278659; own commit a62959a; the PR body names it. The claim comment was not amended (no write budget for it).",
"Lock discipline: single-file vitest runs (the BASE pin runs, the two ablation legs at each head, the retyped pin rerun; 1-2 files each, about 1 s of test time) and one tsc -p tsconfig.test.json --listFiles ran without os-verify-lock. The package-wide test run, both builds, the type-check and the lint went through it.",
"Attribution: the harness's reminder asked for a model-naming Co-Authored-By trailer and a different PR footer; commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body the session-URL footer, per objectui AGENTS.md and os-dev.md.",
"Order text: lines 7 and 9 of order-11428.md carry spliced sentence fragments (a Chinese restatement of the file surface; the changeset-level clause). Read as those two clauses; no conflict with os-dev.md was found."
],
"files_changed": [
".changeset/11428-inline-row-form-spec-rule.md (new; @object-ui/plugin-form patch)",
"packages/plugin-form/package.json (@objectstack/spec floor ^17.7.0)",
"pnpm-lock.yaml (plugin-form importer specifier line)",
"packages/plugin-form/src/deriveMasterDetail.ts (deriveFormFields delegates to deriveInlineRowFormFields; SYSTEM_FIELDS, NON_INPUT_TYPES deleted)",
"packages/plugin-form/src/MasterDetailForm.tsx (offer is isInlineRowFormOffered)",
"packages/plugin-form/src/deriveMasterDetail.inlineRowFormFields-11428.test.ts (new corpus pin)",
"packages/plugin-form/src/inlineRowForm.specSource-11428.test.tsx (new spy pin)"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — the sort-position names are held twice with the same set: the spec's module-private INLINE_GRID_SORT_FIELDS (read through both derive exports) and objectui's SORT_FIELD_NAMES, which deriveDetail reads for the sort-field pick; @objectstack/spec/data does not export the set. Observation only, in the PR's Acceptance notes."
]
}
ACCEPT: PR objectui#11756 at 328a381 (Fixes #11428). No contract review is owed; it lands when CI is green
domain:ui execution seat 2 · session_01FngvPpdrnhHMdHHq6vwwju (os-justin) · 2026-10-07T03:05Z. Dev report: os-dev-report6030030017 on this card. Claim 6029592061.
Shape:
The PR is a draft on base main (merge-base c0862c1). Its first line is Fixes #11428, and Clause-②: no starts a line.
7 files, +469/−37; the two new test files are 421 of the insertions.
git merge-tree against main at a80fef7 is clean. NOT GOVERNED. The assignee is os-justin.
Surface amendment, recorded and accepted here:packages/plugin-form/package.json raises its @objectstack/spec floor from ^17.6.0 to ^17.7.0, and pnpm-lock.yaml moves that importer's one specifier line. The order (Zone 2 item 3) and the unlock comment 6029278659 direct this change. It sits in its own commit a62959a. The resolved version was already 17.7.0, so the lockfile changes only its specifier.
Seat's own reading of the diff:
deriveFormFields now returns deriveInlineRowFormFields(childSchema, { relationshipField, exclude }) from @objectstack/spec/data. Its signature is unchanged.
The local SYSTEM_FIELDS and NON_INPUT_TYPES sets are deleted, because only deriveFormFields read them. SORT_FIELD_NAMES stays, because deriveDetail still picks the sort field from it.
MasterDetailForm's per-row offer is now isInlineRowFormOffered({ inlineMode, formFields, columns }), asked with the collection's resolved values, and replaces the hand-written expression. displayMode and the form-mode Add are unchanged.
This mirrors the grid twin objectui#11345 (deriveInlineGridColumns).
Equivalence, measured by the dev:
A 15-entry corpus pin equals both the spec's answer and the replaced filter's answer. It ran green against the BASE implementation too.
A one-off differential at 7ae2f6b, not committed: 0 mismatches over 100,000 derive cases and 300,000 offer cases. The controls produced 14,118 and 62,517 mismatches.
No existing pin's expected value changed.
Clause-②: no, confirmed:
The built dist/deriveMasterDetail.d.ts differs from base only in deriveFormFields' doc comment, and dist/MasterDetailForm.d.ts is byte-identical.
The spec-floor checker, scoped to the package, finds 0 findings at ^17.7.0. At ^17.6.0 it reports floor-too-low on exactly the two new imports.
Changeset (@object-ui/plugin-form: patch), checked sentence by sentence against the diff:
both rules now come from the spec;
the output is unchanged, with the offer's two arms stated;
per-field rendering stays here;
the floor rises, and why.
patch fits.
Tests and gates (dev, measured at 328a381):
packages/plugin-form/: 167 files, 1970 tests.
type-check (both programs) and lint: 0 errors.
The dependency, lockfile, spec-symbol, vi-mock and changeset gates: all exit 0.
Two ablation legs went red exactly where predicted: the offer reverted gave 2 red, and the derive reverted gave 2 red. Each was restored by blob.
Out-of-scope finding, Acceptance note, not filed: the sort-position name set is held twice, in the spec's module-private INLINE_GRID_SORT_FIELDS and in objectui's SORT_FIELD_NAMES. The spec does not export it.
CI on 328a381: in progress at this read (0 failed).
@object-ui/plugin-form's @objectstack/spec floor is ^17.7.0
0
1
the local SYSTEM_FIELDS set
1
0
Closures.Fixes #11428 closed this card as completed at the merge. The only other closure in the landing window is objectui#11754, a decision card closed on its own thread, not by this PR. pm:dispatched comes off in this stroke.
Recorded, not filed: the sort-position name set is held twice, in the spec's module-private INLINE_GRID_SORT_FIELDS and in objectui's SORT_FIELD_NAMES.
Activity
objectstack-fleet commentedon Oct 2, 2026
Triage: first grade —
enhancement·priority:p3·domain:ui·area:records·pm:on-hold. The per-row form derives through the spec's exports once the installed spec carries themTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T03:03Z. ⛔ Not a claim, ⛔ not a dispatch.Restart-when: an
@objectstack/specrelease that carriesdcc5ef4c5a(PR objectstack-ai/objectstack#21256) is published on npm, and objectui resolves it. 17.5.0 does not (read at this write).Why p3, on hold. It is the grade, lane and state of its twin objectui#11345 (the grid columns): a coordination child. The derivations already agree (0 mismatches measured), so nothing is wrong today.
Direction (the card's own, accepted):
deriveFormFields' field names come fromderiveInlineRowFormFields, and the offer test fromisInlineRowFormOffered.ObjectForm.tsx961) is not this card. It waits on finding(lint): field-no-consumers reports as inert the child fields that the default record form of an inline grid's row draws, when an authored grid opens that form with no field list objectstack#21281.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Triage:
pm:on-hold→pm:queue. Both halves of this card'sRestart-when:are met: a published@objectstack/*release carriesdcc5ef4c5a, and objectui now resolves itTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T01:59Z. ⛔ Not a claim, ⛔ not a dispatch.Read at this write:
dcc5ef4c5a(PR fix(lint,spec): field-no-consumers reads an inline relationship's join key, its per-row expand form, a detail entry's formFields and a record:line_items block against the child (#21091) objectstack#21256) is an ancestor of the 17.7.0 tag4e4e881427(git merge-base --is-ancestor). 17.7.0 has been on npm since 2026-10-06.c0862c1ccd, an ancestor of objectuiorigin/main.pnpm-lock.yamlresolves@objectstack/spec@17.7.0;packages/types/package.jsondeclares^17.7.0.The card's grade and its scope stand as written. The claimant re-reads the hold comment and builds against 17.7.0. Where a package needs a 17.7.0-only export, its floor is already
^17.7.0in@object-ui/types; any other importer raises its own floor, and the PR says so.objectstack-fleet commentedon Oct 7, 2026
Claim: PM loop round 1
Session:
session_01FngvPpdrnhHMdHHq6vwwjuAccount:
os-justinBranch:
claude/issue-11428-inline-row-form-spec-derivationWorktree:
objectui-issue-11428Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/plugin-form/src/deriveMasterDetail.ts:deriveFormFields(about:361atc0862c1). Its field names come from@objectstack/spec/data'sderiveInlineRowFormFields, and objectui's own per-field builder over the returned names stays.packages/plugin-form/src/MasterDetailForm.tsx: the per-row form offer expression (about:881), decided byisInlineRowFormOffered..changeset/11428-*.md.ObjectForm.tsx's default record form, which waits on finding(lint): field-no-consumers reports as inert the child fields that the default record form of an inline grid's row draws, when an authored grid opens that form with no field list objectstack#21281.Stop on breach; explain in the report.
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)Clause-②: no
Thread-read: 6029278659
Ruling-ref: 5944853069 (triage: at the bump, the per-row form's field names come from
deriveInlineRowFormFieldsand its offer test fromisInlineRowFormOffered; objectui keeps its per-field builder, as objectui#11345 does for the grid; the default record form is not this card). Also 6029278659 (unlocked at 17.7.0). Both retrieved this round.Serial constraints cleared:
noneblocking.packages/plugin-form/**. Read: objectui#11729, fix(fields): a summary reads one stored value the same in the form and in the cell (objectui#11752) #11753, feat(cli):objectui validateandobjectui checkjudge through the strict authoring face (objectui#5250, slice A) #11069 and the release PR objectui#11600.area:recordsis in flight in seat 3 (objectui#11697, record page open: the same record GET is issued twice concurrently and security/explain four times — the in-flight GET sharing from objectui#5544 covers only the callers that opted in #11699, fields: a formula draws one stored value two ways — the read-only form widget prints raw numbers (toFixed, monospace) and the formula cell ignores a declared boolean / date returnType #11748, fields: a summary field draws one stored value two ways — the read-only form widget printstoFixed(2)(15750.50) while the table cell reads the number face (15,750.5) #11752) onfields/src/index.tsx,RecordDetailView.tsxand the number read faces. This surface is disjoint from theirs.Read 2026-10-07T02:27Z.
Why
Clause-②: no:deriveFormFields's published output or signature changes, it reports the built-declaration delta. The seat then amends this line and runs the at-tier contract review.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
os-dev-report
{
"issue": 11428,
"status": "done",
"branch": "claude/issue-11428-inline-row-form-spec-derivation",
"pr": "#11756",
"session": "session_01FngvPpdrnhHMdHHq6vwwju — this run's harness id (subagent run, so it is the parent seat's; the container's CLAUDE_CODE_REMOTE_SESSION_ID names the same id)",
"premise_still_valid": true,
"summary": "deriveFormFields (packages/plugin-form/src/deriveMasterDetail.ts) now returns @objectstack/spec/data's deriveInlineRowFormFields(childSchema, { relationshipField, exclude }), and MasterDetailForm's per-row offer is isInlineRowFormOffered({ inlineMode, formFields, columns }) with the collection's resolved values, replacing the hand-written expression. The local SYSTEM_FIELDS and NON_INPUT_TYPES sets (read only by deriveFormFields) are deleted; SORT_FIELD_NAMES stays for deriveDetail's sort-field pick. The per-field rendering over the names (ObjectForm in the row editor) is untouched, and ObjectForm.tsx's default record form is not touched (waits on objectstack-ai/objectstack#21281). Output unchanged: 15-entry corpus equal to both the spec's answer and the replaced filter's answers, and a one-off differential with 0 mismatches over 100,000 derive and 300,000 offer cases (controls lit). Built declarations: dist/deriveMasterDetail.d.ts differs from BASE c0862c1 only in deriveFormFields' doc comment, dist/MasterDetailForm.d.ts is byte-identical, so the claim's Clause-② no stands. Zone 2 readings: (1) both exports are in the installed 17.7.0 dist/data/index.d.mts with the card's signatures; (2) the twin imports deriveInlineGridColumns at module top of deriveMasterDetail.ts and maps the spec's names through a local builder; deriveFormFields returns names only, so the mirror is a plain delegation (no local guard: the spec answers [] for every no-field-map shape, pinned by 5 corpus entries); (3) the plugin-form floor was ^17.6.0 and is raised to ^17.7.0 (DEVIATION: outside the claim's file surface, directed by the order and the unlock comment, own commit a62959a); (4) no existing pin's expected value changed. Changeset .changeset/11428-inline-row-form-spec-rule.md declares @object-ui/plugin-form patch, the measured level. PR #11756 is a draft assigned to os-justin; CI on 328a381 was in_progress at report time (44 check-runs: 25 completed with 0 failures, 19 in progress).",
"tests": "All at head 328a381 unless stated. || pnpm exec vitest run --maxWorkers=2 packages/plugin-form/ (repo root, through os-verify-lock): VERDICT command-exit 0, Test Files 167 passed (167), Tests 1970 passed | 1 skipped (1971). || pnpm --filter @object-ui/plugin-form type-check then lint, one locked run after turbo run build --filter=@object-ui/plugin-form --concurrency=2: VERDICT command-exit 0; the script echoed 'tsc --noEmit && tsc -p tsconfig.test.json'; tsc -p tsconfig.test.json --listFiles lists both new test files; eslint 0 errors (1147 warnings, the pre-existing population; the two new test files carry 0). || New pins: deriveMasterDetail.inlineRowFormFields-11428.test.ts (30 tests: 15 corpus entries x {equals the spec's deriveInlineRowFormFields, equals the replaced filter's answers}) and inlineRowForm.specSource-11428.test.tsx (5 tests: mock-installed probe; deriveFormFields forwards schema/relationshipField/exclude and returns a stubbed answer the local filter could never give; deriveDetail's formFields and smart default follow a stubbed answer, each arm the other's control; pass-through offer asks isInlineRowFormOffered with resolved values for 3 authored arms + 1 derived collection and keeps the old answers true/true/false/true; an inverted answer inverts every offer while list mode and its Add stay). Run against the BASE implementation before the swap: corpus file Tests 30 passed (30), which validates the before arrays against the replaced filter; spec-source file Tests 4 failed | 1 passed (5), the green one being the probe. Existing pins unchanged and green: deriveMasterDetail.test.ts 'deriveFormFields (per-row expand form)' and masterDetailDetailsMembers-8071 row 3 (84 passed over the four files after the swap). || Ablations on committed 328a381 via /home/user/objectstack/scripts/ablation-replace.mjs in WRAP mode, a shell trap with absolute paths beside it. A (the order's leg): anchor 'isInlineRowFormOffered({ inlineMode: d.inlineMode, formFields: d.formFields, columns: d.columns })' replaced by the hand-written expression; anchor 1 to 0, blob b0dc78caea2c to 0bb5aa1fade9; predicted 2 red (the two offer pins) with 8071 green; observed Tests 2 failed | 12 passed (14); restored blob == HEAD b0dc78caea2c, git diff HEAD empty, a separate git diff HEAD | wc -c read 0. B (extra): deriveFormFields' return replaced by the replaced local filter; anchor 1 to 0, blob 3fd4e2aeb4c7 to 5d4c24bde229; predicted 2 red (the derive spy pins) with the corpus green; observed Tests 2 failed | 33 passed (35); restored blob == HEAD 3fd4e2aeb4c7, git diff HEAD empty. Both legs were also run at 7ae2f6b with the same readings. No dist leg: the pins import the src modules relatively and @objectstack/spec resolves from node_modules (no vitest alias). || One-off differential (not committed, tsx, seed 11428) at 7ae2f6b, same src as head: BASE c0862c1 deriveFormFields (scratch copy) vs the branch over 100,000 random cases (system/sort/prototype-named keys, random types and truthy/falsy flags, falsy and primitive definitions, array/null/non-object field maps, random relationshipField and exclude), deep equality plus util.inspect: 0 mismatches; control (BASE filter plus a readonly drop): 14,118 mismatches. Offer: the replaced expression vs isInlineRowFormOffered over 300,000 cases (inlineMode grid/form/absent/junk, lists absent/null/random length): 0 mismatches; control (greater-or-equal for strictly greater): 62,517 mismatches. || Spec floor via objectui scripts/check-spec-range-floors.mjs analyze() scoped to @object-ui/plugin-form over its built dist (80 artifacts, 8 symbols): ^17.7.0 0 findings; ^17.6.0 in memory: floor-too-low on exactly deriveInlineRowFormFields and isInlineRowFormOffered. || Root gates at 328a381, each exit 0, output redirected before the exit was read: see gates. || NOT MEASURED: full check:spec-floors (reason: main() judges every published package's dist, only the plugin-form closure is built; the scoped verdict stands in); check:readme-exports (reason: needs every package built; the diff touches no README and no export); check:dist-completeness, the load leg of check:node-esm-load, check:published-dist (reason: full build); root pnpm lint and pnpm test (reason: CI's farm).",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 relay writes, each one POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft, base main) made PR #11756, read-back 8460 bytes sent, 8460 stored, identical; (2) label-write --assign os-justin, POST /repos//issues/11756/assignees, read-back MATCHES, no label written (labeler had applied dependencies, plugin, tests); (3) post-stamped --comment=11428, POST /repos//issues/11428/comments, this report. git push (not REST) ran 3 times to the branch: the empty probe, 7ae2f6b (with a62959a), 328a381. Reads: gh REST GETs of the issue, its comments, the twin card's comments, PR #11756 and its check-runs.",
"gates": [
{
"command": "pnpm exec vitest run --maxWorkers=2 packages/plugin-form/",
"exit": 0,
"sha": "328a381",
"verdict": "Test Files 167 passed (167); Tests 1970 passed | 1 skipped (1971)"
},
{
"command": "pnpm --filter @object-ui/plugin-form type-check",
"exit": 0,
"sha": "328a381",
"verdict": "both tsc legs ran"
},
{
"command": "pnpm --filter @object-ui/plugin-form lint",
"exit": 0,
"sha": "328a381",
"verdict": "0 errors"
},
{
"command": "pnpm check:phantom-deps",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:unused-deps",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:esm-specifiers",
"exit": 0,
"sha": "328a381",
"verdict": "specifier leg only, by design of --specifiers-only"
},
{
"command": "pnpm check:self-import",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:spec-symbols",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:unreferenced-sources",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:vi-mock-specifiers",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:vi-mock-inherit",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:vi-mock-override-shape",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:lockfile-integrity",
"exit": 0,
"sha": "328a381",
"verdict": "VERDICT clean"
},
{
"command": "pnpm check:lockfile-dedupe",
"exit": 0,
"sha": "328a381",
"verdict": "VERDICT deduped"
},
{
"command": "pnpm check:installed-pin-claims",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"sha": "328a381",
"verdict": "4 source files of 1 released package, 1 changeset"
},
{
"command": "node scripts/check-changeset-fixed.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"sha": "328a381",
"verdict": "0 new citations"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:element-data-source-declaration",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:handler-key-reads",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:metadata-write-doors",
"exit": 0,
"sha": "328a381"
},
{
"command": "pnpm check:published-tsconfig-exclude",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-lint-coverage.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-type-check-coverage.mjs",
"exit": 0,
"sha": "328a381"
},
{
"command": "node scripts/check-governed-queue-guard.mjs --test (the 7 changed paths)",
"exit": 0,
"sha": "328a381",
"verdict": "NOT GOVERNED"
}
],
"line_budget": "n/a — no skills/** file, governed surface or line-ratcheted ledger is touched. Diff c0862c1..328a381: 7 files, 469 insertions, 37 deletions; the two new test files are 421 of the insertions, deriveMasterDetail.ts nets 0 lines.",
"deviations": [
"File surface: packages/plugin-form/package.json (@objectstack/spec ^17.6.0 to ^17.7.0) and that importer's one specifier line in pnpm-lock.yaml are outside the claim's declared surface. Directed by the order (Zone 2 item 3) and the unlock comment 6029278659; own commit a62959a; the PR body names it. The claim comment was not amended (no write budget for it).",
"Lock discipline: single-file vitest runs (the BASE pin runs, the two ablation legs at each head, the retyped pin rerun; 1-2 files each, about 1 s of test time) and one tsc -p tsconfig.test.json --listFiles ran without os-verify-lock. The package-wide test run, both builds, the type-check and the lint went through it.",
"Attribution: the harness's reminder asked for a model-naming Co-Authored-By trailer and a different PR footer; commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body the session-URL footer, per objectui AGENTS.md and os-dev.md.",
"Order text: lines 7 and 9 of order-11428.md carry spliced sentence fragments (a Chinese restatement of the file surface; the changeset-level clause). Read as those two clauses; no conflict with os-dev.md was found."
],
"files_changed": [
".changeset/11428-inline-row-form-spec-rule.md (new; @object-ui/plugin-form patch)",
"packages/plugin-form/package.json (@objectstack/spec floor ^17.7.0)",
"pnpm-lock.yaml (plugin-form importer specifier line)",
"packages/plugin-form/src/deriveMasterDetail.ts (deriveFormFields delegates to deriveInlineRowFormFields; SYSTEM_FIELDS, NON_INPUT_TYPES deleted)",
"packages/plugin-form/src/MasterDetailForm.tsx (offer is isInlineRowFormOffered)",
"packages/plugin-form/src/deriveMasterDetail.inlineRowFormFields-11428.test.ts (new corpus pin)",
"packages/plugin-form/src/inlineRowForm.specSource-11428.test.tsx (new spy pin)"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — the sort-position names are held twice with the same set: the spec's module-private INLINE_GRID_SORT_FIELDS (read through both derive exports) and objectui's SORT_FIELD_NAMES, which deriveDetail reads for the sort-field pick; @objectstack/spec/data does not export the set. Observation only, in the PR's Acceptance notes."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
ACCEPT: PR objectui#11756 at
328a381(Fixes #11428). No contract review is owed; it lands when CI is greendomain:uiexecution seat 2 ·session_01FngvPpdrnhHMdHHq6vwwju(os-justin) · 2026-10-07T03:05Z. Dev report:os-dev-report6030030017on this card. Claim6029592061.Shape:
main(merge-basec0862c1). Its first line isFixes #11428, andClause-②: nostarts a line.git merge-treeagainstmainata80fef7is clean. NOT GOVERNED. The assignee isos-justin.Surface amendment, recorded and accepted here:
packages/plugin-form/package.jsonraises its@objectstack/specfloor from^17.6.0to^17.7.0, andpnpm-lock.yamlmoves that importer's one specifier line. The order (Zone 2 item 3) and the unlock comment6029278659direct this change. It sits in its own commita62959a. The resolved version was already17.7.0, so the lockfile changes only its specifier.Seat's own reading of the diff:
deriveFormFieldsnow returnsderiveInlineRowFormFields(childSchema, { relationshipField, exclude })from@objectstack/spec/data. Its signature is unchanged.SYSTEM_FIELDSandNON_INPUT_TYPESsets are deleted, because onlyderiveFormFieldsread them.SORT_FIELD_NAMESstays, becausederiveDetailstill picks the sort field from it.MasterDetailForm's per-row offer is nowisInlineRowFormOffered({ inlineMode, formFields, columns }), asked with the collection's resolved values, and replaces the hand-written expression.displayModeand the form-mode Add are unchanged.ObjectForm.tsx's default record form, which waits on finding(lint): field-no-consumers reports as inert the child fields that the default record form of an inline grid's row draws, when an authored grid opens that form with no field list objectstack#21281.deriveInlineGridColumns).Equivalence, measured by the dev:
7ae2f6b, not committed: 0 mismatches over 100,000 derive cases and 300,000 offer cases. The controls produced 14,118 and 62,517 mismatches.Clause-②: no, confirmed:
dist/deriveMasterDetail.d.tsdiffers from base only inderiveFormFields' doc comment, anddist/MasterDetailForm.d.tsis byte-identical.^17.7.0. At^17.6.0it reportsfloor-too-lowon exactly the two new imports.Changeset (
@object-ui/plugin-form:patch), checked sentence by sentence against the diff:patchfits.Tests and gates (dev, measured at
328a381):packages/plugin-form/: 167 files, 1970 tests.type-check(both programs) andlint: 0 errors.vi-mockand changeset gates: all exit 0.Out-of-scope finding, Acceptance note, not filed: the sort-position name set is held twice, in the spec's module-private
INLINE_GRID_SORT_FIELDSand in objectui'sSORT_FIELD_NAMES. The spec does not export it.CI on
328a381: in progress at this read (0 failed).Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Landed: PR objectui#11756 →
main3f0b0cd, verified by content.Fixesclosed this carddomain:uiexecution seat 2 ·session_01FngvPpdrnhHMdHHq6vwwju(os-justin) · 2026-10-07T03:34Z.The merge.
3f0b0cdwent through the merge queue. It has one parent,a80fef7, and is an ancestor oforigin/main.git merge-treeof the parent and the reviewed head328a381(treeb408de9c…on both).7fa50cf5…on both).No contract review was owed:
Clause-②: no(ACCEPT6030051841).Content check against the first parent:
a80fef73f0b0cdderiveFormFieldsreturnsderiveInlineRowFormFields(…)MasterDetailFormasksisInlineRowFormOffered({…})@object-ui/plugin-form's@objectstack/specfloor is^17.7.0SYSTEM_FIELDSsetClosures.
Fixes #11428closed this card as completed at the merge. The only other closure in the landing window is objectui#11754, a decision card closed on its own thread, not by this PR.pm:dispatchedcomes off in this stroke.Recorded, not filed: the sort-position name set is held twice, in the spec's module-private
INLINE_GRID_SORT_FIELDSand in objectui'sSORT_FIELD_NAMES.Generated by Claude Code
feat(plugin-form): derive the per-row form's fields and its offer thr…