feat(grid): compute all eleven spec column summary aggregations (#2890) - #2897
Merged
Conversation
`ColumnSummarySchema` accepts eleven aggregation names; `useColumnSummary`
computed five. The other six — `none`, `count_empty`, `count_filled`,
`count_unique`, `percent_empty`, `percent_filled` — passed validation at
authoring time and then rendered a blank footer cell, with no error raised on
either side. objectstack#3761 widened the reachable surface further by
promoting the `{ type, field }` object form, whose `type` reuses this enum.
Implement the six, splitting the computation into two families:
- count/percent read *raw* cell values, before the numeric parse, so they work
on text, select and lookup columns and a value that does not parse as a
number still counts as a filled row. A cell is empty when it is null,
undefined, "" or an empty array — the convention already used by audit
history display and form dirty-checking — so an unset multi-select reads as
empty rather than as a filled `[]`. `count_unique` keys objects and arrays by
value; a raw Set compares by reference and would call every row distinct.
- sum/avg/min/max keep the existing numeric parse and column formatting.
Two behavior changes fall out of the enum having both names:
- `count` is now every row. `count_filled` is the non-empty variant, and with
both in the enum they cannot mean the same thing. Only a column whose values
are all empty renders differently than before.
- a zero count renders "Empty: 0" instead of collapsing to a blank cell. Zero
is the answer to "how many are empty", not the absence of one.
Column currency/percent formatting is now gated to the numeric family in one
place rather than scattered `type !== 'count'` guards, so `count_unique` on a
currency column reads "Unique: 3" and not "$3.00".
`none` and unrecognized names skip the entry entirely instead of registering a
blank one, so a view whose columns all opt out renders no footer row.
Widen the objectui-local `{ type, field }` arm of `ListColumnSchema` to take
its vocabulary from `SpecColumnSummarySchema` by reference. It was stuck at the
same five names, which left the per-column `field` override unavailable for the
six new aggregations, and it collapses to zero delta when that spec release
lands and the `.extend()` goes away.
Guard both directions with a parity test: a spec name the renderer omits is the
bug this commit fixes, and a renderer name the spec omits would be local
dialect (Commandment #0).
The footer summary was undocumented in both the package README and the plugin
docs page; both now carry the full table and the emptiness rule.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3EDGLKNvbcpDTLzyiAd3Q
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
`@object-ui/react` exported a hook of the same name as the grid's, with no callers anywhere in the repo. It was not a duplicate implementation so much as a competing one: - a different API — `(data, config, locale)` against the grid's `(columns, data, fieldMetadata)` — and a colliding `ColumnSummaryConfig` export of a different shape; - a doc comment claiming it implements "ColumnSummarySchema from @objectstack/spec v2.0.7", six majors behind; - a `distinct` aggregation that is not in the spec vocabulary at all. The spec calls that `count_unique`. That last point is what makes it worth removing rather than leaving inert: a publicly exported name the spec does not define is the second de-facto contract Commandment #0.1 warns about, and the parity guard added alongside the grid hook does not reach it. Callers wanting footer aggregations use `useColumnSummary` from `@object-ui/plugin-grid`, which now implements the spec enum in full. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3EDGLKNvbcpDTLzyiAd3Q
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 28, 2026 04:24
This was referenced Jul 28, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 28, 2026
Audits every named enum export in `@objectstack/spec@16.0.0-rc.0` `ui/` against its renderer counterpart, in both directions, plus field types and filter operators. 24 confirmed Direction A gaps that produce a user-visible defect, 11 inert ones, 10 surfaces carrying renderer-local dialect. Corrects the issue's premise: `ChartTypeSchema` is 19 values in the spec, not 7. The 7-value list is an objectui fork re-exported under the spec's own symbol name — one of five such forks. Triaged by runtime symptom rather than by direction, because that orders the work by how long a defect survives undetected: silently-wrong output (6) is worse than the silently-absent case (15) that prompted #2897, and the loud red-box tier (3) is cheapest precisely because it cannot hide. Refs #2901, #2897, #2231, #2731 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
…silently wrong (#2941) (#2993) Every row here validated at authoring time and produced output that looked correct and wasn't (#2901 Tier 1 — worse than a blank cell, because nobody asks why a plausible number is wrong): - PivotTable: an out-of-vocabulary aggregation (`count_distinct`, `array_agg`, `string_agg` — engine names with no client renderer) returned a SUM through the `default:` branch. It now refuses loudly with a visible notice; the implemented set is pinned to the spec's 5-name `ChartAggregateFunctionSchema`. - Report chart: 10 of 19 `ChartTypeSchema` values silently drew a bar. `planReportChart` now classifies all 19 — the 12 series families reach the generic chart verbatim (`horizontal-bar` stays horizontal), the 5 single-value families render the measure as a server-aggregated number (dimensionless dataset query — no client math), `table`/`pivot` add no duplicate chart, and out-of-spec values get a visible notice. - `selection.type: 'single'`: `selectable` was a bare truthy, so single rendered the full multi-select UX. The data-table now enforces replace-on-select with no select-all header, and ObjectGrid stops offering the cross-page "select all N matching" escalation in single mode. - Filter `type: 'select'`: the spec names both `select` and `multi-select`, so `select` is single-choice. It now renders radios and replaces the pick (badge × clears); restored/default multi-value selections clamp to one. Inferred (omitted) types keep the historical multi-check UX. - `addRecord.position: 'both'` collapsed to `top` through a binary ternary; both buttons now render. - `tabular` vs `summary` was resolved from whether `rows` was non-empty; the DECLARED type now picks the branch: summary (and degraded matrix) carries a server-computed grand-total footer (`totals: { groupings: [[]] }`, ADR-0021 red line respected), tabular is the same selection as a plain list. Each fix lands with a spec-parity guard per the #2897 template (`summary-spec-parity.test.ts`) plus behavior tests; `components`, `plugin-dashboard` and `plugin-report` gain the `@objectstack/spec` devDependency that makes those guards possible (`components` also pins `zod@^4` so the spec resolves to the same store instance as the rest of the repo instead of forking a second `zod@3` peer variant via shadcn's MCP SDK). Refs #2941, #2901 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
Jul 30, 2026
… into nothing (#2942) Every row below is the #2897 shape — validates at authoring time, renders nothing (or a dead control), no test fails and no warning fires: - UserFilters `element: 'toggle'`: `default: return null` deleted the ENTIRE filter bar for stored toggle configs. The existing-but-unreachable ToggleFilters branch is wired; authoring stays untypeable (ADR-0053) while stored metadata keeps rendering (spec ADR-0047 §3.4a). - UserFilters `date-range` / `text`: dead "No options" popovers become a from/to date pair (emits >=/<= bounds) and a contains search input. - useAnimation: preset/easing maps re-keyed to the spec's underscore vocabulary (+ rotate/flip via usePageTransition's classes); hyphen dialect and `scale-fade` stay accepted for stored configs. The `EASING_MAP[easing] || easing` fallthrough no longer emits invalid CSS. - NotificationContext: displayType materialized (spec default toast, legacy `modal` presents as alert) and the unions now match NotificationTypeSchema / NotificationPositionSchema instead of claiming to. - useNavigationOverlay: the spec `size` buckets resolve to viewport-clamped widths off app-shell too (explicit `width` still wins; `auto` stays host-derived). - Both ThemeProviders: `mode: 'auto'` follows the OS instead of adding a dead `auto` class that locked the light theme; `system` kept as the pre-spec spelling. - AdvancedChart: the single-value families (gauge/solid-gauge/metric/kpi/ bullet) render the measure as a number, table/pivot name their owning component, unknown types are named — never the bar SHELL with null series marks that was indistinguishable from an empty dataset (reachable via ChartRenderer's `schema.chartType ?? spec.chartType` bypass). - Timeline: the spec `scale` key is read at last (legacy `timeScale` kept); hour/quarter/year generate real gantt header buckets instead of a blank axis. - Toaster: position (all six spec values) and `limit` reach sonner instead of being discarded by a bare `<SonnerToaster />`. - useSpecGesture: the DECLARED `config.type` drives recognition — pan/drag/rotate/double_tap no longer collapse to tap; useGesture gains real double-tap (two taps, not one) and two-touch pinch/rotate deltas. - ReportViewer: `aggregation: 'distinct'` computes a distinct count instead of a blank summary cell. - FieldEditWidget: inline resolution goes through the form's alias table, so `json` gets the code editor, `tree` the lookup picker, and composite/ record/repeater/video/audio/autonumber resolve to their documented exclusions; ObjectGrid's editability gate consults the same contract, so a `composite` cell is read-only instead of a value-corrupting text box. - FilterBuilder: $startsWith/$endsWith/$null/$exists become authorable (startsWith/endsWith/isNull/isNotNull/exists/notExists) and round-trip through condToMongo/kvToCondition — every FieldOperatorsSchema token is now reachable from the UI. The export-menu row (PDF silently downloading nothing) was fixed concurrently by #2999, which this branch rebases onto and defers to. Each fix lands with a spec-parity guard per the #2897 template; `fields`, `mobile`, `plugin-charts` and `providers` gain the `@objectstack/spec` devDependency that makes those guards possible (no second zod peer variant materialized — verified in the lockfile). Refs #2942, #2901 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
… into nothing (#2942) (#3008) Every row below is the #2897 shape — validates at authoring time, renders nothing (or a dead control), no test fails and no warning fires: - UserFilters `element: 'toggle'`: `default: return null` deleted the ENTIRE filter bar for stored toggle configs. The existing-but-unreachable ToggleFilters branch is wired; authoring stays untypeable (ADR-0053) while stored metadata keeps rendering (spec ADR-0047 §3.4a). - UserFilters `date-range` / `text`: dead "No options" popovers become a from/to date pair (emits >=/<= bounds) and a contains search input. - useAnimation: preset/easing maps re-keyed to the spec's underscore vocabulary (+ rotate/flip via usePageTransition's classes); hyphen dialect and `scale-fade` stay accepted for stored configs. The `EASING_MAP[easing] || easing` fallthrough no longer emits invalid CSS. - NotificationContext: displayType materialized (spec default toast, legacy `modal` presents as alert) and the unions now match NotificationTypeSchema / NotificationPositionSchema instead of claiming to. - useNavigationOverlay: the spec `size` buckets resolve to viewport-clamped widths off app-shell too (explicit `width` still wins; `auto` stays host-derived). - Both ThemeProviders: `mode: 'auto'` follows the OS instead of adding a dead `auto` class that locked the light theme; `system` kept as the pre-spec spelling. - AdvancedChart: the single-value families (gauge/solid-gauge/metric/kpi/ bullet) render the measure as a number, table/pivot name their owning component, unknown types are named — never the bar SHELL with null series marks that was indistinguishable from an empty dataset (reachable via ChartRenderer's `schema.chartType ?? spec.chartType` bypass). - Timeline: the spec `scale` key is read at last (legacy `timeScale` kept); hour/quarter/year generate real gantt header buckets instead of a blank axis. - Toaster: position (all six spec values) and `limit` reach sonner instead of being discarded by a bare `<SonnerToaster />`. - useSpecGesture: the DECLARED `config.type` drives recognition — pan/drag/rotate/double_tap no longer collapse to tap; useGesture gains real double-tap (two taps, not one) and two-touch pinch/rotate deltas. - ReportViewer: `aggregation: 'distinct'` computes a distinct count instead of a blank summary cell. - FieldEditWidget: inline resolution goes through the form's alias table, so `json` gets the code editor, `tree` the lookup picker, and composite/ record/repeater/video/audio/autonumber resolve to their documented exclusions; ObjectGrid's editability gate consults the same contract, so a `composite` cell is read-only instead of a value-corrupting text box. - FilterBuilder: $startsWith/$endsWith/$null/$exists become authorable (startsWith/endsWith/isNull/isNotNull/exists/notExists) and round-trip through condToMongo/kvToCondition — every FieldOperatorsSchema token is now reachable from the UI. The export-menu row (PDF silently downloading nothing) was fixed concurrently by #2999, which this branch rebases onto and defers to. Each fix lands with a spec-parity guard per the #2897 template; `fields`, `mobile`, `plugin-charts` and `providers` gain the `@objectstack/spec` devDependency that makes those guards possible (no second zod peer variant materialized — verified in the lockfile). Refs #2942, #2901 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the
useColumnSummaryitems tracked in #2890.The gap
ColumnSummarySchemaaccepts eleven aggregation names.useColumnSummarycomputed five. The other six —none,count_empty,count_filled,count_unique,percent_empty,percent_filled— passed validation at authoring time and then rendered a blank footer cell, with no error raised on either side.objectstack#3761 widened the reachable surface: it promoted the
{ type, field }object form into the spec, and that form'stypereuses this same enum.What changed
The computation now splits into two families:
count_uniqueworks on text, select and lookup columns, and a value that does not parse as a number still counts as a filled row.A cell is empty when it is
null,undefined,""or an empty array — the convention already used by audit history display and form dirty-checking — so an unset multi-select reads as empty rather than as a filled[].count_uniquekeys objects and arrays by value; a rawSetcompares by reference and would call every row distinct.Two behavior changes fall out of the enum carrying both names
countis now every row.count_filledis the non-empty variant, and with both in the enum they cannot mean the same thing. Existing tests use data whose rows are all filled, so they do not distinguish the two; the only column that renders differently than before is one whose values are all empty.Empty: 0instead of collapsing to a blank cell. Zero is the answer to "how many are empty", not the absence of one — the oldcount > 0 ? count : nullswallowed it.Formatting
Column currency/percent formatting is now gated to the numeric family in one place, rather than the scattered
type !== 'count'guards. Without this,count_uniqueon a currency column would render$3.00instead ofUnique: 3.nonenoneand unrecognized names skip the entry entirely instead of registering a blank one, so a view whose columns all opt out renders no footer row rather than an empty one.Types
The objectui-local
{ type, field }arm ofListColumnSchemawas stuck at the same five names, which left the per-columnfieldoverride unavailable for the six new aggregations. It now takes its vocabulary fromSpecColumnSummarySchemaby reference, so the shorthand and the object form cannot accept different names — and the delta drops to zero when that spec release lands and the.extend()goes away.Removed: the second
useColumnSummary@object-ui/reactexported a hook of the same name with no callers anywhere in the repo. Not a duplicate implementation so much as a competing one:(data, config, locale)against the grid's(columns, data, fieldMetadata)— and a collidingColumnSummaryConfigexport of a different shape;@objectstack/specv2.0.7", six majors behind;distinctaggregation not in the spec vocabulary at all — the spec calls thatcount_unique.That last point is why it was worth removing rather than leaving inert: a publicly exported name the spec does not define is the second de-facto contract Commandment #0.1 warns about, and the parity guard below does not reach it.
Anti-drift guard
New
summary-spec-parity.test.tsasserts the renderer's supported set equals the spec enum in both directions:Both new tests were verified against a deliberately broken implementation rather than assumed to work:
count_uniquecompare by referenceexpected 2 to be 1)Docs
Per Commandment #2. The footer summary was undocumented in both the package README and the plugin docs page; both now carry the full eleven-value table, the emptiness rule, and the formatting boundary.
Verification
type-check(types + plugin-grid)any@object-ui/reactafter the removal@objectstack/specadded as a devDependency of plugin-grid for the parity test — six otherplugin-*packages already depend on it.Noted, not changed
The footer renders
{col.header}: {summary.label}while the label already carries its own prefix, producingAmount: Sum: 600. Pre-existing, and changing it is a design decision rather than a bug fix.