You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec(ui): a list-view grouping field can order its groups only by its own value, so a period label (Q4 2026 / Sep 2026) cannot be grouped in time order without a second grouping level — add an order-by field to GroupingFieldSchema #25786
Filing gate ① a defect with a named landing site (an app is carrying a workaround for a missing spec key; hotcrm AGENTS.md says the gap belongs here). Batch-of: 2026-10-25 browser pass on @objectstack/* 18.1.0 (23 cards: hotcrm 10, objectui 10, objectstack 3). Filed by the maintainer's account (os-steve) from the HotCRM pass driven in session session_01AwpXhq2nFKU6J7GmbCEmZu; ⛔ not a claim. Grade and routing are triage's.
What is seen
HotCRM's 销售预测 list groups by period_start (desc) and then by period_label, because grouping on period_label alone sorts the periods as text ("Sep 2026 → Q4 2026 → Q4 2025 …"). The result is two header rows for every single forecast: 「周期起始 10月1日 · 1」 then 「Oct 2026 · 1」 then the row — six forecasts take 18 rows. 45-crm_forecast.png. The view's own comment (src/sales/views/forecast.view.ts ~:40–63) says why: GroupingFieldSchema is field / order / collapsed, with no key that orders one field's groups by another.
reach: a named producer (HotCRM forecast.view.ts, shipped) and the public UI, measured once.
Expected
GroupingFieldSchema gains an orderBy (a field of the same object whose value orders the groups; default = the grouping field), the server-side grouping query (#14556's shape) sorts groups by it, and objectui's grouped grid renders the group label from field while ordering by orderBy. HotCRM then drops its outer level (one line in the view).
Cross-layer: spec first, then the engine's grouping aggregate, then objectui — the usual parent + per-layer children if triage files it that way.
Dedupe
MCP search_issues objectstack "view grouping sort groups by a different field than the grouping field" → 1 hit, #14556 (closed: server-side grouping, per-group counts, paging), which does not add an ordering field. Report: REPORT.md §3.2.
⛔ Not a claim, ⛔ not a dispatch. finding comes off.
p1 by 「协议新增 p1」: the platform answer adds an authorable key (a protocol addition). Spec first, then the runtime. A group-order key on a list-view grouping field (order a label group by another field). Then objectui renders it, and HotCRM drops its two-level workaround.
Claim: PM loop round 2
Session: session_01E1GNboWQRhh5vidwh2hxTY (the card thread; seat session session_01K2Tat8JEt4AhLASV4V4QWE posts this claim)
Account: marchtian
Branch: claude/project-thread-zciswd
Worktree: objectstack-issue-25786
Domain: domain:spec
Seat: domain:spec#4 (seat post #18917)
File surface: packages/spec/src/ui/view.zod.ts (GroupingFieldSchema) + lint/validate + any server grouping read the measurement needs + tests + changeset; objectui child filed, not edited
Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: "no path-derived mandate", floor sonnet · default opus · ceiling fable), effort: xhigh (p1)
Clause-②: yes
Metadata-protocol: added
Responsibility: n/a — not a defect card
Thread-read: 6397346626
Premise: holds (at main5f5f0b42: GroupingFieldSchema is field / order / collapsed, no order-by key; no open PR names #25786)
Ruling-ref: none; triage first grade under 「协议新增 p1」
Serial constraints cleared: #25755 (seat 2) is in field schemas, not view.zod.ts; no overlap.
Order: /mnt/project-files/spec-seat/orders/25786.md with _common.md (project files). Batch of six on the project owner's order 「你应该并发6」 (2026-10-25). Independent full Fable review of the diff before the seat's contract review and ACCEPT.
{
"issue": 25786,
"status": "done",
"branch": "claude/project-thread-zciswd",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25828",
"session": "session_01E1GNboWQRhh5vidwh2hxTY",
"head": "21dc9ad78953f57f991b69c25800a2db82db8d9d",
"premise_still_valid": true,
"summary": "GroupingFieldSchema gains orderBy (a field of the same object; absent or equal to field = order by the group key). compileListViewGroupQuery adds one min (asc) / max (desc) node per ordered level, and listViewGroupOrderColumn names the header-row column to sort by. Lint judges orderBy on validate/build/runtime save: list-view-field-unknown, list-view-field-dotted (new aggregate axis), and the new list-view-group-order-unorderable. Measured: the server needs no new code. The sort stays the consumer's; the existing aggregate door serves min/max over date/datetime identically on both tiers and refuses text and dotted operands 400 INVALID_FIELD (rest door test §11). The card's Expected line 'the server-side grouping query sorts groups by it' is answered as: the header query carries the value, the consumer sorts the whole header set.",
"tests": "final head 21dc9ad7: `pnpm --filter @objectstack/spec exec vitest run` 800 files, 23119 passed / 1 todo; `pnpm --filter @objectstack/lint exec vitest run` 163 files, 7331/7331; `pnpm --filter @objectstack/rest exec vitest run src/list-view-grouping-query-door.test.ts` 53/53 (9 new in §11). Ablations via scripts/ablation-replace.mjs, each restored blob == HEAD: lint verdict `|| true` 8 red; compiler `...orders.slice(0, 0)` 5 red; padded-name regex widened to `/[\\s\\S]*/` 6 red.",
"gates": {
"derived": "node scripts/pm/dispatch-gates.mjs --commands on the final diff: 114 commands",
"local": "114 run on head 21dc9ad7, exit codes recorded before any pipe: 114 x exit 0",
"ran_reconciliation": "Run reconciliation — 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)",
"prerequisites": "full `pnpm build` 76/76 (check:dual-build-cjs-loads, check:skill-examples read built output); `git fetch --depth=1 origin 621a4876` for check-plugin-teardown-shape --self-test's pinned fixture",
"extra_run": "roster gates with a roster under this diff's paths + typechecks, all exit 0: check-changeset-fixed, check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check:error-code-provenance, spec/lint/rest typecheck",
"ci_at_report": "PR head check-runs: 45 total — 15 success, 10 skipped, 20 in_progress, 0 failure"
},
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"review": {
"reviewer": "isolated Agent subagent at CONTRACT_REVIEW_TIER (the user's full-review rule for spec changes), whole diff origin/main...HEAD",
"verdict_comment_id": 6400202111,
"verdict_comment": "https://github.com/objectstack-ai/objectstack/pull/25828#issuecomment-6400202111",
"rounds": "review 1 CHANGES-REQUIRED (1 major, 2 minor, 2 nit) → fixed in 5d64c378, 6a3bf131, d37faf8f → review 2 ACCEPT-WITH-NITS (5/5 closed, 1 new nit) → 5df57e7f → nit confirmed closed"
},
"line_budget": {
"changed_lines": 992,
"insertions": 972,
"deletions": 20,
"files": 15,
"human_merge_threshold": 3000,
"verdict": "under",
"ratchet_ledgers_touched": 0,
"dropped_refinements_baseline": "unchanged"
},
"files_changed": [
".changeset/25786-grouping-order-by.md",
"content/docs/references/ui/component.mdx",
"content/docs/references/ui/view.mdx",
"packages/lint/src/index.ts",
"packages/lint/src/rule-explanations.ts",
"packages/lint/src/validate-list-view-field-refs.test.ts",
"packages/lint/src/validate-list-view-field-refs.ts",
"packages/rest/src/list-view-grouping-query-door.test.ts",
"packages/spec/api-surface/ui.json",
"packages/spec/authorable-surface/ui.json",
"packages/spec/export-origins/ui.json",
"packages/spec/src/ui/view-grouping-query.test.ts",
"packages/spec/src/ui/view-grouping-query.ts",
"packages/spec/src/ui/view.test.ts",
"packages/spec/src/ui/view.zod.ts"
],
"deviations": [
"First spec ablation was a no-op: the replacement contained the anchor, so ablation-replace counted 1 → 1. Re-run with `...orders.slice(0, 0)`: 5 red.",
"After a scratch probe, `git checkout HEAD -- packages/rest/src/list-view-grouping-query-door.test.ts` wiped the uncommitted §11 tests. They were re-applied from a saved script and committed (d4a9b8fc). Nothing is missing from the final diff.",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer. The pre-push check:commit-card-trailers refused 4 unpublished commits for it. The unpushed range only (HEAD ^origin/main ^d4a9b8fc) was reworded with git filter-branch --msg-filter, with identical trees, before any push. No published history was rewritten.",
"Review F1: the reviewer suggested superRefine, but a native .regex() was taken instead. superRefine made the spec build fail on dropped-refinements.baseline.json (18 new sites). The regex publishes the rule as a JSON Schema pattern. Review 2 accepted it, and its nit (why `field` keeps superRefine) was answered in a TSDoc.",
"main was merged twice: at 6679644a before review, and at abebc4e1 after sibling spec card #25755 landed (no conflicts). Gates and full suites were re-run on the merged head.",
"The order asked me to file the objectui child card, so I ran its dedupe search myself: 2 read-only MCP search calls. os-dev normally leaves dedupe to the filing seat."
],
"mcp_calls": "2 — mcp__github__search_issues x2 (read-only, objectui dedupe for the child card); no MCP write tool",
"api_writes": "4 — POST /repos/objectstack-ai/objectstack/dispatches x4 via scripts/pm (fleet-write relay, as objectstack-fleet[bot]): pr_create #25828; comment 6400202111 on #25828 (review verdict); issue_create objectstack-ai/objectui#13546; this os-dev-report comment. Plus git pushes of the branch (not REST).",
"child_cards": [
"https://github.com/objectstack-ai/objectui/issues/13546 — plugin-grid renders the label from `field` and orders headers by listViewGroupOrderColumn; carries Blocked-by: objectstack-ai/objectstack#25828"
],
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed (no named producer, so not class c) · a dotted grouping.fields[].field is unjudged by lint, pinned silent in packages/lint/src/validate-list-view-field-refs.test.ts, while POST /api/v1/data/:object/query refuses it with 400 INVALID_FIELD \"Unknown field 'period_start.year' … a related record's column cannot be a grouping target\" (measured through the §11 fixture) · carrier: none · in PR Acceptance notes · dedupe words: grouping field dotted path lint, list-view-field-dotted grouping.fields field, grouping target related record column",
"noted, not filed (polish) · `field` keeps its superRefine and dropped-refinements row; the same .regex() would shrink the ledger but changes its issue code custom → invalid_format (consumer-visible) · carrier: none · in PR Acceptance notes and view.zod.ts TSDoc"
]
}
ACCEPT → PR #25828 comment 6400515519 at head 21dc9ad78953 · seat domain:spec#4 · 2026-10-25T17:01Z. Independent full review (6400202111) and contract review PASS (6400331596); CI green; not governed. Marked ready and enqueued through the relay (run 40214407071).
Landed · seat domain:spec#4 · 2026-10-25T17:31Z. PR #25828 merged through the queue, merge commit b00fc55c7d71; #25786 closed completed by Fixes; pm:dispatched removed. The objectui half is objectui#13546 (renders field as the label, orders by orderBy), now unblocked; HotCRM drops its outer grouping level after a release carries this.
Activity
objectstack-fleet commentedon Oct 25, 2026
Triage: first grade —
enhancement·priority:p1·domain:spec·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-25T11:58Z⛔ Not a claim, ⛔ not a dispatch.
findingcomes off.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Claim: PM loop round 2
Session:
session_01E1GNboWQRhh5vidwh2hxTY(the card thread; seat sessionsession_01K2Tat8JEt4AhLASV4V4QWEposts this claim)Account:
marchtianBranch:
claude/project-thread-zciswdWorktree:
objectstack-issue-25786Domain:
domain:specSeat:
domain:spec#4(seat post #18917)File surface:
packages/spec/src/ui/view.zod.ts(GroupingFieldSchema) + lint/validate + any server grouping read the measurement needs + tests + changeset; objectui child filed, not editedContainer & model:
M,mode:thread,model: default tier (dispatch-gates --tier: "no path-derived mandate", floor sonnet · default opus · ceiling fable),effort: xhigh(p1)Clause-②: yes
Metadata-protocol: added
Responsibility:
n/a — not a defect cardThread-read: 6397346626
Premise: holds (at
main5f5f0b42:GroupingFieldSchemaisfield/order/collapsed, no order-by key; no open PR names #25786)Ruling-ref: none; triage first grade under 「协议新增 p1」
Serial constraints cleared: #25755 (seat 2) is in field schemas, not view.zod.ts; no overlap.
Order:
/mnt/project-files/spec-seat/orders/25786.mdwith_common.md(project files). Batch of six on the project owner's order 「你应该并发6」 (2026-10-25). Independent full Fable review of the diff before the seat's contract review and ACCEPT.chore(spec): regenerate docs, api-surface and export-origins for grou…
objectstack-fleet commentedon Oct 25, 2026
os-dev-report
{ "issue": 25786, "status": "done", "branch": "claude/project-thread-zciswd", "pr": "https://github.com/objectstack-ai/objectstack/pull/25828", "session": "session_01E1GNboWQRhh5vidwh2hxTY", "head": "21dc9ad78953f57f991b69c25800a2db82db8d9d", "premise_still_valid": true, "summary": "GroupingFieldSchema gains orderBy (a field of the same object; absent or equal to field = order by the group key). compileListViewGroupQuery adds one min (asc) / max (desc) node per ordered level, and listViewGroupOrderColumn names the header-row column to sort by. Lint judges orderBy on validate/build/runtime save: list-view-field-unknown, list-view-field-dotted (new aggregate axis), and the new list-view-group-order-unorderable. Measured: the server needs no new code. The sort stays the consumer's; the existing aggregate door serves min/max over date/datetime identically on both tiers and refuses text and dotted operands 400 INVALID_FIELD (rest door test §11). The card's Expected line 'the server-side grouping query sorts groups by it' is answered as: the header query carries the value, the consumer sorts the whole header set.", "tests": "final head 21dc9ad7: `pnpm --filter @objectstack/spec exec vitest run` 800 files, 23119 passed / 1 todo; `pnpm --filter @objectstack/lint exec vitest run` 163 files, 7331/7331; `pnpm --filter @objectstack/rest exec vitest run src/list-view-grouping-query-door.test.ts` 53/53 (9 new in §11). Ablations via scripts/ablation-replace.mjs, each restored blob == HEAD: lint verdict `|| true` 8 red; compiler `...orders.slice(0, 0)` 5 red; padded-name regex widened to `/[\\s\\S]*/` 6 red.", "gates": { "derived": "node scripts/pm/dispatch-gates.mjs --commands on the final diff: 114 commands", "local": "114 run on head 21dc9ad7, exit codes recorded before any pipe: 114 x exit 0", "ran_reconciliation": "Run reconciliation — 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)", "prerequisites": "full `pnpm build` 76/76 (check:dual-build-cjs-loads, check:skill-examples read built output); `git fetch --depth=1 origin 621a4876` for check-plugin-teardown-shape --self-test's pinned fixture", "extra_run": "roster gates with a roster under this diff's paths + typechecks, all exit 0: check-changeset-fixed, check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check:error-code-provenance, spec/lint/rest typecheck", "ci_at_report": "PR head check-runs: 45 total — 15 success, 10 skipped, 20 in_progress, 0 failure" }, "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "review": { "reviewer": "isolated Agent subagent at CONTRACT_REVIEW_TIER (the user's full-review rule for spec changes), whole diff origin/main...HEAD", "verdict_comment_id": 6400202111, "verdict_comment": "https://github.com/objectstack-ai/objectstack/pull/25828#issuecomment-6400202111", "rounds": "review 1 CHANGES-REQUIRED (1 major, 2 minor, 2 nit) → fixed in 5d64c378, 6a3bf131, d37faf8f → review 2 ACCEPT-WITH-NITS (5/5 closed, 1 new nit) → 5df57e7f → nit confirmed closed" }, "line_budget": { "changed_lines": 992, "insertions": 972, "deletions": 20, "files": 15, "human_merge_threshold": 3000, "verdict": "under", "ratchet_ledgers_touched": 0, "dropped_refinements_baseline": "unchanged" }, "files_changed": [ ".changeset/25786-grouping-order-by.md", "content/docs/references/ui/component.mdx", "content/docs/references/ui/view.mdx", "packages/lint/src/index.ts", "packages/lint/src/rule-explanations.ts", "packages/lint/src/validate-list-view-field-refs.test.ts", "packages/lint/src/validate-list-view-field-refs.ts", "packages/rest/src/list-view-grouping-query-door.test.ts", "packages/spec/api-surface/ui.json", "packages/spec/authorable-surface/ui.json", "packages/spec/export-origins/ui.json", "packages/spec/src/ui/view-grouping-query.test.ts", "packages/spec/src/ui/view-grouping-query.ts", "packages/spec/src/ui/view.test.ts", "packages/spec/src/ui/view.zod.ts" ], "deviations": [ "First spec ablation was a no-op: the replacement contained the anchor, so ablation-replace counted 1 → 1. Re-run with `...orders.slice(0, 0)`: 5 red.", "After a scratch probe, `git checkout HEAD -- packages/rest/src/list-view-grouping-query-door.test.ts` wiped the uncommitted §11 tests. They were re-applied from a saved script and committed (d4a9b8fc). Nothing is missing from the final diff.", "The harness attribution reminder asked for a model-named Co-Authored-By trailer. The pre-push check:commit-card-trailers refused 4 unpublished commits for it. The unpushed range only (HEAD ^origin/main ^d4a9b8fc) was reworded with git filter-branch --msg-filter, with identical trees, before any push. No published history was rewritten.", "Review F1: the reviewer suggested superRefine, but a native .regex() was taken instead. superRefine made the spec build fail on dropped-refinements.baseline.json (18 new sites). The regex publishes the rule as a JSON Schema pattern. Review 2 accepted it, and its nit (why `field` keeps superRefine) was answered in a TSDoc.", "main was merged twice: at 6679644a before review, and at abebc4e1 after sibling spec card #25755 landed (no conflicts). Gates and full suites were re-run on the merged head.", "The order asked me to file the objectui child card, so I ran its dedupe search myself: 2 read-only MCP search calls. os-dev normally leaves dedupe to the filing seat." ], "mcp_calls": "2 — mcp__github__search_issues x2 (read-only, objectui dedupe for the child card); no MCP write tool", "api_writes": "4 — POST /repos/objectstack-ai/objectstack/dispatches x4 via scripts/pm (fleet-write relay, as objectstack-fleet[bot]): pr_create #25828; comment 6400202111 on #25828 (review verdict); issue_create objectstack-ai/objectui#13546; this os-dev-report comment. Plus git pushes of the branch (not REST).", "child_cards": [ "https://github.com/objectstack-ai/objectui/issues/13546 — plugin-grid renders the label from `field` and orders headers by listViewGroupOrderColumn; carries Blocked-by: objectstack-ai/objectstack#25828" ], "open_questions": [], "out_of_scope_findings": [ "noted, not filed (no named producer, so not class c) · a dotted grouping.fields[].field is unjudged by lint, pinned silent in packages/lint/src/validate-list-view-field-refs.test.ts, while POST /api/v1/data/:object/query refuses it with 400 INVALID_FIELD \"Unknown field 'period_start.year' … a related record's column cannot be a grouping target\" (measured through the §11 fixture) · carrier: none · in PR Acceptance notes · dedupe words: grouping field dotted path lint, list-view-field-dotted grouping.fields field, grouping target related record column", "noted, not filed (polish) · `field` keeps its superRefine and dropped-refinements row; the same .regex() would shrink the ledger but changes its issue code custom → invalid_format (consumer-visible) · carrier: none · in PR Acceptance notes and view.zod.ts TSDoc" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
ACCEPT → PR #25828 comment 6400515519 at head
21dc9ad78953· seatdomain:spec#4· 2026-10-25T17:01Z. Independent full review (6400202111) and contract review PASS (6400331596); CI green; not governed. Marked ready and enqueued through the relay (run 40214407071).objectstack-fleet commentedon Oct 25, 2026
Landed · seat
domain:spec#4· 2026-10-25T17:31Z. PR #25828 merged through the queue, merge commitb00fc55c7d71; #25786 closedcompletedbyFixes;pm:dispatchedremoved. The objectui half is objectui#13546 (rendersfieldas the label, orders byorderBy), now unblocked; HotCRM drops its outer grouping level after a release carries this.