Skip to content

plugin-report docs still teach the pre-9.0 query form as the live authoring shape — and name three renderers removed at the ADR-0021 cutover #5047

Description

@yinlianghui

Found while implementing #5016 (the three fabricated exports in packages/plugin-report/README.md). Filed unassigned, out of that card's scope: #5016 is scoped to the fabricated export names; this is the schema shape the surrounding prose teaches, which is a larger and separately-decidable surface. Measured on origin/main at dc9d651a2c55c10a9e4d30434e429e5fdc5ae968.

What the code says

packages/plugin-report/src/ReportRenderer.tsx:6-20 states the single form and the retirement in as many words:

 * ADR-0021 single-form: a 9.0 report is **dataset-bound** — it binds a
 * semantic-layer `dataset` and selects its `values` (measure names) grouped
 * by `rows` (dimension names). That is the live rendering path.
 ...
 * The pre-9.0 query-form renderers (SpecReportGrid / MatrixRenderer /
 * JoinedReportRenderer) were retired with the cutover — stored old-shape JSON
 * renders through the lossy {@link specReportToPresentation} bridge until it
 * is migrated to a dataset binding.

packages/plugin-report/CHANGELOG.md:1284 records the same removal, and packages/plugin-report/src/index.tsx:33-37 records that useReportData() and its buildAggregateQuery / groupAndAggregate / pivotRows helpers were removed with ADR-0021 because dataset-bound reports aggregate in the semantic layer.

What the docs still teach

1. content/docs/plugins/plugin-report.mdx — names the retired renderers as the live ones. Its "At a glance" table maps each variant to a renderer:

line claim
:22-25 tabular / summary renderer is SpecReportGrid; matrix is MatrixRenderer; joined is JoinedReportRenderer
:79-83 dateGranularity routes to POST /api/v1/data/:object/query, falling back to applyInMemoryAggregation()
:85-86 "MatrixRenderer renders row totals, column totals and grand total automatically"

All three renderer names and applyInMemoryAggregation are zero-hit in packages/plugin-report/src. Every authoring example on the page (:36-50, :60-72, :92-120) is in the objectName + columns + groupingsDown / groupingsAcross query form.

2. packages/plugin-report/README.md — the same query form, in the sections #5016 does not touch. After PR-for-#5016 lands, the remaining stale surface is:

