Skip to content

plugin-grid: order a grouped grid's groups by the level's orderBy (spec grouping.fields[].orderBy), the label still from field #25858

Closed

Description

@objectstack-fleet

Filing gate: ④ coordination node. This is the objectui layer child of #25786 (the parent card); the spec key and its lint land in #25828.
Blocked-by: #25828
Reader: the objectui plugin-grid lane, in packages/plugin-grid/src/useGroupedData.ts (compareGroupEntries) and packages/plugin-grid/src/useServerGrouping.ts. The work is the next plugin-grid PR once objectui's @objectstack/spec pin covers #25828.
Dedupe (open AND closed): "grouped grid order groups by another field orderBy grouping header sort" → 6 hits, none this one (the closest is objectstack-ai/objectui#11809, a select grouping field's option order, closed). "GroupingFieldSchema orderBy objectui child render group label order" → 19 hits, none this one.
Business reason: inherited from the parent. HotCRM's forecast list (src/sales/views/forecast.view.ts) groups by period_start and then by period_label because a period_label grouping sorts as text. That prints two header rows for every forecast. The spec key retires the workaround, but only once the grid reads it.

The contract this layer reads (spec, @objectstack/spec/ui)

  • GroupingFieldSchema.orderBy?: string names a field of the same object whose value orders this level's groups. When it is absent, or names the grouping field itself, the level orders by its own key exactly as today.
  • compileListViewGroupQuery now adds one aggregation node per ordered level, min over orderBy for order: 'asc' and max for 'desc'. The node is aliased min_FIELD / max_FIELD. The grouping key stays the only groupBy column, so there is still one header per label.
  • listViewGroupOrderColumn(grouping, level) names the header-row column a level sorts by: the grouping field itself, or that node's alias. Read it from there instead of rebuilding the alias.
  • What the consumer owes, as stated on the key:
    • a group whose order value is null sorts last in both directions;
    • equal order values fall back to the group key, compared in the same direction.
  • The header set is sorted whole. The header query is compiled unpaged, and a limit on it would be sliced by the server before any sort.

What this card does

  1. Server-grouped grid. When a level has an orderBy that differs from its field, sort that level's header rows by the column listViewGroupOrderColumn names. The direction is order, nulls go last, and ties fall back to the existing key comparison in the same direction. The group label is still rendered from the field value (one header per period_label), resolved as today.
  2. Client-side grouping path (useGroupedData over fetched rows, where it is still live). Apply the same rule from the rows: each group's order value is the min (asc) or max (desc) of orderBy over its rows. Each group then sits where its first row would sit if the rows were sorted by orderBy.
  3. Precedence. A level with orderBy orders by it, ahead of the declared option order (Both worktree-first guards treat ANY git-dir path containing /worktrees/ as a linked worktree — a primary checkout under ~/worktrees/ is unguarded from subdirectories #11809) and the label order. A level without one is unchanged.
  4. Type mirror. If objectui mirrors GroupingField, it gains orderBy?: string.

Acceptance

  • Take a list grouped by period_label with orderBy: 'period_start', order: 'desc', over rows whose labels sort differently as text (Q4 2025, Sep 2026, Oct 2026, Q4 2026, Nov 2026, Q1 2027, plus one with no period_start). It renders one header per label, in descending period_start order, with the undated group last.
  • A test feeds header rows shaped as compileListViewGroupQuery compiles them and asserts the order. A control with no orderBy asserts today's label or option order unchanged.
  • ⛔ Not this card: HotCRM's view edit. Its follow-up is to drop the outer period_start level in forecast.view.ts once this lands.

Activity

  1. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — enhancement · priority:p2 · domain:ui · area:records · pm:blocked · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-25T16:56Z

    ⛔ Not a claim, ⛔ not a dispatch.

    Blocked-by: #25786


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor
  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Transferred 2026-10-26T01:52Z from objectstack-ai/objectui#13546, with its comments and timeline; the old URL redirects here. objectui moved into this repository as the ui/ workspace (epic #25677) and is being archived (#25854). The body is left exactly as filed.

    Paths. Read every repository path in the body with a ui/ prefix: packages/plugin-grid/src/useGroupedData.ts is now ui/packages/plugin-grid/src/useGroupedData.ts; packages/plugin-grid/src/useServerGrouping.ts is now ui/packages/plugin-grid/src/useServerGrouping.ts.

    References. The bare issue numbers in the body were written in objectui and now resolve against this repository by mistake; read them as objectstack-ai/objectui#11809.

    No stale pm:dispatched or assignee to strip.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: unlocked → pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T01:56Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_017iXjUm1rujwDk7f3KT1Pkk (seat); implementing card thread session_01Baqv5mDLFH63hvh9G4YGn5
    Account: os-steve
    Branch: claude/project-thread-6wnpcu (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record. Wording amended by the seat: an issue-shaped placeholder here was read by the Closing-Target Claim Guard as a second claimed branch)
    Worktree: the card thread's own container
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: ui/packages/plugin-grid/src/useGroupedData.ts (SortableGroup, compareGroupEntries and both sort call sites); if the header column is not forwarded, ui/packages/plugin-grid/src/useServerGrouping.ts / ObjectGrid.tsx at the header-row read; tests beside them (stop on breach; explain in the report). ⛔ ui/.github/** and the root .github/workflows/ci.yml are out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root .changeset/.
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no (reads the landed grouping.fields[].orderBy key; no spec change)
    Metadata-protocol: none
    Responsibility: n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public Console
    Thread-read: 6406521677
    Premise: holds (read-only premise check at origin/main 59d9181, 2026-10-26T06:38Z; spot-checked by the seat)
    Serial constraints cleared: none: no open PR touches ui/packages/plugin-grid/** (open-PR file scan 2026-10-26T06:33Z); no in-flight domain:ui claim; the spec half (#25828) is merged

    Dispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25858,
      "status": "done",
      "branch": "claude/project-thread-6wnpcu",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25977",
      "head": "19f3f573d2036c5c9c0353317a46095a1f5ab25f",
      "session": "session_01Baqv5mDLFH63hvh9G4YGn5",
      "premise_still_valid": true,
      "summary": "Measured at origin/main: orderBy was dropped in three places, ObjectGrid serverGroupingKey (rebuilt entries as field/order/collapsed), useServerGrouping groupingOf (header query compiled from names only, so no min_/max_ node and no order column on header rows), and useGroupedData compareGroupEntries (no orderBy arm). All three fixed: ObjectGrid forwards orderBy (behind the same groupingKeyReadable gate as the key), the header query compiles each level with orderBy+order, the server path reads the level's order value off listViewGroupOrderColumn(grouping, depth), the client path folds min (asc) / max (desc) of orderBy over each group's rows; nulls last both directions, ties fall back to the existing option/label comparison in the same direction. Card item 4: no mirror exists (UsableGroupingField derives from the spec type; zod faces take GroupingConfigSchema by reference).",
      "tests": "code tree b3b58b69 (final head 19f3f573 adds only scripts/sdui-manifest.record.json): groupOrderBy-25858.test.tsx Test Files 1 passed, Tests 18 passed; pnpm exec vitest run packages/plugin-grid/ (from ui/) Test Files 248 passed (248), Tests 2222 passed (2222), lock VERDICT command-exit 0; pnpm --filter @object-ui/plugin-grid run type-check (tsc --noEmit and tsc -p tsconfig.test.json) VERDICT command-exit 0. Ablations via scripts/ablation-replace.mjs, each anchor hit 1 and restored blob == HEAD with git diff HEAD empty: order-value arm of compareGroupEntries off -> 10 failed / 8 passed; numeric-string reading off -> 1 failed; headerLevelOf forwarding no orderBy -> 1 failed (ObjectGrid server test); ObjectGrid forwarding no orderBy -> 1 failed (same test). Tests import ../useGroupedData source, so no dist leg applies.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 19f3f573: 47 families, all run, all exit 0; --ran with per-command exit codes: 47 run, 0 NOT-MEASURED (a DERIVED zero). check-sdui-manifest exit 1 before regenerating (registry source hash moved), exit 0 after node scripts/gen-sdui-manifest-node.mjs (sdui.manifest.json byte-identical, record file re-recorded). pnpm check:type-check-debt exit 0 (re-measure 26 raw errors, none above record). 35 of 47 ran at 02167adb (pre-sdui-record tree, same ui code), the 12 added by the record file ran at 19f3f573. CI families: in_progress, not awaited per contract.",
      "dogfood_battery": "n/a — no ! in the PR title and Clause-②: no without (narrowing)",
      "mcp_calls": "4 — mcp__github__issue_read x4 (get, get_comments x3); no MCP write tool used",
      "api_writes": "2 — relay stroke fw-20261026T073604Z-98642f (pr_create: POST /repos/objectstack-ai/objectstack/pulls draft + POST /repos/objectstack-ai/objectstack/issues/25977/assignees os-steve, read-back identical 6242 bytes); this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/25858/comments). git push is not counted.",
      "deviations": [
        "files_changed outside the claim's file surface: scripts/sdui-manifest.record.json (generator-owned record; check-sdui-manifest hashes ui/packages registry sources, so any plugin-grid src edit moves it) and .changeset/25858-grid-group-order-by.md (named by the dispatch).",
        "No reviewer request on the PR: the session context asks for update_pull_request reviewers os-steve, the dispatch forbids MCP writes; the seat may request it. PR activity not subscribed, per the dispatch contract."
      ],
      "files_changed": [
        ".changeset/25858-grid-group-order-by.md",
        "scripts/sdui-manifest.record.json",
        "ui/packages/plugin-grid/src/ObjectGrid.tsx",
        "ui/packages/plugin-grid/src/__tests__/groupOrderBy-25858.test.tsx",
        "ui/packages/plugin-grid/src/useGroupedData.ts",
        "ui/packages/plugin-grid/src/useServerGrouping.ts"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: HotCRM forecast.view.ts follow-up (drop the outer period_start level) — named by the card as not this card; noted, not filed"
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat: PR #25977 ACCEPT (2026-10-26T08:04Z), checks green on head 19f3f573; ready + auto-merge requested. See the ## 席位核对 comment on the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uienhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions