Skip to content

docs(spec): the master-detail detail entry's sortField and amountField describes state each renderer path (#21315) - #21355

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21315-detail-entry-sortfield-describe
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21315-detail-entry-sortfield-describe

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21315
Clause-②: no

What changed

An object-master-detail-form detail entry has two describes for keys the renderer can fill in when they are omitted: sortField and amountField. Both now state what happens on each of the renderer's paths. The reference page that lifts them (content/docs/references/ui/component.mdx) is regenerated by check:generated --fix. No schema accepts or refuses anything new. There is no shape, default or nullability change, and no objectui edit. The model is PR #21307, which did the same for inlineMode and formFields.

sortField, before:

Child field holding the line sort position, stamped on drag-reorder (derived from a position / sort_order / … field when omitted)

after:

Child field holding the line sort position, stamped on drag-reorder. When omitted it is the child object's first field named position, sort_order, sequence, line_no, line_number or sort, if it has one — except on an entry that names relationshipField and at least one column and gives every column a type. The renderer keeps that entry exactly as authored: nothing is derived, the grid stamps no line position, and a drag-reorder is not saved

amountField, before:

Numeric child column summed for the running total

after:

Numeric child column summed for the running total and the totalField rollup. When omitted it is picked from the grid's number and currency columns: a computed one, else one named amount, total, subtotal, line_total, line_amount or net_amount, else the last currency column, else the last numeric one — except on an entry that names relationshipField and at least one column and gives every column a type. The renderer keeps that entry exactly as authored, so nothing is picked. With no amountField authored or picked, the sums read a child column named amount, and the grid shows a running total only when totalField is set

What the renderer does (objectui at the .objectui-sha pin 31971ff1e28f, read with git show)