line (at that PR's head) claim
:9 feature bullet: dateGranularity on groupingsAcross / groupingsDown
:10 feature bullet: "tabular/summary delegate to ObjectGrid"
:12 feature bullet: each joined block owns its own objectName
:83-146 the whole "Spec Reports — the four variants" section: ReportInput examples in the query form, plus :145 "each block runs an isolated useReportData() call"

The dataset-bound shape those should be describing is { name, type, dataset, rows: string[], columns?: string[], values: string[], filter?, order?, drilldown?, chart?, blocks? } — see DatasetReportLike at packages/plugin-report/src/DatasetReportRenderer.tsx:128-158.

Why this is not "just stale prose"

The query form is not rejected, it is silently degraded: ReportRenderer routes a stored isSpecReport document through specReportToPresentation, which its own comment calls "lossy by construction". So an author following either doc gets a report that renders — differently from what the page promises, with no diagnostic. That is the same failure shape as #4600 (the 9 dashboard examples teaching the pre-ADR-0021 widget shape), one corpus over.

Both files are also AI-authoring inputs: the mdx is the published authoring reference, and package READMEs ship to npm.

Open question for triage, not for an implementer

Whether these documents should be migrated to the dataset form or should keep a clearly-labelled "stored pre-9.0 documents" section describing the bridge. That depends on whether the query form is a supported compat arm or a migration waiting room — ReportRenderer.tsx:102-105 says "the proper fix is migrating the stored report to a dataset binding", which reads like the latter, but the decision belongs to whoever owns ADR-0021's migration story. Same enforce-or-retire question #4600 raised for the widget shape.

Recheck

grep -rn "SpecReportGrid\|MatrixRenderer\|JoinedReportRenderer\|applyInMemoryAggregation" packages/plugin-report/src   # zero hits
grep -n "SpecReportGrid\|MatrixRenderer\|JoinedReportRenderer" content/docs/plugins/plugin-report.mdx
grep -rn "ReportViewer" packages/plugin-report/src | head   # positive control: a name that IS real

Related: #5016 (the export-name half, in flight), #4600 (same class, dashboard examples), #5002 / PR #5021 (the plugin-map precedent for a README rewritten against its code).

Activity

  1. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded — maintainer, 2026-08-17, batch acceptance of the PM decision-inbox recommendations, verbatim: 「同意」. Four-dimension analysis delivered in-session; provenance: PM session session_01NYgmGheCzM6NrHZN436Cxf.

    Ruled: migrate. The query form is a migration waiting room, not a supported compat arm (the code's own words: lossy bridge, "proper fix is migrating the stored report"). Both documents move to the dataset-bound form as the sole authoring shape (DatasetReportLike), plus one clearly-labelled short section on stored pre-9.0 documents rendering through the lossy bridge — describing the bridge's existence and its migration direction, never teaching the old shape as authorable. Retired renderer names and applyInMemoryAggregation go. Docs-only; coordinate with #5016's in-flight PR so the two do not collide on README lines.

    Label needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  2. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    Contributor

    Claimed — PM dispatch.

    • Session: session_01NYgmGheCzM6NrHZN436Cxf
    • Branch: claude/issue-5047-plugin-report-docs-migrate
    • Container & model: claude-opus-5. No path-derived tier mandate (docs prose under packages/plugin-report/), and clause ② is not reached — this teaches a shape the code already implements; it changes no accept/reject behaviour and widens no public surface. Recorded here rather than recalled.

    Dispatching the ruling as recorded above: migrate. The dataset-bound form (DatasetReportLike) becomes the sole authoring shape in both documents; the pre-9.0 query form survives only as one clearly-labelled short section describing the lossy bridge that renders stored documents and the direction of migration — never as something a reader could mistake for authorable. Retired renderer names and applyInMemoryAggregation go.

    ⚠️ Collision constraint carried into the dispatch: #5016 has an in-flight PR touching the same README. The dev seat re-checks that PR's current diff before editing and reports any overlapping lines rather than resolving them unilaterally.


    Generated by Claude Code

  3. self-assigned this
    on Aug 18, 2026
  4. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    Contributor
    {
      "issue": 5047,
      "status": "done",
      "branch": "claude/issue-5047-plugin-report-docs-migrate",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5140",
      "premise_still_valid": true,
      "summary": "Ruling implemented as MIGRATE. Both documents now teach the dataset-bound form as the sole authoring shape via defineReport, with one clearly labelled migration-only section on stored pre-9.0 documents. Card verification: all four retired names (SpecReportGrid / MatrixRenderer / JoinedReportRenderer / applyInMemoryAggregation) confirmed dead from code, no discrepancy with the card's list; applyInMemoryAggregation has zero hits repo-wide including retirement comments. Two dead names the card did NOT list were found inside the same two documents: ReportInput (not on @objectstack/spec 17.0.0's export surface at all - both docs annotated every typed example with it) and registerDrillHandler (a fabricated export still live in the mdx; #5053 fixed only the README half). Sharpened the card's 'silently degraded' claim by measurement: specReportToPresentation reads the CURRENT keys, so a stored pre-9.0 document converts to title + reportType + fields:[] - it renders with every column and grouping dropped and no diagnostic. Also corrected two independently false README claims found while re-deriving: 'tabular/summary delegate to ObjectGrid' (ObjectGrid is zero-hit in this package) and joined blocks owning their own objectName (they own their own dataset).",
      "tests": "Union run at fc93cc7 (final commit; git rev-parse --short HEAD). Gate family derived by reading each gate's own scan set, not recalled. node scripts/check-doc-links.mjs exit=0 ('Links are valid across 13 scan roots.'); node scripts/check-doc-component-types.mjs exit=0 ('481 registered, 88 exempted'); node scripts/check-control-bytes.mjs exit=0 ('scanned 4562 tracked text file(s)'); node scripts/check-changeset-no-major.mjs exit=0; node scripts/check-changeset-presence.mjs exit=0; vitest scripts/__tests__/doc-version-claims.test.ts + check-changeset-presence.test.ts exit=0 (49 passed). Two ratchets verified rather than assumed: doc-version-claims KNOWN_CLAIMS keeps its packages/plugin-report/README.md peer row (Peer Dependencies block untouched), and DOC_TYPE_EXEMPTIONS' matrix/joined entries stay live (exempted count unchanged at 88) - a stale exemption is a hard failure. Heavy phases (types build, plugin-report build, vitest) all ran under flock -E 99 -w on /tmp/os-heavy-verify.lock. SNIPPET PROBE: all 20 ts/tsx blocks in both docs extracted BY SCRIPT (not hand-copied) and compiled --strict against the BUILT dist/index.d.ts; forward leg rc=0, zero diagnostics, 20/20. Resolution self-check confirms it read dist/*.d.ts, not source. The probe found two real doc defects, fixed in the docs not the harness: the README's Matrix/Joined/chart examples called defineReport with no import of their own. REVERSE VERIFICATION (direction predicted before running): predicted RED with the diagnostic on the IMPORT (TS2305 on ReportInput) and key-level errors MASKED by the unresolved annotation. Masking prediction confirmed exactly - Leg A on HEAD's old snippets gave TS2305 ReportInput / TS2304 x2 / TS2305 registerDrillHandler and NOT ONE diagnostic on any key. Reporting one honest trap: the FIRST Leg-A run was a false green - parse errors in two prose-fragment files suppressed semantic diagnostics program-wide, and only removing them produced the real reading. A planted sentinel (ThisNameIsDefinitelyNotExported) also returned TS2305, proving the probe was not resolving to any. Leg B (old literal under the real Report type) surfaced the masked key errors: TS2322 on object-shaped columns x2, and isolated, TS2353 on objectName. Control (new dataset-bound literal, same annotation): zero diagnostics. Runtime zod probe agrees: ReportSchema is z.core.$strict and rejects the old form with 'unrecognized_keys: objectName, groupingsDown' + 'columns.0 expected string, received object', and names the filter replacement itself ('Did you mean filter -> runtimeFilter?').",
      "open_questions": [
        {
          "question": "The ruling says retired renderer names go. The mdx has zero mentions. The README keeps ONE sentence naming the three renderers inside the migration-only section, as removed with no replacement export. Keep or drop?",
          "options": [
            "A. Keep - matches the convention #5053 established three paragraphs above it in the same file, which likewise records ReportBuilder / ScheduleConfig / ChartConfig by name as removed so they are not re-added as live",
            "B. Drop - read the ruling literally as zero mentions anywhere"
          ],
          "recommendation": "A, because the adjacent landed paragraph in this exact file already does it and the naming is what stops a future agent re-adding them as live; but this is a one-line revert if the intent was zero mentions."
        }
      ],
      "out_of_scope_findings": [
        "filed as #5136: content/docs/core/report-schema.mdx teaches the same objectName-based column hydration - a third document, same defect class, outside this card's two-document scope; objectName has zero read points in plugin-report/src and the strict schema rejects the key",
        "filed as #5137: DatasetReportRenderer:1246 reads report.filter as a lenient alias for runtimeFilter, a key the strict spec rejects WITH an explicit rename suggestion - contract-first inversion; left unfixed because this card is docs-only and must not change plugin-report behaviour",
        "filed as #5138 (finding): no gate reads a doc snippet's schema KEYS against the spec. This is the gate that would have caught this defect class and it does not exist - all doc gates were GREEN on the stale prose (baseline measured on unmodified origin/main before editing). It is #4823's explicitly-deferred 'second dimension', and #4823 is closed as completed, so the class is currently untracked",
        "filed as #5139 (finding): DOC_TYPE_EXEMPTIONS reasons in scripts/check-doc-component-types.mjs cite ReportInput, the symbol this PR removed as non-existent"
      ]
    }

    #5016 overlap check — outcome: no overlap; the collision constraint had already expired. #5053 (for #5016) merged 2026-08-17T21:06:21Z as squash f331f5a, and #5060 (for #5048) merged 21:51:58Z as 82a9417. Both are ancestors of this branch's base 958d757, so their work is under this PR rather than beside it. Open PRs touching plugin-report: none. Nothing was resolved unilaterally and neither PR was touched — edits were made on top of their landed text, with every section they rewrote left alone.

    Changeset: @object-ui/plugin-report: patch. check-changeset-presence says none is owed (nothing under a package src/); the patch is deliberate, following the precedent set twice in this exact file (#5053, #5060) because the README is in the package's published files. No skip-changeset label exists in this repository and none was created.

    Contract note: the dispatch specified the bare https://claude.ai/code attribution footer; the standing dev contract requires the session-URL form (the bare form is stripped on every later edit). Followed the contract, per its "prompt 与本文件冲突时以本文件为准" clause.


    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentationpm:dispatched

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions