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
一句话:由字段级 relatedList:'primary' 自动派生的相关列表,列表查询不带任何 $orderby(行序 = 记录 id 兜底序),而 17.2.0 没有给这类相关列表任何排序声明位;且 17.0.0 移除 view.form.defaultSort 时的迁移说明「a related list inside a form sorts by its own list view's sort」与 17.2.0 实测行为不符。请求提供排序能力(字段级 relatedListSort,或让派生相关列表真正继承子对象列表视图的 sort)。
表单级:view.form.defaultSort 已在 17.0.0 移除(schema 现为 ZodNever),但当时迁移说明原话是「a related list inside a form sorts by its own list view's sort」——与第 2 条实测矛盾,两者必有一处需要修正(实现补上继承,或迁移说明改口)。
Maintainer ruling recorded (atomic four-part write)
Ruled 2026-08-23, live PM chat, verbatim: 「10950 不考虑存量,其他接受你的建议」 — this card falls under the accepted recommendations. Ruled: direction 1 — derived related lists inherit the child object's list view sort; no new spec key. This honours the published 17.0.0 migration note (declared becomes enforced). needs-user-decision → pm:queue in the same stroke. Dispatch notes: locate the derivation emit point first (fix lands where the derived query is built); the 17.0.0 migration prose and 17.2.0 behavior must end up agreeing; objectui#5795 stays parked on this card and returns via the unlock scan as the UI-side acceptance leg when this closes. Field-level relatedListSort was NOT approved — do not add a key.
Claim: PM seat session_01T9cDbY2NBiVJWYx3BpWfH2 (/pm-dispatch, spec lane) — dispatching to an os-dev subagent under the recorded maintainer ruling (2026-08-23 15:02Z, direction 1: derived related lists inherit the child object's list view sort; no new spec key — relatedListSort NOT approved).
Session: session_01T9cDbY2NBiVJWYx3BpWfH2
Branch: claude/issue-11345-related-list-inherit-sort
Worktree: ../objectstack-11345 (created by the dev agent, per-task)
Domain: domain:spec
File surface: measurement-first — the ruling's dispatch note says locate the derivation emit point before fixing (where the derived related-list descriptor/query is built). If the emit point lives in THIS repo (server/client SDK side), the fix lands here + the 17.0.0 migration prose must end up agreeing with behaviour. If it lives in objectui's console/renderer, the dev STOPS with the measured locations and zero objectstack-side implementation — this seat then routes the execution leg (scope order: this seat handles spec@objectstack only; objectui#5795 stays parked on this card either way).
Container & model: os-dev subagent, opus (cross-layer measurement + inheritance semantics).
Clause-②: not expected — the ruling adds NO spec key; inheritance is behaviour at the derivation site + prose truth. If the fix turns out to require a packages/spec/src/** diff (e.g. describe/prose on the relatedList family), the path limb applies: stop at PR-draft and report for contract review.
Serial constraints: none against in-flight #11287 (i18n-resolver) / #11307 (object.zod.ts annotation).
Route: STOP — the emit point lives wholly in objectui. Zero objectstack-side implementation, no branch, no PR. Phase 1 measurement below is read-only and complete; every claim in the card's section 4 is confirmed at file:line.
Ruling honoured
Ruled: direction 1 — derived related lists inherit the child object's list view sort; no new spec key. This honours the published 17.0.0 migration note (declared becomes enforced). … Field-level relatedListSort was NOT approved — do not add a key.
Nothing was added, nothing was edited. The measurement confirms direction 1 is reachable with no new spec key: the descriptor slot it needs is already declared and already consumed (see "contract channel" below).
Measurement map
(a) Descriptor derivation — objectui
objectui/packages/app-shell/src/utils/deriveRelatedLists.ts:48-67 — DerivedRelatedList: childObject / childLabel / referenceField / title? / columns? / isOwned / isPrimary. No sort, no filter.
objectui/packages/app-shell/src/utils/deriveRelatedLists.ts:133-146 — the emit point. Builds that descriptor from fieldDef.relatedList / relatedListTitle / relatedListColumns only. It iterates the child object defs at :118 but never reads their views.
objectui/packages/app-shell/src/views/RecordDetailView.tsx:1955-1978 — second drop point: maps each descriptor into buildDefaultPageSchema's related shape, emitting title / objectName / relationshipField / columns? / icon? / isPrimary?.
objectui/packages/plugin-detail/src/synth/buildDefaultPageSchema.ts:174-188 — the related option type has no sort member; :683-690 — componentNode('record:related_list', …) forwards title/objectName/relationshipField/columns/limit/icon and no sort.
(b) Query assembly — objectui
objectui/packages/plugin-detail/src/renderers/record-related-list.tsx:211 — defaultSort={schema.sort}. The renderer already forwards a block-level sort; the channel is open and unfilled on the derived path.
objectui/packages/plugin-detail/src/RelatedList.tsx:251-267 — normalizeSortSpec: 'field' / '-field' string or [{field, order}] to the array form.
objectui/packages/plugin-detail/src/RelatedList.tsx:519-529 — the assembly, verbatim confirmation of the card: const orderby = fetchSortField ? [{…}] : defaultSortSpec; if (orderby.length > 0) params.$orderby = orderby; — no sort in, no $orderby out, hence the PK fallback order.
objectui/packages/plugin-detail/src/RelatedList.tsx:383-389 — windowed = !dataProvided && !!api && effectivePageSize > 0 && dataSource.find && !filterActive. A derived related list self-fetches, so it is windowed by default: an inherited sort would take effect through this exact path.
(c) Which child list view is "the" list view — objectstack owns the resolver, already exported and already consumed
objectstack/packages/spec/src/ui/view.zod.ts:4111-4198 — expandViewContainerWithDiagnostics / expandViewContainer. The item matching the declared default list is flagged isDefault (:4146-4150); with no list declared, the first list item takes the flag (:4147).
Answer: "the" list view is the child object def's list after that merge — the isDefault expanded list view. Its sort is declared at objectstack/packages/spec/src/ui/view.zod.ts:1470-1486 (union: legacy string "field desc" | [{field, order}], with direction aliased to order).
Nothing new needs exporting from objectstack — resolver and value are already on the objectui side of the boundary.
Contract channel already open (this is why no new key is needed)
objectstack/packages/spec/src/ui/component.zod.ts:883-928 — RecordRelatedListProps.sort: z.union([z.string(), z.array(strictObject({ field, order }))]).optional().describe('Sort order for related records'). Declared, parsed, forwarded by the renderer, consumed by RelatedList. Direction 1 only has to fill it on the derived path.
The 17.0.0 prose
objectstack/packages/spec/src/ui/view.zod.ts:2487-2494 — the form.defaultSort retiredKey text: "nothing read it: a related list inside a form sorts by its own list view's sort."
Mirrors: content/docs/references/ui/view.mdx:284 (generated) and packages/spec/liveness/view.json:355.
Per the ruling, behaviour moves to match the prose, so no prose edit is needed and none was made. The published skills do not repeat the claim (skills/objectstack-ui/SKILL.md:151-160, skills/objectstack-data/rules/relationships.md:316-335 cover prominence / title / columns only), so no published skill text is falsified either.
Is there ANY objectstack-side slice?
No implementation slice. Everything objectstack owns here is already in place and correct: the default-list-view resolver, the ListView sort shape, and the record:related_list.sort prop. Confirmed no server/SDK-side related-list query builder exists — grep for relatedListFilter|relatedListColumns across packages/runtime/src packages/rest/src packages/objectql/src packages/metadata/src returns nothing, packages/console is a stub (README + CHANGELOG + package.json only), and the only record:related_list occurrences in objectstack sources are lint validators, the spec itself, and hand-authored platform-objects pages. This matches the recorded #8704 precedent, which already ruled the derivation lives wholly in objectui (packages/spec/src/data/field.test.ts:596-600).
The one optional objectstack slice, offered for routing rather than taken: mirroring the #8704 pattern, the inheritance rule could be pinned as contract text in relatedList's .describe() (as relatedListFilter did at field.zod.ts:1014-1047, pinned by field.test.ts:602-608). That would be a packages/spec/src/** diff and therefore the path limb — not started, since the dispatch says stop at zero objectstack implementation when the emit point is objectui.
Handoff — what the objectui-side fix is
Three edits, no new keys, no spec change:
deriveRelatedLists.ts — add sort? to DerivedRelatedList and emit it from the child object def's default list view (child.list?.sort), never from a field key. The child def is already in hand at :118; no new plumbing.
RecordDetailView.tsx:1955-1978 — carry sort onto the related entry (childObjectDef is already resolved at :1956).
buildDefaultPageSchema.ts:174-188 and :683-690 — add sort to the related option type and forward it into the record:related_list node. Renderer and RelatedList need no change.
Shape caveat for the implementer: the ListView legacy string form is space-separated "field desc" (view.zod.ts:1470), while normalizeSortSpec (RelatedList.tsx:258-266) reads a string as 'field' / '-field'. Copying a legacy string through verbatim would emit $orderby on a field literally named seq_no desc. The inheritance must normalize the ListView union into the array form, or inherit only the array arm.
The three acceptance pins the dispatch specified all land in objectui: (i) derived query carries $orderby from the child list view's sort; (ii) child with no list-view sort still emits no $orderby; (iii) the view read is the isDefault one.
Out-of-scope findings (searched before filing; no new cards created)
relatedListFilter non-consumption — confirmed as the same gap at the same two lines (deriveRelatedLists.ts:133-146 emits no filter; RecordDetailView.tsx:1959-1978 forwards none; buildDefaultPageSchema.ts:683-690 has no filter), while the block prop exists (component.zod.ts:930) and the spec's contract text binds every consumer. Already filed: objectui#4664 (open, pm:queue). Not fixed, no duplicate card.
This card's objectui half — already filed: objectui#5795 (open, pm:blocked, parked here). Its root-cause section matches this measurement exactly.
NEW, covered by neither card: a declared record:related_list.sort is silently dropped whenever the list leaves windowed mode. windowed goes false while the client text filter is active (RelatedList.tsx:383-389) and the client-mode path returns rows unsorted unless the user clicked a column (:731-733, if (windowed || !sortField) return filteredData;). So an authored sort stops applying the moment a viewer types in the filter box — pre-existing, affecting the authored prop today, and it will affect the inherited sort identically. Deliberately not filed: it lands in objectui on the same file surface objectui#5795 already owns and the PM routes objectui; a third overlapping card risks two agents on one file. Recommend attaching it to objectui#5795 as an acceptance sub-point.
Report
{
"issue": 11345,
"status": "blocked",
"branch": null,
"pr": null,
"premise_still_valid": true,
"summary": "Measurement-first routing: the derived related-list descriptor is emitted in objectui (deriveRelatedLists.ts:133-146, re-dropped at RecordDetailView.tsx:1955-1978 and buildDefaultPageSchema.ts:174-188/683-690), and the $orderby assembly is objectui too (RelatedList.tsx:519-529, fed via record-related-list.tsx:211). Both halves the ruling touches are objectui-side, so per dispatch I stopped with zero objectstack implementation: no worktree, no branch, no PR, no spec diff, no prose edit. objectstack's share of direction 1 is already in place and needs no change - the default-list-view resolver (view.zod.ts:4111-4198, isDefault, already imported by objectui's MetadataProvider), the ListView sort shape (view.zod.ts:1470-1486), and the record:related_list.sort prop the inheritance would fill (component.zod.ts:883-928), which is why no new spec key is required. The 17.0.0 migration prose (view.zod.ts:2487-2494) stays as written and becomes true once the objectui leg lands.",
"measurement_map": {
"descriptor_derivation": "objectui/packages/app-shell/src/utils/deriveRelatedLists.ts:48-67 (interface, no sort), :133-146 (emit point); re-dropped at objectui/packages/app-shell/src/views/RecordDetailView.tsx:1955-1978 and objectui/packages/plugin-detail/src/synth/buildDefaultPageSchema.ts:174-188 + :683-690",
"query_assembly": "objectui/packages/plugin-detail/src/RelatedList.tsx:519-529 ($orderby only when handed a sort), :251-267 (normalizeSortSpec), :396-403 (defaultSortSpec memo), :383-389 (windowed predicate); fed by objectui/packages/plugin-detail/src/renderers/record-related-list.tsx:211 (defaultSort={schema.sort})",
"which_child_list_view": "The isDefault expanded list view: objectstack/packages/spec/src/ui/view.zod.ts:4111-4198 flags it (:4146-4150; first list item when no `list` is declared, :4147); objectui/packages/app-shell/src/providers/MetadataProvider.tsx:9,:413-414,:377-378,:429-431 merges it onto the child object def as `obj.list`. Its sort shape: objectstack/packages/spec/src/ui/view.zod.ts:1470-1486.",
"contract_channel_already_open": "objectstack/packages/spec/src/ui/component.zod.ts:883-928 - RecordRelatedListProps.sort is declared, parsed, forwarded and consumed; direction 1 only needs to fill it, hence no new spec key.",
"prose": "objectstack/packages/spec/src/ui/view.zod.ts:2487-2494 (retiredKey text), mirrored at content/docs/references/ui/view.mdx:284 and packages/spec/liveness/view.json:355. No edit made or needed - behaviour moves to match it. Published skills do not repeat the claim (skills/objectstack-ui/SKILL.md:151-160, skills/objectstack-data/rules/relationships.md:316-335).",
"objectstack_side_slice": "None for implementation. No server/SDK related-list query builder exists: relatedListFilter|relatedListColumns absent from packages/runtime/src, packages/rest/src, packages/objectql/src, packages/metadata/src; packages/console is a stub; record:related_list in objectstack appears only in lint validators, the spec, and hand-authored platform-objects pages. Matches the #8704 precedent recorded at packages/spec/src/data/field.test.ts:596-600. Optional, NOT taken: pinning the inheritance rule as contract text in relatedList's .describe() (the #8704 pattern) - that would be a packages/spec/src/** diff and therefore the path limb.",
"objectui_fix": "1) deriveRelatedLists.ts - add sort? to DerivedRelatedList, emit from child.list?.sort (child def already in hand at :118). 2) RecordDetailView.tsx:1955-1978 - carry sort onto the related entry (childObjectDef resolved at :1956). 3) buildDefaultPageSchema.ts:174-188 and :683-690 - add sort to the related option type and forward into the record:related_list node. Renderer and RelatedList unchanged. CAVEAT: ListView legacy string sort is space-separated 'field desc' (view.zod.ts:1470) while normalizeSortSpec reads 'field'/'-field' (RelatedList.tsx:258-266) - normalize to the array form or inherit only the array arm, else $orderby goes out on a field literally named 'seq_no desc'."
},
"tests": "None run - zero edits were made, so there is nothing to verify and no gate to derive. Phase 1 was read-only static tracing across both checkouts (/home/user/objectstack at b863f012fa, /home/user/objectui), by grep and file reads only. No worktree, no install, no build, no test, no dispatch-gates run, no lock taken. Declared explicitly rather than reported as a pass.",
"open_questions": [],
"out_of_scope_findings": [
"already filed as objectui#4664 (open, pm:queue): the same emit point ignores relatedListFilter - confirmed at deriveRelatedLists.ts:133-146, RecordDetailView.tsx:1959-1978, buildDefaultPageSchema.ts:683-690; not fixed, no duplicate card",
"already filed as objectui#5795 (open, pm:blocked, parked on #11345): this card's objectui execution half; its root-cause section matches this measurement",
"NOT filed (reported for attachment to objectui#5795): a declared record:related_list.sort is dropped whenever the list leaves windowed mode - windowed goes false while the client text filter is active (RelatedList.tsx:383-389) and the client path returns rows unsorted unless a column was clicked (:731-733). Pre-existing, affects the authored prop today and will affect the inherited sort identically. Not filed to avoid a third card on the file surface objectui#5795 already owns"
]
}
PM routing: blocked-with-measurement — the dependency inverted
Blocked-by: objectui#5795
Collected the measurement round (os-dev-report above, zero edits, both trees clean — correct stop per the dispatch's routing rule). Findings of record:
objectstack's share is already in place: isDefault list-view resolver (view.zod.ts:4111-4198), sort shape (:1470-1486), and the open record:related_list.sort contract channel (component.zod.ts:883-928) — which is exactly why the ruling needs no new spec key.
The 17.0.0 migration prose stays as written (view.zod.ts:2487-2494, mirrored in view.mdx:284 and liveness/view.json:355): behaviour moves to match it when the objectui leg lands; no prose edit needed or made.
So the parking arrangement inverts: objectui#5795 flipped pm:blocked → pm:queue this stroke with the full map, the ruling, the legacy-string-sort caveat, and the windowed-mode pre-existing defect relayed onto it (comment posted there). This card stays pm:blocked on objectui#5795 and closes when that lands and the acceptance repro from this body (10/20/30/40) passes.
Noted, not taken (would be a packages/spec/src/** diff → path limb, and adds nothing until the reader exists): pinning the inheritance rule as contract text in relatedList's .describe(), the #8704 pattern — a candidate follow-up once the objectui leg ships.
Unlock scan (spec seat R8, session_012xGvxcwPRTJfA7RfjXEYA4): pm:blocked → pm:queue, assignee cleared in the same stroke (agent identity os-steve, the seat that parked the card — not a human assignment).
Release double-check (放行双查), both passed:
Most recent conversion comment (2026-08-23T22:41Z) parks this card on objectui#5795 and sets the close condition: "closes when that lands and the acceptance repro from this body (10/20/30/40) passes". objectui#5795 is now CLOSED completed via merged PR objectui#6088 ("derived related lists inherit the child object's default list view sort") — the landing half of the condition is met.
This card carries no merged PR newer than that conversion comment (closed_by_pull_requests = 0).
Remaining work (the reason this returns to queue instead of closing): the acceptance half is unverified. The body's repro (4 child records, seq_no 10/20/30/40, derived related list must render 10/20/30/40 with $orderby on the wire) has not been run against the landed change.
Dispatch premise for the next claim (pin lag): objectstack consumes the console via pin. Before running the repro on an objectstack-booted console, verify the console pin covers objectui PR #6088's merge commit (REST compare for ancestry, not local merge-base). If the pin predates it, the repro will falsify a premise that is actually just pin lag — in that case re-park this card pm:blocked on the console bump card instead of reporting the feature missing.
Also on the card's plate from the measurement round (optional, per the 2026-08-23T22:41Z comment): pinning the inheritance rule as contract text in relatedList's .describe() (the #8704 pattern) — a packages/spec/src/** diff, i.e. contract-review path if taken.
Unlock (domain:spec seat, session_01KX8wnyjStaZcuMyAMNsy3N, R1): the 2026-08-28 park target #12912 is CLOSED — as duplicate, so the release double-check was run on the substance rather than the closure:
Latest conversion comment (2026-08-28T08:24Z) prescribes: stay parked until the console pin covers objectui#6088.
Pin reading, taken fresh: .objectui-sha on origin/main (7404925) = 9602dc820450…, and that objectui commit is dated 2026-08-25T22:43:27Z — after objectui#6088's merge (2026-08-24T16:56:18Z) — so the pin contains the inheritance fix. (Method: shallow fetch of the pinned commit from the objectui remote, read-only; the local clone is shallow, so no local ancestry claim is made. Both are main commits under a merge queue, so date order is containment here.)
Returned pm:blocked → pm:queue same-stroke. Remaining work for the next claim, unchanged from the 2026-08-26/28 comments:
Acceptance repro against an objectstack-booted console: child records with seq_no 10/20/30/40 must render 10/20/30/40 and the derived related-list request must carry $orderby on the wire. Browser/dogfood verification ⇒ dispatch shape is mode:cloud.
Claim: PM loop round R1, wave 3 (domain:spec seat) — verification-only dispatch (the acceptance repro the 2026-08-26/28/29 unlock chain prescribes)
Session: session_01KX8wnyjStaZcuMyAMNsy3N
Branch: none expected — zero code changes are in scope; a branch/PR appears only if the repro FAILS and the failure is measured to be a framework-side defect (then: stop and report the fork, do not fix on this card)
Worktree: objectstack-issue-11345 (scratch app + browser run only)
Domain: domain:spec
File surface: none in the repo. Deliverable = a browser-measured acceptance reading: derived related list (relatedList:'primary') renders child rows in the child object's default list-view sort order (seq_no 10/20/30/40) and the list request carries $orderby on the wire.
Container & model: M (browser dogfood), mode:subagent, model: claude-opus-5. This container carries Chromium + Playwright preconfigured; the browser/dogfood-cloud-card rule was written for containers without one.
Clause-②: no — verification only.
Serial constraints cleared: zero file writes; the pin premise was re-verified this round (.objectui-sha = 9602dc820450, objectui commit 2026-08-25T22:43Z, AFTER objectui#6088's merge — containment established; reading recorded in the 15:23Z unlock comment). The optional relatedList.describe() contract-text pin (the #8704 pattern) is explicitly NOT this card's work — path-limb work for a separate claim.
Claim: os-dev subagent, verification-only run (session session_01KX8wnyjStaZcuMyAMNsy3N, dispatched by the domain:spec PM seat, R1 wave 3).
No branch, no PR by design — this dispatch is the acceptance repro the 2026-08-26 / 08-28 / 08-29 unlock chain prescribes, not an implementation card. Zero repo writes; scratch app lives in a throwaway worktree objectstack-issue-11345 and is torn down after the run. Assignee left untouched (PM owns it).
What I am measuring, against a console booted at the current pin (.objectui-sha = 9602dc820450):
Parent object plus child object related by a lookup declaring relatedList: 'primary' with relatedListColumns; child has seq_no; the child's default list view declares sort by seq_no asc.
Four child records created in the order 40 / 10 / 30 / 20.
PASS = the derived related list renders 10 / 20 / 30 / 40 AND the related-list request carries $orderby on the wire (captured via Playwright request interception).
Negative control: a child with no list-view sort emits no $orderby.
Forks per the dispatch: PASS reports the measurement (PM closes, not me); FAIL measured as pin lag reports that fork; FAIL measured as a real defect stops with the diagnosis and no fix. The optional relatedList.describe() contract-text pin is explicitly not this card.
Verdict: PASS — the acceptance repro passes at the current pin. Fork taken: PASS (no branch, no PR, zero repo writes).
Verification-only run (os-dev subagent of PM session session_01KX8wnyjStaZcuMyAMNsy3N). The 2026-08-26 / 08-28 / 08-29 unlock chain's acceptance repro was built as a throwaway scratch app and driven in a real Chromium via Playwright with request interception. Both the render leg and the wire leg pass, the negative control passes, and a reverse verification (flipping the declared sort to desc) moves both legs in the predicted direction.
Environment actually measured
Repo worktree: objectstack-issue-11345, detached at origin/main = 74049254. No branch created, none pushed — git ls-remote origin for any *11345* head returns nothing.
Console pin served: .objectui-sha = 9602dc820450dda956843c6cfe5b329bcf88c757; packages/console/dist/.objectui-sha stamp = the same SHA; pnpm check:console-sha printed Console dist matches the objectui pin (objectui@9602dc820450). The console was built from that pin in this run by pnpm objectui:build (shallow clone of objectui at the pinned commit), and the asset filenames served by GET /_console/ (assets/index-CjyVRHjI.js, assets/framework-BnvBDV_V.js, assets/ui-components-h8wdbxbM.js, assets/vendor-radix-DFmVRiEf.js, assets/vendor-react-CsWFRkED.js) are exactly the files that build emitted — so the booted console IS that pin, not a stale bundle.
objectui#6088 containment verified by CONTENT, not ancestry (the clone is shallow): at the pinned commit packages/app-shell/src/utils/deriveRelatedLists.ts carries sort? on DerivedRelatedList (line 94), a doc block naming this card and the maintainer ruling (lines 78-79), convertSortToQueryParams(list?.sort) (line 131), and the emit ...(inheritedSort ? { sort: inheritedSort } : {}) (line 205).
App: purpose-built scratch stack examples/app-relsort (namespace rs) — NOT an example in the repo. Port 3477, objectstack dev --ui --seed-admin -d file:/tmp/relsort-11345/data.db, SqlDriver(better-sqlite3), dev admin admin@objectos.ai.
Browser: preinstalled Chromium at /opt/pw-browsers/chromium via executablePath (playwright 1.62.1's own browser revision is not present in this container; playwright install was NOT run).
The repro as declared
rs_parent (parent). rs_child and rs_child_nosort (children), each related by a lookup declaring relatedList: 'primary' plus relatedListTitle and relatedListColumns: ['name', 'seq_no']; each child has a seq_no number field.
rs_child's DEFAULT list view declares sort: [{ field: 'seq_no', order: 'asc' }]. rs_child_nosort's default list view is identical with the sort key omitted — that is the negative control.
Server-side confirmation that the inherited view really is the default one: GET /api/v1/meta/view?object=rs_child returns a single item rs_child.default with "isDefault": true and config.sort = [{"field":"seq_no","order":"asc"}].
Four child records per object, created through the REST API in creation order 40 / 10 / 30 / 20.
Result 1 — render leg: PASS
The derived "Sorted Items" tab on the parent record detail page (/_console/apps/rs_app/rs_parent/record/eSxcYqhvwX3suXbM) renders, top to bottom:
Item 10 10
Item 20 20
Item 30 30
Item 40 40
A full-page screenshot confirms it visually, including an ascending sort indicator on the Seq No column header. This is neither creation order (40/10/30/20) nor the record-id fallback order.
Result 2 — wire leg: PASS, with one spelling fact this card should record
Verbatim captured request (raw, exactly as it went out), positive case:
GET http://localhost:3477/api/v1/data/rs_child?top=5&skip=0&sort=seq_no&filter=%5B%22parent%22%2C%22%3D%22%2C%22eSxcYqhvwX3suXbM%22%5D
The ordering parameter is on the wire as sort=seq_no, not as a literal $orderby=. That is not a miss. $orderby is objectui's internal QueryParams member name, and objectui lowers it to this server's sort shorthand before the request leaves the browser. Both ends are pinned in code at the measured pin:
objectui packages/data-objectstack/src/index.ts:332 — "Serialize a $orderby to the server's sort shorthand (field,-other_field)" — applied at :3292-3293: const sortStr = serializeOrderBy(params.$orderby); if (sortStr) queryParams.set('sort', sortStr);
objectstack packages/client/src/index.ts:5309-5316 emits sort= for the canonical option.
So "carries $orderby on the wire" is satisfied by sort=seq_no. A reader of this card should expect that spelling, and any future pin test should assert sort=, not the OData token.
Result 3 — negative control: PASS
Verbatim captured request for the child whose default list view declares no sort:
GET http://localhost:3477/api/v1/data/rs_child_nosort?top=5&skip=0&filter=%5B%22parent%22%2C%22%3D%22%2C%22eSxcYqhvwX3suXbM%22%5D
No ordering parameter at all. Rendered order: Item 20 / Item 10 / Item 40 / Item 30 — which is precisely ascending record id (2r2slH1S4H39c-GE, QfrBHP2l0WKLkFvC, eKApe1pLPoN8dFot, sYafz_ctxIIno0EK under binary collation), i.e. the primary-key fallback this card originally reported. The control therefore proves two things at once: absence of a declared sort still emits nothing, and the ordering seen in Result 1 really is the inherited one rather than an incidental default.
Result 4 — reverse verification (predicted direction: reversal)
Predicted before running: flipping the child's declared order from asc to desc must flip both the wire token and the rendered order, and must leave the control untouched. Mutation and restore were each confirmed ON DISK by blob hash, never by an editor exit code:
pre-mutation git hash-object = bddf1b9a3b9189ac7a81a8d71412292384b60e79; post-mutation = 5944fc9d139921404a4be229789f324e35b73bb5, with the order: 'asc' grep count going 1 to 0 and order: 'desc' 0 to 1. The mutation ran under a trap ... EXIT INT TERM restore.
Observed with desc: wire sort=-seq_no; rendered Item 40 / 30 / 20 / 10; control unchanged (no ordering parameter, 20/10/40/30). Server metadata confirmed config.sort = [{"field":"seq_no","order":"desc"}] with isDefault: true.
RESTORE leg proven, not assumed: file restored, git hash-object back to bddf1b9a... exactly, served metadata back to order: "asc", and a third browser run reproduced run 1 exactly (wire sort=seq_no, rows 10/20/30/40).
No stale-build ambiguity: the mutated artefact is the scratch app's own source, which os dev's watcher recompiles, and the server's /api/v1/meta/view was re-read before each browser run to confirm which sort was actually being served. The console bundle under test was rebuilt from the pin at the start of the run.
What this reading does NOT cover
Only the windowed self-fetch path was exercised. The pre-existing defect relayed onto objectui#5795 in the 2026-08-23 measurement round (a declared or inherited sort is dropped once the list leaves windowed mode, e.g. while the client text filter is active) was NOT re-measured here and is unchanged by this reading.
The legacy space-separated string sort arm ("seq_no desc") was not exercised; only the array arm was declared. The normalization caveat recorded in the 2026-08-23 handoff stands unmeasured.
One undiagnosed browser-console 404 for a single resource appeared during console boot in all three runs. It is not on the acceptance surface, the scratch app declares no branding assets, and the stack was torn down before it could be identified. Recorded as an observation and deliberately NOT filed — filing an unidentified 404 would be a guess, and identifying it is a fresh boot's work.
Teardown
Dev server killed by exact PID (never by process name); port 3477 confirmed closed. Scratch worktree removed after reading its git status (only the untracked examples/app-relsort/ plus the pnpm-lock.yaml edit that linking it caused — nothing tracked, nothing belonging to another agent); git worktree list afterwards shows only the shared checkout and two other agents' worktrees, untouched. The shared checkout /home/user/objectstack is clean, and no *11345* branch exists locally or on the remote.
{
"issue": 11345,
"status": "done",
"branch": null,
"pr": null,
"premise_still_valid": true,
"summary": "Verification-only dispatch: the acceptance repro prescribed by the 2026-08-26/28/29 unlock chain was built as a throwaway scratch app and driven in a real Chromium, and it PASSES at the current pin. On an objectstack-booted console at .objectui-sha 9602dc820450 (dist stamp and served asset filenames both verified to be that build, not a stale bundle), a derived related list from a lookup declaring relatedList:'primary' renders its child rows in the child object's DEFAULT list-view sort order - seq_no 10/20/30/40 against a creation order of 40/10/30/20 - and the list request carries the ordering parameter on the wire. Negative control passes: a sibling child object identical except that its default list view omits sort emits NO ordering parameter and falls back to ascending record id (20/10/40/30). A reverse verification flipping the declared order to desc moved both legs in the predicted direction (wire sort=-seq_no, rows 40/30/20/10) and the restore leg was proven back to byte-identical. One spelling fact the card should record: the parameter reaches the wire as sort=seq_no, not as a literal $orderby= - objectui's serializeOrderBy (packages/data-objectstack/src/index.ts:332, applied :3292-3293) lowers QueryParams.$orderby to this server's sort shorthand before the request leaves the browser. Zero repo writes, no branch, no PR; scratch worktree and dev server torn down.",
"rendered_order_observed": {
"sorted_child_asc": "Item 10, Item 20, Item 30, Item 40 (creation order was 40/10/30/20)",
"sorted_child_desc_reverse_verification": "Item 40, Item 30, Item 20, Item 10",
"negative_control_no_list_view_sort": "Item 20, Item 10, Item 40, Item 30 = ascending record id (PK fallback), unchanged across all three runs"
},
"orderby_query_captured_verbatim": {
"positive_asc_raw": "GET http://localhost:3477/api/v1/data/rs_child?top=5&skip=0&sort=seq_no&filter=%5B%22parent%22%2C%22%3D%22%2C%22eSxcYqhvwX3suXbM%22%5D",
"positive_asc_decoded": "GET /api/v1/data/rs_child?top=5&skip=0&sort=seq_no&filter=[\"parent\",\"=\",\"eSxcYqhvwX3suXbM\"]",
"positive_desc_decoded": "GET /api/v1/data/rs_child?top=5&skip=0&sort=-seq_no&filter=[\"parent\",\"=\",\"eSxcYqhvwX3suXbM\"]",
"negative_control_decoded": "GET /api/v1/data/rs_child_nosort?top=5&skip=0&filter=[\"parent\",\"=\",\"eSxcYqhvwX3suXbM\"] - no ordering parameter present",
"spelling_note": "$orderby is objectui's internal QueryParams member; it is serialized to this server's sort shorthand by serializeOrderBy (objectui packages/data-objectstack/src/index.ts:332, applied :3292-3293) and emitted as sort= by objectstack packages/client/src/index.ts:5309-5316. A pin test should assert sort=, not the OData token."
},
"console_pin_served": "9602dc820450dda956843c6cfe5b329bcf88c757 - repo .objectui-sha and packages/console/dist/.objectui-sha agree; pnpm check:console-sha printed 'Console dist matches the objectui pin (objectui@9602dc820450)'; the console was built from that pin in this run and the served /_console/ asset filenames (index-CjyVRHjI.js, framework-BnvBDV_V.js, ui-components-h8wdbxbM.js, vendor-radix-DFmVRiEf.js, vendor-react-CsWFRkED.js) are the files that build emitted. objectui#6088 containment verified by CONTENT at the pinned commit (deriveRelatedLists.ts: sort? on DerivedRelatedList :94, convertSortToQueryParams :131, emit :205) because the clone is shallow and ancestry is unavailable.",
"environment": "Scratch app examples/app-relsort (namespace rs; rs_parent + rs_child + rs_child_nosort), built inside detached worktree objectstack-issue-11345 at origin/main 74049254 and never committed. objectstack dev --ui --seed-admin -p 3477 -d file:/tmp/relsort-11345/data.db, SqlDriver(better-sqlite3), 30 plugins, dev admin admin@objectos.ai. Browser: preinstalled Chromium /opt/pw-browsers/chromium via executablePath, playwright 1.62.1, headless, request interception on every /api/v1/data/ request. Record detail URL /_console/apps/rs_app/rs_parent/record/eSxcYqhvwX3suXbM.",
"fork_taken": "PASS - reported as a measurement; the PM closes the card, not me. Neither the pin-lag fork nor the framework-defect fork applies: the pin demonstrably contains objectui#6088 and the behaviour it promises is present.",
"tests": "No repo tests were run and none were owed - zero files changed, so no gate family derives. Verification commands, all foreground, exit codes captured before any pipe: (1) pnpm install in the worktree, exit 0. (2) shallow fetch of objectui at the pinned SHA, HEAD verified equal to the pin. (3) pnpm install --frozen-lockfile --prefer-offline --prod=false in the objectui clone, exit 0. (4) bash scripts/pm/os-verify-lock.sh -c 'pnpm objectui:build' - VERDICT line 'command-exit 0 - held the lock 399s'; the script's own assertions printed 'Bundle canary import/jobs present' and 'Console bundle carries THIS tree's @objectstack/spec, and only it'. (5) bash scripts/pm/os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/example-relsort^... --concurrency=2' - VERDICT 'command-exit 0 - held the lock 196s'; turbo reported '56 successful, 56 total'. (6) pnpm check:console-sha printed 'Console dist matches the objectui pin (objectui@9602dc820450)'. (7) Three browser runs of the interception script - run1 asc, run2 desc, run3 asc restored - with the captured request lines and rendered rows quoted above. Reverse verification: mutation confirmed ON DISK by git hash-object moving bddf1b9a to 5944fc9d plus grep counts of both the injected and the removed text, under a trap-based restore; the RESTORE leg was proven the same way (hash back to bddf1b9a exactly, served /api/v1/meta/view back to order 'asc', run3 reproducing run1's wire line and row order). No dist ablation was involved - the mutated artefact is the scratch app's own source, recompiled by os dev's watcher and re-read from the server's metadata endpoint before each browser run, so there is no stale-dist path here.",
"mcp_calls": "0 - the container's repo-scoped REST probe returned 200, so every GitHub read and write on this run went through curl on the REST channel (issue body, all 9 comments, the claim comment POST, and this report comment). No MCP GitHub tool was called.",
"open_questions": [],
"out_of_scope_findings": []
}
ACCEPT — acceptance repro PASSES; closing per the card's recorded close condition (domain:spec seat, session_01KX8wnyjStaZcuMyAMNsy3N, R1 wave 3).
The 2026-08-23 conversion comment set the condition: "closes when that [objectui#5795 → objectui#6088] lands and the acceptance repro from this body (10/20/30/40) passes." Both halves are now met:
Landed: objectui#6088 merged 2026-08-24; console pin 9602dc820450 verified to contain it — by CONTENT at the pinned commit (deriveRelatedLists.ts: sort? on the descriptor, convertSortToQueryParams, the emit site) AND by the served build (check:console-sha green; served asset filenames = that build's output).
Measured, in a real browser (scratch app rs_parent/rs_child, children created 40/10/30/20 with child default list view sort: seq_no asc):
rendered order 10 / 20 / 30 / 40 ✓; wire request: GET /api/v1/data/rs_child?top=5&skip=0&sort=seq_no&filter=["parent","=","…"] ✓
reverse verification: flipping the declared sort to desc moved BOTH legs as predicted (sort=-seq_no, rows 40/30/20/10); restore leg proven byte-identical and reproducing run 1
negative control: a sibling child object with NO list-view sort emits no ordering parameter and falls back to ascending record id — direction-1's "no sort in, no $orderby out" preserved.
One spelling fact for the record: the parameter reaches the wire as sort=seq_no, not a literal $orderby= — objectui's serializeOrderBy lowers QueryParams.$orderby to this server's sort shorthand before the request leaves the browser. The 17.0.0 migration prose ("a related list inside a form sorts by its own list view's sort") is now TRUE as written, unchanged — declared became enforced, exactly what direction 1 bought.
Downstream: the EHR blocker recorded in this card's body is released on the platform side (the replacement path freeze can lift). The optional follow-up carried since 2026-08-26 — pinning the inheritance rule as contract text in relatedList's .describe() (the #8704 pattern, path-limb work) — is filed as its own card with the measurement attached, so it stops living in comments.
Activity
claude commentedon Aug 23, 2026
Triage:
needs-user-decision,domain:spec, Feature —— 排序声明位是契约形状提案(spec 面新增键 或 语义继承规则),人工地板。维护者亲filed,复现完整(17.2.0 真机)。姊妹卡 objectui#5795(组件侧同一现象)已并挂pm:blocked指向本卡 —— 单一派发入口在此。四维分析(中文):
view.form.defaultSort时的迁移说明(「相关列表按子对象列表视图自身 sort 排序」)与 17.2.0 实测不符 —— 要么兑现迁移说明(继承路线),要么给新声明位;维持「说明与行为都不成立」最伤。sort(兑现既有迁移说明)不新增任何 spec 面;字段级relatedListSort是新键扩面,仅当继承语义确实不够表达(同一子对象在不同父视图要不同排序)才值得付。选项:1) 派生查询继承子对象列表视图的
sort(兑现 17.0.0 迁移说明,零新键);2) 新增字段级relatedListSort声明位(spec 契约扩面,条款②);3) 两者皆做(继承为默认,字段级覆写)。推荐:方向 1 —— 零扩面、兑现已发布的迁移承诺、修复 declared≠enforced;若维护者判「同一子对象不同父上下文需不同排序」是真需求,再走 3 的覆写增量。
Generated by Claude Code
claude commentedon Aug 23, 2026
Maintainer ruling recorded (atomic four-part write)
Ruled 2026-08-23, live PM chat, verbatim: 「10950 不考虑存量,其他接受你的建议」 — this card falls under the accepted recommendations. Ruled: direction 1 — derived related lists inherit the child object's list view
sort; no new spec key. This honours the published 17.0.0 migration note (declared becomes enforced).needs-user-decision→pm:queuein the same stroke. Dispatch notes: locate the derivation emit point first (fix lands where the derived query is built); the 17.0.0 migration prose and 17.2.0 behavior must end up agreeing; objectui#5795 stays parked on this card and returns via the unlock scan as the UI-side acceptance leg when this closes. Field-levelrelatedListSortwas NOT approved — do not add a key.Generated by Claude Code
os-steve commentedon Aug 23, 2026
Claim: PM seat
session_01T9cDbY2NBiVJWYx3BpWfH2(/pm-dispatch, spec lane) — dispatching to an os-dev subagent under the recorded maintainer ruling (2026-08-23 15:02Z, direction 1: derived related lists inherit the child object's list viewsort; no new spec key —relatedListSortNOT approved).Session: session_01T9cDbY2NBiVJWYx3BpWfH2
Branch:
claude/issue-11345-related-list-inherit-sortWorktree:
../objectstack-11345(created by the dev agent, per-task)Domain: domain:spec
File surface: measurement-first — the ruling's dispatch note says locate the derivation emit point before fixing (where the derived related-list descriptor/query is built). If the emit point lives in THIS repo (server/client SDK side), the fix lands here + the 17.0.0 migration prose must end up agreeing with behaviour. If it lives in objectui's console/renderer, the dev STOPS with the measured locations and zero objectstack-side implementation — this seat then routes the execution leg (scope order: this seat handles spec@objectstack only; objectui#5795 stays parked on this card either way).
Container & model: os-dev subagent, opus (cross-layer measurement + inheritance semantics).
Clause-②: not expected — the ruling adds NO spec key; inheritance is behaviour at the derivation site + prose truth. If the fix turns out to require a
packages/spec/src/**diff (e.g. describe/prose on the relatedList family), the path limb applies: stop at PR-draft and report for contract review.Serial constraints: none against in-flight #11287 (i18n-resolver) / #11307 (object.zod.ts annotation).
Generated by Claude Code
os-steve commentedon Aug 23, 2026
os-dev-report
Route: STOP — the emit point lives wholly in objectui. Zero objectstack-side implementation, no branch, no PR. Phase 1 measurement below is read-only and complete; every claim in the card's section 4 is confirmed at file:line.
Ruling honoured
Nothing was added, nothing was edited. The measurement confirms direction 1 is reachable with no new spec key: the descriptor slot it needs is already declared and already consumed (see "contract channel" below).
Measurement map
(a) Descriptor derivation — objectui
objectui/packages/app-shell/src/utils/deriveRelatedLists.ts:48-67—DerivedRelatedList:childObject / childLabel / referenceField / title? / columns? / isOwned / isPrimary. Nosort, nofilter.objectui/packages/app-shell/src/utils/deriveRelatedLists.ts:133-146— the emit point. Builds that descriptor fromfieldDef.relatedList / relatedListTitle / relatedListColumnsonly. It iterates the child object defs at:118but never reads their views.objectui/packages/app-shell/src/views/RecordDetailView.tsx:1052-1057— call site:deriveRelatedLists(objectDef, objects, { canRead }).objectui/packages/app-shell/src/views/RecordDetailView.tsx:1955-1978— second drop point: maps each descriptor intobuildDefaultPageSchema'srelatedshape, emittingtitle / objectName / relationshipField / columns? / icon? / isPrimary?.objectui/packages/plugin-detail/src/synth/buildDefaultPageSchema.ts:174-188— therelatedoption type has nosortmember;:683-690—componentNode('record:related_list', …)forwardstitle/objectName/relationshipField/columns/limit/iconand no sort.(b) Query assembly — objectui
objectui/packages/plugin-detail/src/renderers/record-related-list.tsx:211—defaultSort={schema.sort}. The renderer already forwards a block-levelsort; the channel is open and unfilled on the derived path.objectui/packages/plugin-detail/src/RelatedList.tsx:251-267—normalizeSortSpec:'field'/'-field'string or[{field, order}]to the array form.objectui/packages/plugin-detail/src/RelatedList.tsx:519-529— the assembly, verbatim confirmation of the card:const orderby = fetchSortField ? [{…}] : defaultSortSpec; if (orderby.length > 0) params.$orderby = orderby;— no sort in, no$orderbyout, hence the PK fallback order.objectui/packages/plugin-detail/src/RelatedList.tsx:383-389—windowed = !dataProvided && !!api && effectivePageSize > 0 && dataSource.find && !filterActive. A derived related list self-fetches, so it is windowed by default: an inherited sort would take effect through this exact path.(c) Which child list view is "the" list view — objectstack owns the resolver, already exported and already consumed
objectstack/packages/spec/src/ui/view.zod.ts:4111-4198—expandViewContainerWithDiagnostics/expandViewContainer. The item matching the declared defaultlistis flaggedisDefault(:4146-4150); with nolistdeclared, the first list item takes the flag (:4147).objectui/packages/app-shell/src/providers/MetadataProvider.tsx:9imports it (from '@objectstack/spec/ui'),:413-414calls it,:377-378if (view.isDefault) bucket.primary = entry,:429-431if (extra.primary && !obj.list) merged.list = extra.primary.listafter that merge — theisDefaultexpanded list view. Itssortis declared atobjectstack/packages/spec/src/ui/view.zod.ts:1470-1486(union: legacy string"field desc"|[{field, order}], withdirectionaliased toorder).Contract channel already open (this is why no new key is needed)
objectstack/packages/spec/src/ui/component.zod.ts:883-928—RecordRelatedListProps.sort:z.union([z.string(), z.array(strictObject({ field, order }))]).optional().describe('Sort order for related records'). Declared, parsed, forwarded by the renderer, consumed byRelatedList. Direction 1 only has to fill it on the derived path.The 17.0.0 prose
objectstack/packages/spec/src/ui/view.zod.ts:2487-2494— theform.defaultSortretiredKey text: "nothing read it: a related list inside a form sorts by its own list view'ssort."content/docs/references/ui/view.mdx:284(generated) andpackages/spec/liveness/view.json:355.skills/objectstack-ui/SKILL.md:151-160,skills/objectstack-data/rules/relationships.md:316-335cover prominence / title / columns only), so no published skill text is falsified either.Is there ANY objectstack-side slice?
No implementation slice. Everything objectstack owns here is already in place and correct: the default-list-view resolver, the ListView
sortshape, and therecord:related_list.sortprop. Confirmed no server/SDK-side related-list query builder exists —grepforrelatedListFilter|relatedListColumnsacrosspackages/runtime/src packages/rest/src packages/objectql/src packages/metadata/srcreturns nothing,packages/consoleis a stub (README + CHANGELOG + package.json only), and the onlyrecord:related_listoccurrences in objectstack sources are lint validators, the spec itself, and hand-authoredplatform-objectspages. This matches the recorded #8704 precedent, which already ruled the derivation lives wholly in objectui (packages/spec/src/data/field.test.ts:596-600).The one optional objectstack slice, offered for routing rather than taken: mirroring the #8704 pattern, the inheritance rule could be pinned as contract text in
relatedList's.describe()(asrelatedListFilterdid atfield.zod.ts:1014-1047, pinned byfield.test.ts:602-608). That would be apackages/spec/src/**diff and therefore the path limb — not started, since the dispatch says stop at zero objectstack implementation when the emit point is objectui.Handoff — what the objectui-side fix is
Three edits, no new keys, no spec change:
deriveRelatedLists.ts— addsort?toDerivedRelatedListand emit it from the child object def's default list view (child.list?.sort), never from a field key. The child def is already in hand at:118; no new plumbing.RecordDetailView.tsx:1955-1978— carrysortonto therelatedentry (childObjectDefis already resolved at:1956).buildDefaultPageSchema.ts:174-188and:683-690— addsortto therelatedoption type and forward it into therecord:related_listnode. Renderer andRelatedListneed no change.Shape caveat for the implementer: the ListView legacy string form is space-separated
"field desc"(view.zod.ts:1470), whilenormalizeSortSpec(RelatedList.tsx:258-266) reads a string as'field'/'-field'. Copying a legacy string through verbatim would emit$orderbyon a field literally namedseq_no desc. The inheritance must normalize the ListView union into the array form, or inherit only the array arm.The three acceptance pins the dispatch specified all land in objectui: (i) derived query carries
$orderbyfrom the child list view'ssort; (ii) child with no list-view sort still emits no$orderby; (iii) the view read is theisDefaultone.Out-of-scope findings (searched before filing; no new cards created)
relatedListFilternon-consumption — confirmed as the same gap at the same two lines (deriveRelatedLists.ts:133-146emits nofilter;RecordDetailView.tsx:1959-1978forwards none;buildDefaultPageSchema.ts:683-690has nofilter), while the block prop exists (component.zod.ts:930) and the spec's contract text binds every consumer. Already filed: objectui#4664 (open,pm:queue). Not fixed, no duplicate card.pm:blocked, parked here). Its root-cause section matches this measurement exactly.record:related_list.sortis silently dropped whenever the list leaves windowed mode.windowedgoes false while the client text filter is active (RelatedList.tsx:383-389) and the client-mode path returns rows unsorted unless the user clicked a column (:731-733,if (windowed || !sortField) return filteredData;). So an authored sort stops applying the moment a viewer types in the filter box — pre-existing, affecting the authored prop today, and it will affect the inherited sort identically. Deliberately not filed: it lands in objectui on the same file surface objectui#5795 already owns and the PM routes objectui; a third overlapping card risks two agents on one file. Recommend attaching it to objectui#5795 as an acceptance sub-point.Report
{ "issue": 11345, "status": "blocked", "branch": null, "pr": null, "premise_still_valid": true, "summary": "Measurement-first routing: the derived related-list descriptor is emitted in objectui (deriveRelatedLists.ts:133-146, re-dropped at RecordDetailView.tsx:1955-1978 and buildDefaultPageSchema.ts:174-188/683-690), and the $orderby assembly is objectui too (RelatedList.tsx:519-529, fed via record-related-list.tsx:211). Both halves the ruling touches are objectui-side, so per dispatch I stopped with zero objectstack implementation: no worktree, no branch, no PR, no spec diff, no prose edit. objectstack's share of direction 1 is already in place and needs no change - the default-list-view resolver (view.zod.ts:4111-4198, isDefault, already imported by objectui's MetadataProvider), the ListView sort shape (view.zod.ts:1470-1486), and the record:related_list.sort prop the inheritance would fill (component.zod.ts:883-928), which is why no new spec key is required. The 17.0.0 migration prose (view.zod.ts:2487-2494) stays as written and becomes true once the objectui leg lands.", "measurement_map": { "descriptor_derivation": "objectui/packages/app-shell/src/utils/deriveRelatedLists.ts:48-67 (interface, no sort), :133-146 (emit point); re-dropped at objectui/packages/app-shell/src/views/RecordDetailView.tsx:1955-1978 and objectui/packages/plugin-detail/src/synth/buildDefaultPageSchema.ts:174-188 + :683-690", "query_assembly": "objectui/packages/plugin-detail/src/RelatedList.tsx:519-529 ($orderby only when handed a sort), :251-267 (normalizeSortSpec), :396-403 (defaultSortSpec memo), :383-389 (windowed predicate); fed by objectui/packages/plugin-detail/src/renderers/record-related-list.tsx:211 (defaultSort={schema.sort})", "which_child_list_view": "The isDefault expanded list view: objectstack/packages/spec/src/ui/view.zod.ts:4111-4198 flags it (:4146-4150; first list item when no `list` is declared, :4147); objectui/packages/app-shell/src/providers/MetadataProvider.tsx:9,:413-414,:377-378,:429-431 merges it onto the child object def as `obj.list`. Its sort shape: objectstack/packages/spec/src/ui/view.zod.ts:1470-1486.", "contract_channel_already_open": "objectstack/packages/spec/src/ui/component.zod.ts:883-928 - RecordRelatedListProps.sort is declared, parsed, forwarded and consumed; direction 1 only needs to fill it, hence no new spec key.", "prose": "objectstack/packages/spec/src/ui/view.zod.ts:2487-2494 (retiredKey text), mirrored at content/docs/references/ui/view.mdx:284 and packages/spec/liveness/view.json:355. No edit made or needed - behaviour moves to match it. Published skills do not repeat the claim (skills/objectstack-ui/SKILL.md:151-160, skills/objectstack-data/rules/relationships.md:316-335).", "objectstack_side_slice": "None for implementation. No server/SDK related-list query builder exists: relatedListFilter|relatedListColumns absent from packages/runtime/src, packages/rest/src, packages/objectql/src, packages/metadata/src; packages/console is a stub; record:related_list in objectstack appears only in lint validators, the spec, and hand-authored platform-objects pages. Matches the #8704 precedent recorded at packages/spec/src/data/field.test.ts:596-600. Optional, NOT taken: pinning the inheritance rule as contract text in relatedList's .describe() (the #8704 pattern) - that would be a packages/spec/src/** diff and therefore the path limb.", "objectui_fix": "1) deriveRelatedLists.ts - add sort? to DerivedRelatedList, emit from child.list?.sort (child def already in hand at :118). 2) RecordDetailView.tsx:1955-1978 - carry sort onto the related entry (childObjectDef resolved at :1956). 3) buildDefaultPageSchema.ts:174-188 and :683-690 - add sort to the related option type and forward into the record:related_list node. Renderer and RelatedList unchanged. CAVEAT: ListView legacy string sort is space-separated 'field desc' (view.zod.ts:1470) while normalizeSortSpec reads 'field'/'-field' (RelatedList.tsx:258-266) - normalize to the array form or inherit only the array arm, else $orderby goes out on a field literally named 'seq_no desc'." }, "tests": "None run - zero edits were made, so there is nothing to verify and no gate to derive. Phase 1 was read-only static tracing across both checkouts (/home/user/objectstack at b863f012fa, /home/user/objectui), by grep and file reads only. No worktree, no install, no build, no test, no dispatch-gates run, no lock taken. Declared explicitly rather than reported as a pass.", "open_questions": [], "out_of_scope_findings": [ "already filed as objectui#4664 (open, pm:queue): the same emit point ignores relatedListFilter - confirmed at deriveRelatedLists.ts:133-146, RecordDetailView.tsx:1959-1978, buildDefaultPageSchema.ts:683-690; not fixed, no duplicate card", "already filed as objectui#5795 (open, pm:blocked, parked on #11345): this card's objectui execution half; its root-cause section matches this measurement", "NOT filed (reported for attachment to objectui#5795): a declared record:related_list.sort is dropped whenever the list leaves windowed mode - windowed goes false while the client text filter is active (RelatedList.tsx:383-389) and the client path returns rows unsorted unless a column was clicked (:731-733). Pre-existing, affects the authored prop today and will affect the inherited sort identically. Not filed to avoid a third card on the file surface objectui#5795 already owns" ] }Generated by Claude Code
os-steve commentedon Aug 23, 2026
PM routing: blocked-with-measurement — the dependency inverted
Blocked-by: objectui#5795
Collected the measurement round (os-dev-report above, zero edits, both trees clean — correct stop per the dispatch's routing rule). Findings of record:
deriveRelatedLists.ts:133-146+ two re-drop sites) and the$orderbyassembly (RelatedList.tsx:519-529). No objectstack-side implementation slice exists — no server/SDK related-list query builder (measured across runtime/rest/objectql/metadata; matches the feat(spec): no declarative filter for auto-derived related lists — add field-levelrelatedListFilterso soft-deleted child rows can be excluded without abandoning the auto record page #8704 precedent).isDefaultlist-view resolver (view.zod.ts:4111-4198), sort shape (:1470-1486), and the openrecord:related_list.sortcontract channel (component.zod.ts:883-928) — which is exactly why the ruling needs no new spec key.view.zod.ts:2487-2494, mirrored in view.mdx:284 and liveness/view.json:355): behaviour moves to match it when the objectui leg lands; no prose edit needed or made.So the parking arrangement inverts: objectui#5795 flipped
pm:blocked→pm:queuethis stroke with the full map, the ruling, the legacy-string-sort caveat, and the windowed-mode pre-existing defect relayed onto it (comment posted there). This card stayspm:blockedon objectui#5795 and closes when that lands and the acceptance repro from this body (10/20/30/40) passes.Noted, not taken (would be a
packages/spec/src/**diff → path limb, and adds nothing until the reader exists): pinning the inheritance rule as contract text inrelatedList's.describe(), the #8704 pattern — a candidate follow-up once the objectui leg ships.Generated by Claude Code
3 remaining items
os-zhuang commentedon Aug 26, 2026
Unlock scan (spec seat R8,
session_012xGvxcwPRTJfA7RfjXEYA4):pm:blocked→pm:queue, assignee cleared in the same stroke (agent identityos-steve, the seat that parked the card — not a human assignment).Release double-check (放行双查), both passed:
closed_by_pull_requests= 0).Remaining work (the reason this returns to queue instead of closing): the acceptance half is unverified. The body's repro (4 child records,
seq_no10/20/30/40, derived related list must render 10/20/30/40 with$orderbyon the wire) has not been run against the landed change.Dispatch premise for the next claim (pin lag): objectstack consumes the console via pin. Before running the repro on an objectstack-booted console, verify the console pin covers objectui PR #6088's merge commit (REST
comparefor ancestry, not localmerge-base). If the pin predates it, the repro will falsify a premise that is actually just pin lag — in that case re-park this cardpm:blockedon the console bump card instead of reporting the feature missing.Also on the card's plate from the measurement round (optional, per the 2026-08-23T22:41Z comment): pinning the inheritance rule as contract text in
relatedList's.describe()(the #8704 pattern) — apackages/spec/src/**diff, i.e. contract-review path if taken.Generated by Claude Code
os-project-manager commentedon Aug 29, 2026
Unlock (domain:spec seat,
session_01KX8wnyjStaZcuMyAMNsy3N, R1): the 2026-08-28 park target #12912 is CLOSED — asduplicate, so the release double-check was run on the substance rather than the closure:.objectui-shaonorigin/main(7404925) =9602dc820450…, and that objectui commit is dated 2026-08-25T22:43:27Z — after objectui#6088's merge (2026-08-24T16:56:18Z) — so the pin contains the inheritance fix. (Method: shallow fetch of the pinned commit from the objectui remote, read-only; the local clone is shallow, so no local ancestry claim is made. Both aremaincommits under a merge queue, so date order is containment here.)Returned
pm:blocked→pm:queuesame-stroke. Remaining work for the next claim, unchanged from the 2026-08-26/28 comments:seq_no10/20/30/40 must render 10/20/30/40 and the derived related-list request must carry$orderbyon the wire. Browser/dogfood verification ⇒ dispatch shape ismode:cloud.relatedList's.describe()— the feat(spec): no declarative filter for auto-derived related lists — add field-levelrelatedListFilterso soft-deleted child rows can be excluded without abandoning the auto record page #8704 pattern.Generated by Claude Code
os-project-manager commentedon Aug 29, 2026
Claim: PM loop round R1, wave 3 (domain:spec seat) — verification-only dispatch (the acceptance repro the 2026-08-26/28/29 unlock chain prescribes)
Session:
session_01KX8wnyjStaZcuMyAMNsy3NBranch: none expected — zero code changes are in scope; a branch/PR appears only if the repro FAILS and the failure is measured to be a framework-side defect (then: stop and report the fork, do not fix on this card)
Worktree:
objectstack-issue-11345(scratch app + browser run only)Domain:
domain:specFile surface: none in the repo. Deliverable = a browser-measured acceptance reading: derived related list (
relatedList:'primary') renders child rows in the child object's default list-viewsortorder (seq_no 10/20/30/40) and the list request carries$orderbyon the wire.Container & model: M (browser dogfood),
mode:subagent,model: claude-opus-5. This container carries Chromium + Playwright preconfigured; the browser/dogfood-cloud-card rule was written for containers without one.Clause-②: no — verification only.
Serial constraints cleared: zero file writes; the pin premise was re-verified this round (
.objectui-sha=9602dc820450, objectui commit 2026-08-25T22:43Z, AFTER objectui#6088's merge — containment established; reading recorded in the 15:23Z unlock comment). The optionalrelatedList.describe()contract-text pin (the #8704 pattern) is explicitly NOT this card's work — path-limb work for a separate claim.Generated by Claude Code
claude commentedon Aug 29, 2026
Claim: os-dev subagent, verification-only run (session
session_01KX8wnyjStaZcuMyAMNsy3N, dispatched by the domain:spec PM seat, R1 wave 3).No branch, no PR by design — this dispatch is the acceptance repro the 2026-08-26 / 08-28 / 08-29 unlock chain prescribes, not an implementation card. Zero repo writes; scratch app lives in a throwaway worktree
objectstack-issue-11345and is torn down after the run. Assignee left untouched (PM owns it).What I am measuring, against a console booted at the current pin (
.objectui-sha=9602dc820450):relatedList: 'primary'withrelatedListColumns; child hasseq_no; the child's default list view declaressortbyseq_no asc.$orderbyon the wire (captured via Playwright request interception).$orderby.Forks per the dispatch: PASS reports the measurement (PM closes, not me); FAIL measured as pin lag reports that fork; FAIL measured as a real defect stops with the diagnosis and no fix. The optional
relatedList.describe()contract-text pin is explicitly not this card.Generated by Claude Code
claude commentedon Aug 29, 2026
os-dev-report
Verdict: PASS — the acceptance repro passes at the current pin. Fork taken: PASS (no branch, no PR, zero repo writes).
Verification-only run (os-dev subagent of PM session
session_01KX8wnyjStaZcuMyAMNsy3N). The 2026-08-26 / 08-28 / 08-29 unlock chain's acceptance repro was built as a throwaway scratch app and driven in a real Chromium via Playwright with request interception. Both the render leg and the wire leg pass, the negative control passes, and a reverse verification (flipping the declared sort todesc) moves both legs in the predicted direction.Environment actually measured
objectstack-issue-11345, detached atorigin/main=74049254. No branch created, none pushed —git ls-remote originfor any*11345*head returns nothing..objectui-sha=9602dc820450dda956843c6cfe5b329bcf88c757;packages/console/dist/.objectui-shastamp = the same SHA;pnpm check:console-shaprintedConsole dist matches the objectui pin (objectui@9602dc820450). The console was built from that pin in this run bypnpm objectui:build(shallow clone of objectui at the pinned commit), and the asset filenames served byGET /_console/(assets/index-CjyVRHjI.js,assets/framework-BnvBDV_V.js,assets/ui-components-h8wdbxbM.js,assets/vendor-radix-DFmVRiEf.js,assets/vendor-react-CsWFRkED.js) are exactly the files that build emitted — so the booted console IS that pin, not a stale bundle.packages/app-shell/src/utils/deriveRelatedLists.tscarriessort?onDerivedRelatedList(line 94), a doc block naming this card and the maintainer ruling (lines 78-79),convertSortToQueryParams(list?.sort)(line 131), and the emit...(inheritedSort ? { sort: inheritedSort } : {})(line 205).examples/app-relsort(namespacers) — NOT an example in the repo. Port3477,objectstack dev --ui --seed-admin -d file:/tmp/relsort-11345/data.db, SqlDriver(better-sqlite3), dev adminadmin@objectos.ai./opt/pw-browsers/chromiumviaexecutablePath(playwright 1.62.1's own browser revision is not present in this container;playwright installwas NOT run).The repro as declared
rs_parent(parent).rs_childandrs_child_nosort(children), each related by a lookup declaringrelatedList: 'primary'plusrelatedListTitleandrelatedListColumns: ['name', 'seq_no']; each child has aseq_nonumber field.rs_child's DEFAULT list view declaressort: [{ field: 'seq_no', order: 'asc' }].rs_child_nosort's default list view is identical with thesortkey omitted — that is the negative control.GET /api/v1/meta/view?object=rs_childreturns a single itemrs_child.defaultwith"isDefault": trueandconfig.sort=[{"field":"seq_no","order":"asc"}].Result 1 — render leg: PASS
The derived "Sorted Items" tab on the parent record detail page (
/_console/apps/rs_app/rs_parent/record/eSxcYqhvwX3suXbM) renders, top to bottom:A full-page screenshot confirms it visually, including an ascending sort indicator on the Seq No column header. This is neither creation order (40/10/30/20) nor the record-id fallback order.
Result 2 — wire leg: PASS, with one spelling fact this card should record
Verbatim captured request (raw, exactly as it went out), positive case:
URL-decoded:
top=5&skip=0&sort=seq_no&filter=["parent","=","eSxcYqhvwX3suXbM"]The ordering parameter is on the wire as
sort=seq_no, not as a literal$orderby=. That is not a miss.$orderbyis objectui's internalQueryParamsmember name, and objectui lowers it to this server'ssortshorthand before the request leaves the browser. Both ends are pinned in code at the measured pin:packages/plugin-detail/src/RelatedList.tsx(around 519-529):const orderby = fetchSortField ? [...] : defaultSortSpec; if (orderby.length > 0) params.$orderby = orderby;packages/data-objectstack/src/index.ts:332— "Serialize a$orderbyto the server'ssortshorthand (field,-other_field)" — applied at:3292-3293:const sortStr = serializeOrderBy(params.$orderby); if (sortStr) queryParams.set('sort', sortStr);packages/client/src/index.ts:5309-5316emitssort=for the canonical option.So "carries
$orderbyon the wire" is satisfied bysort=seq_no. A reader of this card should expect that spelling, and any future pin test should assertsort=, not the OData token.Result 3 — negative control: PASS
Verbatim captured request for the child whose default list view declares no sort:
No ordering parameter at all. Rendered order: Item 20 / Item 10 / Item 40 / Item 30 — which is precisely ascending record id (
2r2slH1S4H39c-GE,QfrBHP2l0WKLkFvC,eKApe1pLPoN8dFot,sYafz_ctxIIno0EKunder binary collation), i.e. the primary-key fallback this card originally reported. The control therefore proves two things at once: absence of a declared sort still emits nothing, and the ordering seen in Result 1 really is the inherited one rather than an incidental default.Result 4 — reverse verification (predicted direction: reversal)
Predicted before running: flipping the child's declared
orderfromasctodescmust flip both the wire token and the rendered order, and must leave the control untouched. Mutation and restore were each confirmed ON DISK by blob hash, never by an editor exit code:git hash-object=bddf1b9a3b9189ac7a81a8d71412292384b60e79; post-mutation =5944fc9d139921404a4be229789f324e35b73bb5, with theorder: 'asc'grep count going 1 to 0 andorder: 'desc'0 to 1. The mutation ran under atrap ... EXIT INT TERMrestore.desc: wiresort=-seq_no; rendered Item 40 / 30 / 20 / 10; control unchanged (no ordering parameter, 20/10/40/30). Server metadata confirmedconfig.sort=[{"field":"seq_no","order":"desc"}]withisDefault: true.git hash-objectback tobddf1b9a...exactly, served metadata back toorder: "asc", and a third browser run reproduced run 1 exactly (wiresort=seq_no, rows 10/20/30/40).No stale-build ambiguity: the mutated artefact is the scratch app's own source, which
os dev's watcher recompiles, and the server's/api/v1/meta/viewwas re-read before each browser run to confirm which sort was actually being served. The console bundle under test was rebuilt from the pin at the start of the run.What this reading does NOT cover
"seq_no desc") was not exercised; only the array arm was declared. The normalization caveat recorded in the 2026-08-23 handoff stands unmeasured.Teardown
Dev server killed by exact PID (never by process name); port 3477 confirmed closed. Scratch worktree removed after reading its
git status(only the untrackedexamples/app-relsort/plus thepnpm-lock.yamledit that linking it caused — nothing tracked, nothing belonging to another agent);git worktree listafterwards shows only the shared checkout and two other agents' worktrees, untouched. The shared checkout/home/user/objectstackis clean, and no*11345*branch exists locally or on the remote.{ "issue": 11345, "status": "done", "branch": null, "pr": null, "premise_still_valid": true, "summary": "Verification-only dispatch: the acceptance repro prescribed by the 2026-08-26/28/29 unlock chain was built as a throwaway scratch app and driven in a real Chromium, and it PASSES at the current pin. On an objectstack-booted console at .objectui-sha 9602dc820450 (dist stamp and served asset filenames both verified to be that build, not a stale bundle), a derived related list from a lookup declaring relatedList:'primary' renders its child rows in the child object's DEFAULT list-view sort order - seq_no 10/20/30/40 against a creation order of 40/10/30/20 - and the list request carries the ordering parameter on the wire. Negative control passes: a sibling child object identical except that its default list view omits sort emits NO ordering parameter and falls back to ascending record id (20/10/40/30). A reverse verification flipping the declared order to desc moved both legs in the predicted direction (wire sort=-seq_no, rows 40/30/20/10) and the restore leg was proven back to byte-identical. One spelling fact the card should record: the parameter reaches the wire as sort=seq_no, not as a literal $orderby= - objectui's serializeOrderBy (packages/data-objectstack/src/index.ts:332, applied :3292-3293) lowers QueryParams.$orderby to this server's sort shorthand before the request leaves the browser. Zero repo writes, no branch, no PR; scratch worktree and dev server torn down.", "rendered_order_observed": { "sorted_child_asc": "Item 10, Item 20, Item 30, Item 40 (creation order was 40/10/30/20)", "sorted_child_desc_reverse_verification": "Item 40, Item 30, Item 20, Item 10", "negative_control_no_list_view_sort": "Item 20, Item 10, Item 40, Item 30 = ascending record id (PK fallback), unchanged across all three runs" }, "orderby_query_captured_verbatim": { "positive_asc_raw": "GET http://localhost:3477/api/v1/data/rs_child?top=5&skip=0&sort=seq_no&filter=%5B%22parent%22%2C%22%3D%22%2C%22eSxcYqhvwX3suXbM%22%5D", "positive_asc_decoded": "GET /api/v1/data/rs_child?top=5&skip=0&sort=seq_no&filter=[\"parent\",\"=\",\"eSxcYqhvwX3suXbM\"]", "positive_desc_decoded": "GET /api/v1/data/rs_child?top=5&skip=0&sort=-seq_no&filter=[\"parent\",\"=\",\"eSxcYqhvwX3suXbM\"]", "negative_control_decoded": "GET /api/v1/data/rs_child_nosort?top=5&skip=0&filter=[\"parent\",\"=\",\"eSxcYqhvwX3suXbM\"] - no ordering parameter present", "spelling_note": "$orderby is objectui's internal QueryParams member; it is serialized to this server's sort shorthand by serializeOrderBy (objectui packages/data-objectstack/src/index.ts:332, applied :3292-3293) and emitted as sort= by objectstack packages/client/src/index.ts:5309-5316. A pin test should assert sort=, not the OData token." }, "console_pin_served": "9602dc820450dda956843c6cfe5b329bcf88c757 - repo .objectui-sha and packages/console/dist/.objectui-sha agree; pnpm check:console-sha printed 'Console dist matches the objectui pin (objectui@9602dc820450)'; the console was built from that pin in this run and the served /_console/ asset filenames (index-CjyVRHjI.js, framework-BnvBDV_V.js, ui-components-h8wdbxbM.js, vendor-radix-DFmVRiEf.js, vendor-react-CsWFRkED.js) are the files that build emitted. objectui#6088 containment verified by CONTENT at the pinned commit (deriveRelatedLists.ts: sort? on DerivedRelatedList :94, convertSortToQueryParams :131, emit :205) because the clone is shallow and ancestry is unavailable.", "environment": "Scratch app examples/app-relsort (namespace rs; rs_parent + rs_child + rs_child_nosort), built inside detached worktree objectstack-issue-11345 at origin/main 74049254 and never committed. objectstack dev --ui --seed-admin -p 3477 -d file:/tmp/relsort-11345/data.db, SqlDriver(better-sqlite3), 30 plugins, dev admin admin@objectos.ai. Browser: preinstalled Chromium /opt/pw-browsers/chromium via executablePath, playwright 1.62.1, headless, request interception on every /api/v1/data/ request. Record detail URL /_console/apps/rs_app/rs_parent/record/eSxcYqhvwX3suXbM.", "fork_taken": "PASS - reported as a measurement; the PM closes the card, not me. Neither the pin-lag fork nor the framework-defect fork applies: the pin demonstrably contains objectui#6088 and the behaviour it promises is present.", "tests": "No repo tests were run and none were owed - zero files changed, so no gate family derives. Verification commands, all foreground, exit codes captured before any pipe: (1) pnpm install in the worktree, exit 0. (2) shallow fetch of objectui at the pinned SHA, HEAD verified equal to the pin. (3) pnpm install --frozen-lockfile --prefer-offline --prod=false in the objectui clone, exit 0. (4) bash scripts/pm/os-verify-lock.sh -c 'pnpm objectui:build' - VERDICT line 'command-exit 0 - held the lock 399s'; the script's own assertions printed 'Bundle canary import/jobs present' and 'Console bundle carries THIS tree's @objectstack/spec, and only it'. (5) bash scripts/pm/os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/example-relsort^... --concurrency=2' - VERDICT 'command-exit 0 - held the lock 196s'; turbo reported '56 successful, 56 total'. (6) pnpm check:console-sha printed 'Console dist matches the objectui pin (objectui@9602dc820450)'. (7) Three browser runs of the interception script - run1 asc, run2 desc, run3 asc restored - with the captured request lines and rendered rows quoted above. Reverse verification: mutation confirmed ON DISK by git hash-object moving bddf1b9a to 5944fc9d plus grep counts of both the injected and the removed text, under a trap-based restore; the RESTORE leg was proven the same way (hash back to bddf1b9a exactly, served /api/v1/meta/view back to order 'asc', run3 reproducing run1's wire line and row order). No dist ablation was involved - the mutated artefact is the scratch app's own source, recompiled by os dev's watcher and re-read from the server's metadata endpoint before each browser run, so there is no stale-dist path here.", "mcp_calls": "0 - the container's repo-scoped REST probe returned 200, so every GitHub read and write on this run went through curl on the REST channel (issue body, all 9 comments, the claim comment POST, and this report comment). No MCP GitHub tool was called.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
os-project-manager commentedon Aug 29, 2026
ACCEPT — acceptance repro PASSES; closing per the card's recorded close condition (domain:spec seat,
session_01KX8wnyjStaZcuMyAMNsy3N, R1 wave 3).The 2026-08-23 conversion comment set the condition: "closes when that [objectui#5795 → objectui#6088] lands and the acceptance repro from this body (10/20/30/40) passes." Both halves are now met:
9602dc820450verified to contain it — by CONTENT at the pinned commit (deriveRelatedLists.ts:sort?on the descriptor,convertSortToQueryParams, the emit site) AND by the served build (check:console-shagreen; served asset filenames = that build's output).rs_parent/rs_child, children created 40/10/30/20 with child default list viewsort: seq_no asc):GET /api/v1/data/rs_child?top=5&skip=0&sort=seq_no&filter=["parent","=","…"]✓descmoved BOTH legs as predicted (sort=-seq_no, rows 40/30/20/10); restore leg proven byte-identical and reproducing run 1$orderbyout" preserved.One spelling fact for the record: the parameter reaches the wire as
sort=seq_no, not a literal$orderby=— objectui'sserializeOrderBylowersQueryParams.$orderbyto this server'ssortshorthand before the request leaves the browser. The 17.0.0 migration prose ("a related list inside a form sorts by its own list view'ssort") is now TRUE as written, unchanged — declared became enforced, exactly what direction 1 bought.Downstream: the EHR blocker recorded in this card's body is released on the platform side (the replacement path freeze can lift). The optional follow-up carried since 2026-08-26 — pinning the inheritance rule as contract text in
relatedList's.describe()(the #8704 pattern, path-limb work) — is filed as its own card with the measurement attached, so it stops living in comments.Closing completed;
pm:dispatchedoff same-stroke.Generated by Claude Code
docs(spec): pin the derived related-list sort-inheritance rule in rel…
docs(spec): pin the derived related-list sort-inheritance rule in rel…