packages/plugin-form/src/MasterDetailForm.tsx:

  • 927–929, needsDerive: an entry needs resolution when it has no relationshipField, no columns, or a column without a type. At 958, when no entry in the block needs it, every entry is kept as authored.
  • 965–967, the fast path: relationshipField is set and every column (at least one) has a type. The entry is returned unchanged ({ ...entry, status: 'ready' }). An omitted sortField stays undefined, and 862 passes sort_field: d.sortField, which is undefined. An omitted amountField also stays undefined.
  • 1032–1038: every other entry that loads its child schema goes through deriveDetail. Then amountField = d.amountField ?? derived.amountField and sortField = d.sortField ?? derived.sortField.
  • 1048–1055, the hydrate path: relationshipField is set and columns are present, but some have no type. The config becomes { ...d, columns: derived.columns, amountField, sortField }. So formFields and inlineMode are kept as authored here, but sortField and amountField ARE derived. The comment at 1040–1047 gives the reason (objectui#11144).
  • 1056–1069, the derived path: everything is derived, with amountField and sortField at 1065–1066.

packages/plugin-form/src/deriveMasterDetail.ts:

  • 540 with 55: the derived sortField is the first child field whose name is one of position, sort_order, sequence, line_no, line_number or sort.
  • 534 with 476–495: the derived amountField is pickAmountField(columns). That is a computed number or currency column, else one named in AMOUNT_LIKE_FIELDS, else the last currency column, else the last numeric column.

packages/fields/src/widgets/GridField.tsx 726–737: the grid stamps row[sortField] = index only when sort_field is set. Its comment reads "Without it, reorder is order-of-entry."

When no amountField is authored or picked (MasterDetailForm.tsx):

  • 728: the document subtotal sums e.config.amountField || 'amount'.
  • 861: the grid's total_field is d.amountField || (d.totalField ? 'amount' : undefined), unless the tax stack below it takes over.
  • 1549–1550: the totalField rollup writes sumRows(rows, d.amountField || 'amount').

So the path split is not #21284's. formFields and inlineMode are kept as authored on both the fast path and the hydrate path, which is an entry that names relationshipField and at least one column. sortField and amountField are kept as authored only on the fast path, where every column also has a type. Each describe states its own split.

Why amountField changes too

The triage direction put it in this edit. Its old text made no false claim, but it said nothing about omission, and omission changes the result by path. On the fast path nothing is picked, and the sums read a column named amount. From 1549–1550 (a reading, not a run): take a fully typed entry whose line-total column is line_total, with totalField set and amountField omitted. It writes 0 into the parent's totalField, because sumRows counts a missing value as 0. The derived path would have picked line_total. An author cannot know to write the key on the fast path unless the describe says so.

Every member of masterDetailDetailEntry(), read for the same gap

  • childObject: required, and nothing derives it. Holds.
  • relationshipField: "auto-detected … when omitted". The fast and hydrate paths both need the key, so an entry without it always takes the derived path, and findRelationshipField (deriveMasterDetail.ts 141–154) runs there. That function takes the master_detail field that references the parent, else a lookup. Holds.
  • columns: "derived from the child object when omitted". The fast and hydrate paths both need columns, so an entry without them always reaches deriveColumns. A column given only its name has no type, so the entry is hydrated. Holds.
  • formFields and inlineMode: spec(ui): an object-master-detail-form detail entry's inlineMode describe says the mode is resolved from the relationship's inlineEdit when omitted; on an entry kept as authored the renderer resolves nothing #21284's text, re-read at the pin. They are kept as authored at 967 and 1048–1055, and derived at 1063–1064. The offer conditions are at 847 and 850. Holds.
  • amountField and sortField: changed above.
  • totalField: nothing derives it, and its describe says nothing about omission. The sum it receives is the one the amountField describe now explains. Holds.
  • title, minRows, maxRows and addLabel: passed through or given a default label (755, 863–865). None of their describes claims an omission behaviour. Holds.
  • The factory's TSDoc calls sortField "the line-position field the grid stamps on drag-reorder" and claims no derivation. Holds, not edited.

The sortField retirement waits on the pin bump

objectui main retired the authored sortField at 0a3e5409f (objectui#11376), after this pin. git merge-base --is-ancestor 31971ff1e28f 0a3e5409f exits 0, so the pin is in that commit's history and 0a3e5409f is not in the pin's. At the pin, sortField is still read (85, 862, 1038). This PR does not retire the key. The .objectui-sha bump that crosses 0a3e5409f owes the spec half: retire sortField from this entry, with a tombstone and an ADR-0087 entry, in the same landing. The sortField describe written here is what the current pin does until then. For amountField, objectui main (d8edfe2576) still returns the fast-path entry unchanged at 979, and still sums d.amountField || 'amount' (740, 1564). So, as of that commit, the bump owes no amountField change.

Acceptance notes

  • A form view's subforms[] entry (FormViewSchema.subforms in packages/spec/src/ui/view.zod.ts) has the same amountField describe, "Numeric child column summed for the running total". At the pin, ObjectForm.tsx 340 and 394 send a form with subforms to MasterDetailForm as details, so the same omission behaviour applies there. That describe makes no false claim, so this is an observation, not a filed defect. This PR keeps to its card's surface. Carrier: none.

Verification (every reading below is on HEAD e0b23743d5)

  • pnpm --filter @objectstack/spec build: VERDICT command-exit 0.
  • pnpm --filter @objectstack/spec check:generated: the first run reported 1 of 15 artifact(s) stale: content/docs/references/**, and every other artifact was current, including the authorable surface and the JSON schemas. --fix regenerated that one and its re-check gave ✓ check:docs. In the gate union it exits 0.
  • pnpm --filter @objectstack/spec typecheck: exit 0.
  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: Test Files 597 passed (597), Tests 17487 passed | 1 todo (17488).
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run with no paths: 102 commands. The sorted list is identical to the dispatch's derivation at 97239c3c8a. All were run, with exit codes recorded. --ran answers 102 derived famil(ies) accounted for — 101 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).
  • Six gates first exited 3 because their prerequisite packages were not built: check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, check:lean-entry-closure and check:dual-build-cjs-loads. After building that closure (turbo run build for formula, lint, client-react and objectql, 34 tasks), the first five exit 0.
  • NOT MEASURED: pnpm check:dual-build-cjs-loads. It needs every workspace package's dist (hono, account, setup, studio, client and more), which needs a full-repo build. As a narrower check, all 19 require entry points of @objectstack/spec load. CI runs the full gate.
  • eslint, narrowed. Of the diff's three files, only component.zod.ts is in eslint's population: for the .md and .mdx files, eslint answers "File ignored because no matching configuration was supplied". --format json reports 0 errors and 0 warnings for that file. Its resolved config has parserOptions without project, so linting is not type-aware and this diff cannot change the verdict for any untouched file. The full pnpm lint runs in CI.
  • main was not merged in. Since the base 97239c3c8a, origin/main has gained three commits, and none of them touches packages/spec or the reference page.

Generated by Claude Code

claude added 3 commits October 2, 2026 06:06
…d describes state each renderer path

Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:ui tooling labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6c5bef5f4ee5b3c5ed497e785b4d24f6b2649bf5 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b1ad68f3038154d74c1b5e987584652ed4d96c0 — the merge of head e0b23743d508616834cadbd601d62e90035f3495 into base 6c5bef5f4ee5b3c5ed497e785b4d24f6b2649bf5, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b1ad68f3038154d74c1b5e987584652ed4d96c0 && git checkout 0b1ad68f3038154d74c1b5e987584652ed4d96c0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6c5bef5f4ee5b3c5ed497e785b4d24f6b2649bf5 e0b23743d508616834cadbd601d62e90035f3495 && git checkout -B drift-repro 6c5bef5f4ee5b3c5ed497e785b4d24f6b2649bf5 && git merge --no-ff e0b23743d508616834cadbd601d62e90035f3495

node scripts/docs-audit/affected-docs.mjs --json 6c5bef5f4ee5b3c5ed497e785b4d24f6b2649bf5

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e0b23743d508616834cadbd601d62e90035f3495
Local-runs: none

PR #21355 (card #21315), the net diff against main at this head: 3 files, +16/-4. Only .describe() text moves, for sortField and amountField on masterDetailDetailEntry() in packages/spec/src/ui/component.zod.ts; content/docs/references/ui/component.mdx is regenerated; .changeset/21315-detail-entry-sortfield-describe.md is added. Read against objectui at the .objectui-sha pin 31971ff1e28f (read-only git show), PR #21307's landed describes (75260587b3) and record 5937457620 on #20928. Inputs: the card and its three comments (5945912211, 5946439435, 5946892212), the PR body and file list, the check-runs on this head. Not read: the dispatch order or the dispatching seat's conclusions.

① Derived judgments

  1. No accept-set or public-surface change — right. component.zod.ts differs from origin/main on exactly two lines (5036 and 5037); with the describe string stripped, both are byte-equal to main: z.string().optional() on each key, no default, no nullability move, no member added or removed (12 members, unchanged). The regenerated reference page changes exactly the two table rows those describes feed, and each row is byte-equal to its describe string. No other file on main carries the old sortField sentence (git grep over the tree answers only the two files this PR edits), so no stale generated artifact is left behind. The second amountField row on that page belongs to RecordLineItemsProps (the record line-items panel, component.zod.ts 2157) and is untouched.
  2. The path split, line by line at the pin (MasterDetailForm.tsx) — right, and it differs from spec(ui): an object-master-detail-form detail entry's inlineMode describe says the mode is resolved from the relationship's inlineEdit when omitted; on an entry kept as authored the renderer resolves nothing #21284's exactly as the dev says. needsDerive (927–929) is true when some entry lacks relationshipField, has no columns, or has a column without a type; when it is false every entry is kept as authored (958). In the resolve loop, columnsTyped (965) is true only for a non-empty column list whose every column has a truthy type, and 967 returns the entry unchanged with status: 'ready' when relationshipField and columnsTyped both hold — the fast path. Every other entry loads the child schema and runs deriveDetail (1032–1036), then amountField is the authored value else the derived one (1037) and sortField likewise (1038). The hydrate branch (1048–1055: relationshipField plus a non-empty column list, reached only when some column is untyped) returns the authored config with columns, amountField and sortField replaced — so formFields and inlineMode ride through as authored while sortField and amountField are derived when omitted. The derived branch (1056–1069) derives everything, amountField and sortField at 1065–1066. So formFields and inlineMode are kept on fast plus hydrate (docs(spec): the master-detail detail entry's inlineMode and formFields describes state both renderer paths (#21284) #21307's condition, "names both relationshipField and at least one column") and sortField and amountField are kept on the fast path only (this PR's condition, "... and gives every column a type"). Each describe states its own split, and neither borrows the other's condition.
  3. sortField, each new sentence — true at the pin. "When omitted it is the child object's first field named position, sort_order, sequence, line_no, line_number or sort, if it has one": deriveMasterDetail.ts 540 takes the first key of the child schema's fields whose name is in SORT_FIELD_NAMES, and that set (55) holds exactly those six names; the result is undefined when none matches. "The renderer keeps that entry exactly as authored: nothing is derived": 967. "the grid stamps no line position": 862 passes sort_field: d.sortField, which is undefined; GridField.tsx 729 reads cfg.sort_field and 735 stamps the index into the row only when it is set. "a drag-reorder is not saved": a reading of the grid's own comment (726–728, "so drag-reorder survives a reload ... Without it, reorder is order-of-entry"), not a run, and the dev labels it as a reading. I checked the save side and it holds: sendBatch (from 1541; the operation list is built at 1574–1591 and run at 1592) writes the rows' field values through buildMasterDetailBatch / buildMasterDetailEditBatch, and nothing else records a position, so with no stamped field the new order reaches no stored row. The describe does not overstate it — it says the position is not saved, not that the rows are not saved. The retained opening clause "stamped on drag-reorder" is the old text; the grid in fact stamps on every change (735), so the clause is narrower than the renderer, not false, and this PR did not touch it.
  4. amountField, each new sentence — true at the pin. "summed for the running total and the totalField rollup": 861 (total_field) and 1549–1550 (the parent's totalField receives sumRows over the rows on d.amountField || 'amount'); 728 also sums it into the document subtotal. "When omitted it is picked from the grid's number and currency columns: a computed one, else one named amount, total, subtotal, line_total, line_amount or net_amount, else the last currency column, else the last numeric one": deriveDetail 534 takes the override else pickAmountField(columns); pickAmountField (485–495) keeps the columns typed number or currency, then the first computed one, then the first named in AMOUNT_LIKE_FIELDS (476, exactly those six names), then the last currency column, then the last numeric column, and undefined when there is no numeric column — the case the describe's next sentence covers. "The renderer keeps that entry exactly as authored, so nothing is picked": 967. "With no amountField authored or picked, the sums read a child column named amount": 728 and 1550, both d.amountField || 'amount'. "the grid shows a running total only when totalField is set": 861 passes total_field as undefined under the document totals stack, else the authored amountField, else 'amount' only when totalField is set, else undefined; GridField.tsx 724 and 867 show the total exactly when total_field arrives. "Only when" is a necessary condition, as written; under the totals stack (which needs a header tax rate and some entry with an amountField) the grid shows none, which "only when" does not contradict.
  5. The dev's line_total example (PR body, "a reading, not a run") — a reading, correctly labelled, and not in the describe. sumRows (masterDetailTx.ts 201–207) counts a non-finite value as 0, so a fully typed entry with totalField set, amountField omitted and no column named amount writes 0 to the parent — the same fact the describe states as "the sums read a child column named amount". The describe carries the mechanism, not the example, so nothing is overstated.
  6. Every other member of masterDetailDetailEntry() — spot-checked, holds. relationshipField ("auto-detected from the child's master_detail/lookup field when omitted"): both kept-as-authored branches require it (958, 967, 1048), so an omitted key always reaches deriveDetail 524 and findRelationshipField (141–154), which returns the master_detail field whose reference is the parent, else a lookup. columns ("derived from the child object when omitted"; identity-only entries hydrate): both branches require a non-empty list, so an omitted one reaches deriveColumns (533), and a { name } column has no type, so the entry hydrates (1048–1055 over hydrateColumns, 532). totalField ("Parent field to receive the rolled-up sum"): 1549–1550; nothing derives it and its describe claims no omission behaviour. formFields and inlineMode are docs(spec): the master-detail detail entry's inlineMode and formFields describes state both renderer paths (#21284) #21307's text, unchanged, and still match 967, 1048–1055 and 1063–1064. childObject, title, minRows, maxRows, addLabel: no derivation claimed, none made (755, 863–865).
  7. Not a governed surface. None of the three paths is in the GOVERNED_SURFACES families (docs/adr/**, docs/NORTH-STAR.md, .claude/**, skills/**, AGENTS.md, CLAUDE.md), so Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 asks no record of this PR; this record is owed under the contract-review reference because the diff touches non-test packages/spec/src/**.

② Semver level

@objectstack/spec patch with Clause-②: no — right. The accept set neither widens nor narrows (①.1), and a describe is a shipped string in the published package (its JS, dist and the JSON schemas, as the card's reach: line says), so a corrected one is a patch fix, never skip-changeset. The changeset body carries the Clause-②: no line the PR body also carries. Its prose, sentence by sentence: the "entry the renderer resolves" condition is the complement of 967's; the two derivation rules are ①.3 and ①.4; "it still derives these two" on an entry with some untyped column is 1048–1055; the kept-as-authored consequences are ①.3 and ①.4; the "used to say" quotations are main's strings; "No schema accepts or refuses anything new" is ①.1. The model's changeset (75260587b3, .changeset/21284-detail-entry-describes.md) has the same level and shape. Check Changeset concluded success on this head.

③ Boundary flags

  1. FormViewSchema.subforms[].amountField (view.zod.ts 4295) — outside this card's scope, acceptable as noted, and escalated to the dispatching seat as a candidate follow-up docs card; not a FAIL. The card's surface is "every member of masterDetailDetailEntry()" (triage 5945912211) and the claim's file surface is that factory in component.zod.ts; the subforms entry is a different schema in a different file. The dev's reading is right: ObjectForm.tsx 340 and 394 at the pin route a form with subforms to MasterDetailForm as details (drawer and modal forms host the same component), so the same path-dependent omission applies there, and that entry's describe ("Numeric child column summed for the running total") makes no false claim. Under the os-dev classes it is none of (a) a reproducible defect, (b) a declared contract violated, or (c) metadata the runtime refuses or drops; it is an observation, placed where the rule puts one (the PR's Acceptance notes, carrier none). What I escalate: triage's own reason for putting amountField into this edit — omission changes the result by path, so an author cannot know to write the key — holds word for word for the subforms sibling, which now says less than the detail entry for the same renderer key. Whether that earns a docs card in spec(ui): an object-master-detail-form detail entry's sortField describe says it is derived when omitted; on the renderer's fast path nothing derives it #21315's shape is the seat's call. Nothing filed here.
  2. main not merged in — acceptable; the count has aged, the substance holds. At my read origin/main (f9bcd08bef) is 8 commits past the base 97239c3c8a, not the 3 the report counted at its fetch. None of the 8 touches packages/spec/src/ui/component.zod.ts, content/docs/references/ui/component.mdx, the new changeset path or packages/spec/src/ui/view.zod.ts (git log --name-only over those paths across the range is empty). One of them, 99e1912afc (feat(spec): retire the metric sub-caption — the widget translation subCaption key and translateDashboard's options.description overlay #21342), does touch packages/spec — translation.zod.ts, dashboard.zod.ts, the conversions and migrations registries — so the report's "none under packages/spec" sentence is no longer true as written; it has no bearing on this diff (no shared file, and component.mdx on main is untouched by it). The merge queue rebuilds onto main.
  3. Harness attribution yielded to AGENTS.md — right, and not a deviation. The three commits end with the model-free trailer pair AGENTS.md requires, and AGENTS.md names the harness-written trailer form an exemption that is reporting only, not a declared deviation; the dev over-reported, which costs nothing.
  4. amountField stated rather than held — right. Triage 5945912211 put it in the same edit, and the text written is true (①.4).
  5. The sortField retirement — correctly left pending, and the dev's ancestry claim verified. In objectui, git merge-base --is-ancestor 31971ff1e28f 0a3e5409f exits 0 and the reverse exits 1, so the retirement commit (objectui#11376, 2026-10-01) is after the pin, and sortField is still read at the pin (85, 862, 1038). Record 5937457620 places the spec-side retirement (tombstone plus ADR-0087 entry) with the .objectui-sha bump that crosses 0a3e5409f; this PR does not pre-empt it, and the describe it writes is the pin's behaviour until then.
  6. open_questions: none. Check-runs on this head are the gate verdicts, not the report's self-run list. Read at 2026-10-02T07:02Z, after the last of them concluded: 35 check-runs on e0b23743d5, 33 success and 2 skipped — Console Pin Gate (no .objectui-sha move in the diff) and Packed-tarball smoke (opt-in) — none failing, none still running. Among the successes: Lint & Repo Gates, Build Core, Build Docs, Test Core and its six shards, Type Check · workspace, TypeScript Type Check, Check Changeset, Spec property liveness, Governed Surface Queue Guard, the three Dogfood Regression Gate shards and Temporal Conformance. The dev's one NOT-MEASURED family (check:dual-build-cjs-loads) is inside Lint & Repo Gates, which concluded success.

Implemented-by: claude/issue-21315-detail-entry-sortfield-describe
Reviewed-by: session_01UtnxvdiN376GF3sgXwAw4d

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 07:05
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 07:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 16eefc6 Oct 2, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21315-detail-entry-sortfield-describe branch October 2, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(ui): an object-master-detail-form detail entry's sortField describe says it is derived when omitted; on the renderer's fast path nothing derives it

2 participants