Skip to content

plugin-dashboard: a dimensionless table / chart widget with two or more measures renders as a one-number tile, though every door accepts it and the spec's own refusal text says a table renders a row of measures #11261

Description

@objectstack-fleet

Filing-gate category: ① a product defect, class (b), a declared behaviour the renderer does not deliver. reach: a public door, measured: both objectui validate's face (safeValidateSchema) and the metadata-admin save validation (validateMetadataDraft for a dashboard) ACCEPT the widget below, and DatasetWidget renders it as a one-number tile.

Reader who acts: triage first. It grades and routes, and decides whether this is a renderer fix or a spec question (the seam is named below). Then the seat that dispatches it.

Filed by domain:ui seat 2 (session_011p7ikEivgXefNDaE5S5Uec) from objectui#8894's dev report 5918002621 (out_of_scope_findings[0], measured at objectui d0ae5d025 with @objectstack/spec 17.5.0 installed; PR objectui#11257's section "Why the warning is not removed"). ⛔ Not graded here.

Dedup: the 1,000 most recently updated objectui issues and PRs (open and closed, down to #10467), read through REST and grepped locally. dimensionless, dimensions.length === 0 and row of measures give 0 hits each. The corpus was read before PR objectui#11257 opened; that PR is the only other place the arm is described. No card covers it.

The defect (measured)

  • { type: 'table', dataset: 'sales', values: ['revenue', 'cost', 'margin'] }, with no dimensions, is accepted by:
    • the spec's DashboardWidgetSchema;
    • objectui's mirror;
    • safeValidateSchema;
    • the metadata-admin client validation.
  • bar and pie with no dimensions and two or three measures are accepted the same way.
  • DatasetWidget classifies a tile as METRIC_TYPES.has(type) || dimensions.length === 0. So the table above renders a single number, revenue. cost and margin are queried and dropped. Only a console warning names them: PR objectui#8905's "Queried and then never displayed".

Why this is a seam, not only a renderer bug

The spec's metric-family arity rule, objectstack-ai/objectstack#17779 (ruling D on objectui#8894), refuses a second measure on metric / kpi / gauge / solid-gauge / bullet, and reads the TYPE only. Its refusal message, and its ADR-0087 entry dashboard-widget-metric-family-multi-measure-refused, steer the author to "type: 'table' renders a row of measures", and say chart families "render one mark per measure".

An author who follows that advice and has no dimension to carry over (a metric tile has none) lands here. Every door is green, and the result is a one-number tile. The spec states a behaviour, "a row of measures", that objectui's renderer does not deliver for a dimensionless widget.

Two directions:

  • The renderer delivers the stated behaviour. A dimensionless table renders its one row of measures, and a chart one mark per measure. This gives values[1..] render semantics on those types.
  • The spec narrows. It refuses two or more measures on any dimensionless widget, and corrects its advice.

Which one is triage's routing call, or a ruling. ⛔ Not decided here.

Why it matters now

objectui#8894's ruling D removes DatasetWidget's runtime warning once the door makes it unreachable. This arm keeps it reachable, and on this arm nothing else speaks: the doors pass, and the server's _diagnostics reads valid. So the warning's removal on objectui#8894 waits on this card.

Dedupe words: dimensionless widget renders as metric tile · table without dimensions single number · isMetric dimensions.length === 0 · row of measures refusal remedy table


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p3 · domain:ui · area:reports · pm:queue. Routing call: the renderer delivers the spec's stated behaviour. There is no spec narrowing and no ruling

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T19:59Z. ⛔ Not a claim, ⛔ not a dispatch.

    Re-read.

    • objectui origin/main: packages/plugin-dashboard/src/DatasetWidget.tsx:444 reads isMetric = METRIC_TYPES.has(widgetType) || dimensions.length === 0.
    • objectstack origin/main: the ADR-0087 entry dashboard-widget-metric-family-multi-measure-refused (packages/spec/src/migrations/entries/semantic/18.…) says, in its replacement: "type: 'table' renders a row of measures, and the chart families (bar / line / area / combo) render one mark per measure — all of them keep the unbounded values they have always had."

    Why the renderer, not the spec.

    • The spec is not wrong here. Ruling D (5643392537) judged the metric family wrong because a one-number tile cannot carry several numbers. It named table and the chart families as the correct "different visual" for several numbers, and the spec wrote that down.
    • A table of measures with no dimension is a standard visual: one totals row, as mainstream BI tools render it. Narrowing the spec would refuse the very remedy the ruling points authors to.
    • So this is declared ≠ delivered on objectui's side, and it closes where it is not delivered.

    Why p3. This is the grade of objectui#8894, whose ruling D step 2 waits on this card. Every door accepts the widget, and the result drops measures behind a console warning.

    Direction.

    1. Only a metric-family type is a tile by type. With no dimension and two or more measures:
      • table / pivot renders one row carrying every measure;
      • bar / line / area / combo render one mark per measure, with the category axis being the measures' labels.
    2. One measure and no dimension: unchanged, since nothing is dropped.
    3. Other non-metric types with no dimension and two or more measures (pie / donut / funnel / scatter and the like) are ones the spec's text does not name. The claim measures what they do. If they drop measures, it returns an open question. ⛔ No display semantics are invented for them here.
    4. Pins:
      • the card's table over revenue, cost and margin renders all three;
      • a two-measure dimensionless bar renders two marks;
      • a metric tile is unchanged (the control);
      • the query still selects exactly the declared measures.
    5. objectui#8894's warning is not touched here. Its narrowing follows this card; see the retriage answer there.

    Generated by Claude Code

  2. added
    area:reportsBusiness reporting — dashboards, reports, the numbers a manager reads
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 30, 2026
  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 11 — domain:ui execution seat 2
    Session: session_011p7ikEivgXefNDaE5S5Uec
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11261-dimensionless-measures-render
    Worktree: objectui-issue-11261
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (triage 5918660676: the renderer delivers the spec's stated behaviour, with no spec narrowing and no ruling):

    • packages/plugin-dashboard/src/DatasetWidget.tsx: only a metric-family type is a tile by type. A dimensionless table / pivot with 2+ measures renders one row of every measure. A dimensionless bar / line / area / combo renders one mark per measure, with the measures' labels on the category axis.
    • The chart and table renderers it hands to, in plugin-dashboard, only as far as that needs.
    • Pins beside them, and one changeset.
    • ⛔ Not objectui#8894's warning: its narrowing follows this card. ⛔ No display semantics invented for pie / donut / funnel / scatter: those are measured and returned as an open question if they drop measures.
      Stop on breach; explain in the report.
      Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates has no objectui derivation, so there is no path-derived mandate; objectstack 41dcf118)
      Clause-②: no
      Why no: a renderer delivers what the installed spec already states ("type: 'table' renders a row of measures"; the chart families "render one mark per measure"), for widgets every door already accepts. No schema, published type or accepted set moves.
      Thread-read: 5918660676
      Serial constraints cleared: read 2026-09-30T21:11Z at objectui origin/main f4ed2387e. PR objectui#11257 (objectui#8894, the door pins) landed as dae76a456; it did not touch DatasetWidget.tsx. No open PR names packages/plugin-dashboard/**, and no live claim names DatasetWidget.tsx. objectui#8894 is in pm:queue + pm:retriage and follows this card.

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11261,
      "status": "done",
      "branch": "claude/issue-11261-dimensionless-measures-render",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11278",
      "session": "session_011p7ikEivgXefNDaE5S5Uec (subagent of the PM session; identity = the branch named in claim 5919807834)",
      "premise_still_valid": true,
      "summary": "Premises re-measured at objectui origin/main f61dab169 (the order's f4ed2387e had moved): `isMetric = METRIC_TYPES.has(widgetType) || dimensions.length === 0` is as stated, and the installed @objectstack/spec 17.5.0 carries both texts (the arity refusal message and the ADR-0087 `dashboard-widget-metric-family-multi-measure-refused` replacement). DatasetWidget now treats only the metric family as a tile by type. A dimensionless widget stays a tile unless it has 2+ measures on `table`/`pivot` (the flat-table branch renders its one row, with no totals footer because that row is the [] grand total) or on a widget whose CHART_TYPE_MAP family is bar/line/area/combo. For those, the single row is transposed into one chart row per measure, with the headerLabel as the category, one series labelled by the existing `dashboard.total` key, and the compareTo overlay kept. One measure and no dimension stays on the metric branch on every type (pinned byte-identical for table/pivot/bar/line/area/combo). The nine types the spec's text does not name were measured: they still drop measures, so they are returned as open question 1 with no semantics invented. objectui#8894's warning code is untouched. CONFLICT with the dispatch Zone 1 item 5 ('after this card it fires only for metric-family tiles'): measured, it still fires for those nine types' dimensionless multi-measure tiles until question 1 is answered (open question 2).",
      "tests": "At HEAD 7d54b7cfb: `pnpm exec vitest run packages/plugin-dashboard/src/` -> Test Files 155 passed (155), Tests 1420 passed | 6 skipped (1426); baseline at BASE f61dab169 -> 154 files, 1395 passed | 6 skipped; the delta is exactly the new file's 25 tests. New file DatasetWidget.dimensionlessMeasures-11261.test.tsx alone -> 25 passed (real Recharts marks via the chartConfigMarks-9203 harness: bar/combo 2 `.recharts-bar-rectangle`, x ticks ['Revenue','Cost']; compare overlay 4 marks; table/pivot one tbody row ['510000','120000','390000'], no total row). ABLATION (fix committed first at 15375c974; `node ../objectstack/scripts/ablation-replace.mjs` WRAP mode, anchor = the new isMetric line x1 -> x0, replacement = the old `METRIC_TYPES.has(widgetType) || dimensions.length === 0` x0 -> x1, blob 9640acd2abcc -> 4bf0c16ab58b; no dist involved, since the tests import ../DatasetWidget relatively and the root vitest config aliases @object-ui/* to src) over the new file + the 8894 file + DatasetWidget.subCaption (PR objectui#8887's pins) -> Tests 7 failed | 38 passed (45): exactly the 7 SUBJECT rows red (table, pivot, bar, combo, line, area, compare overlay); 18 CONTROL + 9 objectui#8894 + 11 subCaption green. Restore: blob after == HEAD blob 9640acd2abcc, `git diff HEAD` empty (tool verdict, re-checked independently with git hash-object / rev-parse HEAD:path). Consumers: 4 app-shell files rendering DatasetWidget + PR objectui#11257's door pins (packages/types, app-shell) -> 6 files, 84 passed. type-check: closure built with `turbo run build --filter='@object-ui/plugin-dashboard^...' --concurrency=2` (12/12), then `pnpm --filter @object-ui/plugin-dashboard type-check` exit 0 (script echoed `tsc --noEmit && tsc -p tsconfig.test.json`); `--listFiles` includes both touched test files.",
      "gates": [
        "HEAD 7d54b7cfb · vitest packages/plugin-dashboard/src/ · VERDICT command-exit 0 · 155 files / 1420 passed / 6 skipped",
        "HEAD 7d54b7cfb · pnpm --filter @object-ui/plugin-dashboard type-check · VERDICT command-exit 0",
        "HEAD 7d54b7cfb · pnpm exec eslint on the 3 changed .tsx files · exit 0 · 0 errors; the 11 warnings in DatasetWidget.tsx equal BASE's 11 (same rules, measured by linting BASE contents via --stdin); new test file 0/0",
        "HEAD 7d54b7cfb · node scripts/check-changeset-presence.mjs · exit 0 · 1 changeset for 3 published source files",
        "HEAD 7d54b7cfb · node scripts/check-control-bytes.mjs · exit 0 · OK (9699 tracked text files)",
        "HEAD 7d54b7cfb · pnpm check:new-line-citations · exit 0 · VERDICT 0 new citation(s)",
        "HEAD 7d54b7cfb · node scripts/markdown-test-inputs.mjs --audit · exit 0 (the changeset is markdown)",
        "HEAD 7d54b7cfb · vitest scripts/__tests__/ · VERDICT command-exit 0 · 177 files passed, 2 skipped; 5371 passed | 2 skipped",
        "HEAD 7d54b7cfb · consumers (4 app-shell DatasetWidget files + 2 objectui#11257 door pins) · VERDICT command-exit 0 · 84 passed",
        "CI: not awaited (in_progress at report time; CI convergence is the PM's)"
      ],
      "files_changed": [
        "packages/plugin-dashboard/src/DatasetWidget.tsx (+105 -14)",
        "packages/plugin-dashboard/src/__tests__/DatasetWidget.dimensionlessMeasures-11261.test.tsx (+197, new)",
        "packages/plugin-dashboard/src/__tests__/DatasetWidget.unrenderedMeasures-8894.test.tsx (+12 -5)",
        ".changeset/11261-dimensionless-measures-render.md (+29, new; '@object-ui/plugin-dashboard': patch)"
      ],
      "line_budget": "343 insertions, 19 deletions over 4 files (git diff --stat BASE..HEAD); no line-ratcheted ledger touched",
      "deviations": [
        "DatasetWidget.unrenderedMeasures-8894.test.tsx: one SUBJECT's example moved from a dimensionless `bar` (no longer a tile after this card) to a dimensionless `pie` (still a tile). Its assertions and the warning code are unchanged. Zone 1 item 5 forbids touching the warning, not its pins, but it is named here because that file is objectui#8894's.",
        "File surface: the claim lists DatasetWidget.tsx, its renderers, the pins and one changeset. The diff stays inside that (no chart/table renderer outside DatasetWidget.tsx needed a change); nothing in packages/core was edited.",
        "The claim's origin/main f4ed2387e had moved to f61dab169 when the worktree was cut; the premises were re-measured there.",
        "One foreground wait on a lock-backgrounded suite run matched the wrong PID and timed out (exit 124). The suite had completed meanwhile, and its log was read afterwards; the results were not affected."
      ],
      "mcp_calls": "0",
      "api_writes": "3 through the fleet-write relay (each packed as ONE POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (PR 11278, draft forced by the relay); (2) label-write assign -> POST /repos/objectstack-ai/objectui/issues/11278/assignees (os-support-ai, read back MATCHES; labels untouched, `plugin`/`tests` were already present from the labeler); (3) post-stamped -> POST /repos/objectstack-ai/objectui/issues/11261/comments (this os-dev-report). Plus 2 `git push` of the branch (not REST).",
      "open_questions": [
        {
          "question": "Q1. The nine ChartTypeSchema members the spec's text does not name (`pie`, `donut`, `funnel`, `scatter`, `column`, `horizontal-bar`, `radar`, `treemap`, `sankey`): measured on this branch, a dimensionless one with 2+ measures is still a one-number tile. It renders values[0], drops the rest, and fires objectui#8894's console warning (pinned as nine CONTROL rows marked 'measured, open question'). Every door accepts them. What should they do?",
          "options": [
            "A. Spec narrows: an object-level refusal (beside checkDashboardWidgetMetricMeasureArity) of 2+ measures on a dimensionless widget whose type is outside table/pivot/bar/line/area/combo, with remedy text naming those, plus an ADR-0087 entry. objectui's mirror must re-attach it (its .shape spread drops object-level checks today). Cost: one spec check, one entry, one mirror line. Census: 0 authored widgets affected.",
            "B. Renderer only: add `column` and `horizontal-bar` to MEASURE_AXIS_CHART_FAMILIES (the spec's 'Comparison' group beside `bar`; same transposition path, no new semantics; a 2-member set change plus pins). The other seven keep dropping.",
            "C. B for column/horizontal-bar, and A for the other seven (pie, donut, funnel, scatter, radar, treemap, sankey).",
            "D. Leave as is: tile plus console warning."
          ],
          "recommendation": "C. Business need (measured): 0 authored dimensionless multi-measure widgets of ANY type in objectstack's examples at 75519e1c0 or objectui's examples/apps. There is no demand for a pie-of-measures, while horizontal-bar is the ordinary choice for long measure labels under the spec's own 'use a bar' advice. Long-term: contract-first. A renderer that drops declared measures behind a console warning is declared-not-delivered, and a narrowing is free to relax later (the ADR's own argument). column/horizontal-bar reuse the one path rather than a second dialect. AI-error: a publish-time refusal is loud where a console warning is invisible to an AI author, and D leaves `pie` with three measures silently losing two. Startup focus: C is the minimum that closes the hole, with no new renderer semantics and no new CI gate (the refusal is a schema check on an existing door). B alone leaves seven types dropping; A alone refuses two types that a one-line extension would honour."
        },
        {
          "question": "Q2 (a conflict, named rather than resolved). Dispatch Zone 1 item 5 says that after this card objectui#8894's warning 'fires only for metric-family tiles'. Measured, it also still fires for the nine types in Q1 (the moved objectui#8894 SUBJECT with `pie` and the nine CONTROL rows pin this). objectui#8894's ruling D step 2 removes the warning once the door makes it unreachable. Should that removal wait on Q1?",
          "options": [
            "A. objectui#8894's step 2 waits on Q1. The warning stays reachable, and true, for those nine types until they either render every measure or are refused at the door.",
            "B. Remove the warning now. The nine types then drop measures in complete silence until Q1 lands."
          ],
          "recommendation": "A. Business need: the drop is real today on nine accepted types. Long-term: removing the only signal before the hole closes recreates the pre-objectui#8894 silent-drop defect. AI-error: silence is exactly what lets batch-authored metadata go unnoticed. Startup focus: waiting costs nothing, since no code changes until Q1 is decided."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted in the PR's Acceptance notes, not filed · observation, not measured through a public door: DatasetWidget's `chartType = CHART_TYPE_MAP[widgetType] ?? 'bar'` routes a widget with NO `type` and a dimension to a bar chart, while the spec defaults `type` to `metric` (WIDGET_TYPE_DEFAULT). A spec-parsed document always carries a type, so only an unparsed client draft could reach it. This PR reads CHART_TYPE_MAP without that default, so it adds no path through it · dedupe words: `typeless widget renders as bar` · `CHART_TYPE_MAP default bar` · `WIDGET_TYPE_DEFAULT metric renderer`"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR objectui#11278 at 7d54b7cfb (p3, Fixes). Ready and auto-merge follow this ACCEPT

    domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account.

    • Claim 5919807834.
    • Dev report 5920760284.
    • Contract review 5920945720: PASS on this head.
    Implemented-by:  claude/issue-11261-dimensionless-measures-render
    Reviewed-by:     session_011p7ikEivgXefNDaE5S5Uec
    
    item reading
    classification only a metric-family type is a tile by type. A dimensionless widget with 2+ measures leaves the tile when it is table / pivot, or when its CHART_TYPE_MAP family is bar / line / area / combo. column, horizontal-bar and the other unnamed types still take the tile branch
    table table / pivot render one row carrying every measure, with no totals footer (that row is the grand total)
    chart bar / line / area / combo render one mark per measure: the measures' labels on the category axis, one series labelled by the existing dashboard.total key, and the compareTo overlay kept. No new locale key
    one measure unchanged on every type (the metric branch), pinned byte-identical for table / pivot / bar / line / area / combo
    query still selects exactly the declared measures; PR objectui#8887's two pins stay green
    ablation the old isMetric restored: exactly the 7 SUBJECT rows red; restore proven
    warning objectui#8894's warning code is untouched. One of that file's SUBJECT examples moved from a dimensionless bar to a dimensionless pie, with its assertion unchanged
    changeset @object-ui/plugin-dashboard patch; Clause-②: no
    CI head 7d54b7cfb: 43 check-runs, 40 success, 3 skipped by design (Test (coverage), its matrix row, dependabot), 0 failed
    PR body one sentence miscounted rows by file (the review's ③). It was corrected through the relay before this ACCEPT and read back identical, because the squash message is the body
    closing keywords Fixes #11261
    governed none

    Carried, not answered here: the report's two open questions were returned as triage's direction item 3 asked:

    • Q1: nine unnamed chart types are still tiles that drop measures.
    • Q2: the dispatch line "the warning then fires only for metric-family tiles" is measured false.

    Both go to objectui#8894, the family's carrier, with pm:retriage. Its planned narrowing rests on the premise Q2 falsifies.

    Recorded, not filed: a typeless widget routes to bar through chartType's default. It is reachable only by an unparsed draft, since the spec defaults type to metric, and this diff adds no path through it.

    domain:ui seat 2 · ACCEPT · 2026-09-30T22:42Z


    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

area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions