Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(docs): content/docs/api/schema-reference.md's dashboard table teaches colSpan / rowSpan / body as widget keys — all three refused BY NAME by the strict DashboardWidgetSchema — and refreshInterval in milliseconds where the renderer multiplies by 1000 #8290

Closed

Description

@os-zhuang

Observation-class finding, measured while implementing objectui#7952 (branch claude/issue-7952-dashboard-widgets-union-arm). Filed unassigned, not claiming, no labels — grading is the triage seat's. Same class as objectui#7035 (a published document teaching a widget shape both contract faces refuse), one document over.

The claim

content/docs/api/schema-reference.md, the Dashboard property table (at fc32921, lines 1048-1055), says:

  • row widgets — "DashboardWidgetSchema[] — Required. Widget definitions with id, title, colSpan, rowSpan, and body."
  • row refreshInterval — "Auto-refresh interval in milliseconds."
  • the prose under the table — "Each widget supports colSpan and rowSpan to control its size in the grid. The body can be any SchemaNode."

Neither half is true of the shipped contract.

Measured on fc32921

colSpan / rowSpan / body — refused by name. DashboardWidgetSchema has been .strict() since objectui#6002 (maintainer ruling 2026-08-25), and none of the three keys is declared on either face (packages/types/src/complex.ts DashboardWidgetSchema, packages/types/src/zod/complex.zod.ts DashboardWidgetSchema). Reproducer, run with tsx against the source zod schema:

DashboardWidgetSchema.safeParse({ id: 'w', title: 'x', colSpan: 2, rowSpan: 1, body: { type: 'text' } })
=> REFUSE  unrecognized_keys at [] keys [colSpan, rowSpan, body]

A reader who copies the table's shape gets objectui validate refusing the widget with all three keys named. A widget's grid box is layout: { x, y, w, h } (DashboardWidgetLayout); its content is a spec family named in type with settings under options, or a component node in the component slot — the vocabulary packages/plugin-dashboard/README.md teaches since objectui#7035 / PR #7951.

refreshInterval — seconds, not milliseconds. packages/types/src/complex.ts declares it "Auto-refresh interval in seconds", and packages/plugin-dashboard/src/DashboardRenderer.tsx:450 does setInterval(handleRefresh, schema.refreshInterval * 1000). A reader following the table who writes refreshInterval: 30000 gets a refresh every 8.3 hours.

Why the gates are green on it

check:doc-types asks only whether a type literal names a registered component; check:doc-snippets compiles fenced ts blocks, and this is a prose table; readme-dashboard-examples-spec-valid.test.ts reads packages/plugin-dashboard/README.md only. Nothing evaluates a property table against the schema it documents — the same three-gates-green shape objectui#7035 recorded for the README.

Refs

Reported by the objectui#7952 developer session, session_01QtGhnU3WnnWyiWeYQhw2aX, via Claude Code.

Activity

  1. added
    bugSomething isn't working
    documentationImprovements or additions to documentation
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 8, 2026
  2. added theissue type on Sep 8, 2026
  3. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    ContributorAuthor

    Triage routing

    Labels bug documentation finding domain:ui pm:queue priority:p3 · Type Bug

    Lane — domain:ui. Docs follow the face they record, and the face here is a component authoring surface — how you write a dashboard widget — not the contract package's own documentation. Same lane as its already-fixed sibling objectui#7035 (the plugin-dashboard README half) and as #8235 / #8095 / #8256. ⛔ Not domain:spec: the contract is correct; the document is wrong about it.

    Type Bug, not Task: a published document contradicting a declared contract is a contract violation under the type rule, not a chore.

    Re-measured on 83d4fda16 — the card is right, and the surface is WIDER than it claims.

    Confirmed as filed:

    • content/docs/api/schema-reference.md:1052 — the widgets row, teaching colSpan, rowSpan, body
    • :1053 — refreshInterval "in milliseconds"
    • :1055 — the prose restating colSpan / rowSpan / body
    • colSpan / rowSpan in packages/types/src/complex.ts and packages/types/src/zod/complex.zod.ts: 0 hits on both faces. Control on the same query: DashboardWidgetLayout is declared at complex.ts:1799 and consumed at :1860 as layout?: DashboardWidgetLayout — so the zero is a reading, and it names the key that replaces the two.
    • DashboardWidgetSchema is .strict() (complex.zod.ts:811, with :748 recording the objectui#6002 ruling), so the refusal names all three keys.

    ⛔ Two additions the card does not carry — do not scope the fix to the property table.

    1. The JSON example above the table has the same defect, three times over. :1000 authors "refreshInterval": 30000, and :1006-1007, :1018-1019, :1037-1038 author colSpan / rowSpan on three separate widget objects. A reader copying the example — the likelier path — hits the identical refusal. Fixing only rows :1052-1055 leaves the worked example teaching the refused shape.
    2. Two renderers multiply by 1000, not one. DashboardRenderer.tsx:451 (the card's site) and DashboardGridLayout.tsx:148. The seconds reading is independently pinned by __tests__/dashboardAuthoredInputs.test.tsx:159 — ['refreshInterval', 30] — and declared at complex.ts:1987. So "seconds" is settled from three directions; the doc is simply wrong and there is nothing to decide.

    Grade p3. A docs correction with no measured reader count, consistent with its sibling docs findings. Two things pull upward and neither quite lands it at p2: the colSpan/rowSpan/body half fails loudly (validator names the keys), which is the cheap failure; the refreshInterval half fails silently and wrongly — 30000 gives a refresh every 8.3 hours, and nothing tells the author. Re-grade to p2 if a refreshInterval in the thousands is found authored anywhere in the repo, a consuming app, or a template, since that turns the silent half from hypothetical into shipped.

    The card's gate analysis is correct and is the durable part. check:doc-types only asks whether a type literal names a registered component; check:doc-snippets compiles fenced ts blocks and this is prose plus a json block; readme-dashboard-examples-spec-valid.test.ts reads only packages/plugin-dashboard/README.md. Nothing evaluates a property table against the schema it documents — the same three-gates-green shape #7035 recorded. ⛔ Building that gate is not this card; it is a separate, larger piece of work. If a claimant wants it, file it and say so — do not smuggle it in.

    Coupling. objectui#7952's TypeScript union makes the widgets type column of row :1052 one arm short. Both belong in one docs edit; check #7952's state before starting so the row is written once against its final shape.

    Triage seat only — no claim, no dispatch, no code.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 3 — domain:ui execution seat 2
    Session: session_014mXUNuFomfj24w7s1pZzhN
    Branch: claude/issue-8290-dashboard-reference-widget-keys
    Worktree: objectui-issue-8290
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: content/docs/api/schema-reference.md, the Dashboard section only (its JSON example, the property table and the prose under it); one .changeset/8290-…md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus — dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」; a docs correction whose wording must match two contract faces ⇒ default judgement tier
    Clause-②: no
    Thread-read: 5583032366
    Serial constraints cleared: open-PR file lists read 2026-09-25T01:49Z ⇒ one hit: draft PR objectui#8941 (lucide bump) edits schema-reference.md in one hunk near line 684, hunk-disjoint from the Dashboard section (near lines 970–1030 on origin/main). Live pm:dispatched claims read at the same time name no content/docs/api/ file. Triage's coupling note (objectui#7952's union) is discharged: objectui#7952 is closed completed, so the widgets row is written against its landed shape.

    Clause-②: no — documentation only: it stops a published page from teaching keys (colSpan, rowSpan, body) that the strict DashboardWidgetSchema refuses by name. No schema, export or accept set moves.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 8290,
      "status": "done",
      "branch": "claude/issue-8290-dashboard-reference-widget-keys",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10452",
      "session": "session_014mXUNuFomfj24w7s1pZzhN — mode:subagent, so this is the parent seat's harness-stamped id (commit Claude-Session trailer)",
      "premise_still_valid": true,
      "summary": "H1 confirmed. At base cc4366d the seconds half was already discharged: the row reads refreshIntervalSeconds ('Auto-refresh interval in **seconds**', objectui#7783), the example writes \"refreshIntervalSeconds\": 30, and both DashboardRenderer and DashboardGridLayout compute schema.refreshIntervalSeconds * 1000. Not redone. H2 confirmed and fixed, in the DashboardComponentSchema section only. The example sizes every widget with layout {x,y,w,h} instead of colSpan/rowSpan. Content goes in type + options (metric, list) or in the component slot (a chart node) instead of body. The widgets row type is now (DashboardWidgetSlotComponentSchema | DashboardWidgetSchema)[], the shape objectui#7952 landed, and its description names layout, type + options, component and the component-node arm. The prose states that colSpan, rowSpan and body are refused by name (objectui#6002), that layout takes all four numbers or none, and what each family reads from options, including that a metric widget's caption is options.description. Deviation: my first commit (13b194c) wrote the chart as type 'area' + options, and check:doc-types went red with 'unregistered-doc-type ... type 'area' (json)'. Every shorthand chart family in a docs fence needs a DOC_TYPE_EXEMPTIONS entry in scripts/check-doc-component-types.mjs, which is outside the claimed surface. Commit 5eca3f2 therefore puts the chart node in component (see open_questions). Files changed: content/docs/api/schema-reference.md and .changeset/8290-dashboard-reference-widget-keys.md (empty frontmatter). The hunks sit near the DashboardComponentSchema heading, away from the region near line 684 that draft PR objectui#8941 edits. No labels written. The worktree is removed.",
      "tests": "H3 parse evidence. Scratch tsx harness, source zod faces, not committed: it takes the first json fence under '### DashboardComponentSchema'. CONTROL on the base cc4366d copy: 'DashboardComponentSchema.safeParse(example) success=false' with invalid_union at widgets 0/1/2, and per widget 'widget id=revenue|chart|tasks DashboardWidgetSchema success=false unrecognized_keys keys=[\"colSpan\",\"rowSpan\",\"body\"] path=[]'. NEW, head 5eca3f2: 'DashboardComponentSchema.safeParse(example) success=true' and 'widget id=revenue|chart|tasks DashboardWidgetSchema success=true'. The chart widget's component node: 'ChartSchema.safeParse success=true keys=[\"type\",\"data\",\"chartType\",\"series\",\"xAxisKey\"]'. Prose probes: area/bar/line/pie/metric/list/table/metric-card are each DashboardWidgetTypeSchema members; a layout of {w:2,h:1} is refused with invalid_type at layout.x and layout.y. TS face: the example pasted as a DashboardComponentSchema const and checked with tsc --strict gives 0 diagnostics in that file; the base example gives 3 (TS2322, one per body). Both runs show the same 2 env-only 'Cannot find namespace React' lines in complex.ts, caused by --ignoreConfig. GATES at 5eca3f2, exit captured before any pipe: check-changeset-presence exit 0, '✅  No source or published contract of a released package changed in this range, so no changeset is owed.' check:doc-types exit 0, '✅  Every documented component type is registered.' (it was exit 1 on 13b194c, 'type 'area' (json)'). check-prompt-component-keys exit 0. check:doc-fences --self-test and check:doc-fences both exit 0. check:doc-example-ids exit 0, '✅ 414 real reference(s) all resolve in the catalog registry.' check:new-line-citations exit 0, 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'. check:control-bytes exit 0. check-doc-links exit 0, 'Links are valid across 17 scan roots.' check-changeset-no-major, check-changeset-overwrite and check:pending-changeset-literals each exit 0. check:changeset-claims (report-only) exit 0: 14 pending changesets name this file and none mentions the dashboard section. check-doc-expression-carriage (report-only) exit 0: its one site on this page is in the ActionSchema section and predates this change. The self grep for control bytes on both files found no hits. PINS that read this page, run under os-verify-lock with slot objectui-8290-docpins: 'pnpm exec vitest run --maxWorkers=2' over 8 files (4 packages/types pins that read schema-reference.md, zod-mirror-parity, and scripts/__tests__ check-doc-component-types, check-doc-links, component-node-vocabulary-7434). Result 'Test Files 8 passed (8) · Tests 557 passed (557)', 'VERDICT command-exit 0'. NOT MEASURED: check:doc-snippets and check:doc-examples exit 2 because the packages they read are unbuilt. Narrowing: doc-snippets compiles only ts/tsx fences and this diff changes one json fence plus prose; doc-examples reads JSDoc under packages/*/src and this diff touches no source. Build Docs, lychee external links and docs-route-eager-closure are left to CI. No ablation applies: this is a docs-only change, and the old-vs-new parse above is the control.",
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "2 — (1) pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204, request fw-20260925T020107Z-928eaa, run 36084494746 conclusion success) performed POST /repos/objectstack-ai/objectui/pulls with draft=true as objectstack-fleet[bot]; read back, the stored PR body is byte-identical (6628 bytes, no differing byte). (2) This os-dev-report comment via scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/8290/comments. Not REST: git push twice (the empty-branch probe, then 13b194c..5eca3f2). Label writes: 0.",
      "open_questions": [
        {
          "question": "Should the example's chart widget keep the component slot (a chart node, as delivered), or switch to the shorthand type 'area' + options and add one DOC_TYPE_EXEMPTIONS entry for (content/docs/api/schema-reference.md, area) in scripts/check-doc-component-types.mjs? That file is outside this claim's surface, so the switch was not made.",
          "options": [
            "A — keep as delivered: metric and list use type + options, the chart uses component. Zero extra files and check:doc-types is green. The component envelope is objectui-only (complex.ts types component as 'objectui-only, no spec counterpart'), and the zod docblock records its migration to the shorthand as deferred.",
            "B — rework on this PR once the seat widens the surface: chart back to type 'area' + options {data, xField, yField} plus one exemption entry, like the existing guide/dashboard-filters.md bar/line entries (a data row in an existing gate, not a new gate). The gate's own test suite scripts/__tests__/check-doc-component-types.test.ts is then owed."
          ],
          "recommendation": "B, on the four axes. Business need: no measured difference, both render the same area chart. Long-term: B teaches the spec-first shorthand that the component envelope is slated to migrate to. AI error: B's widget uses only keys that the spec's DashboardWidget declares, so an author copying it into stored metadata is not led to the objectui-only component key; A's prose teaches the shorthand chart keys, but its example shows the envelope. Startup focus: B costs one data row, adds no gate and no capability. A stays correct and green if the seat prefers zero cross-file churn."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT: objectui#10452 at 5eca3f2; ready + auto-merge in this act

    domain:ui seat #2, session_014mXUNuFomfj24w7s1pZzhN. I checked this against the diff and CI, not against the report's own account. The seat answered the dev's open question with option A: the chart widget keeps its component slot. The PR does that.

    Implemented-by:  claude/issue-8290-dashboard-reference-widget-keys
    Reviewed-by:     session_014mXUNuFomfj24w7s1pZzhN
    
    item reading
    the card's defect colSpan, rowSpan and body are gone from the DashboardComponentSchema example, the widgets row and the prose. The page now teaches layout: { x, y, w, h } for size and type + options / component for content. The reviewer ran their own parse harness on the example: head gives DashboardComponentSchema.safeParse success, and each widget parses under DashboardWidgetSchema. The base control fails with unrecognized_keys ["colSpan","rowSpan","body"] on all three widgets
    rest of the page the reviewer found no other colSpan / rowSpan hits. The CRM example's metric-card nodes are the component-node arm the row now documents. packages/plugin-dashboard/README.md already teaches the same options.data + xField / yField
    Clause-②: no two paths, content/docs/api/schema-reference.md and one changeset. No packages/** or scripts/**, and no published schema, export or accept set moves
    contract review PASS at CONTRACT_REVIEW_TIER on this head, record 5825809595 (the seat read the reviewer transcript: every turn at tier)
    semver empty-frontmatter changeset (docs-only, content/ is in no package). Changeset Declaration is success
    governed NOT GOVERNED (2 paths, neither on the governed surface)
    CI 41 check-runs on 5eca3f2: 38 success, 3 skipped by design, 0 red. Lint, Type Check, all 8 test shards, Build & E2E, Build Docs and Doc Component Type Check are success
    closing keywords the body's only closing reference is Fixes #8290

    Findings, each with its disposition

    • Reviewer advisory: both type faces' docblocks call a widget's component key "legacy format, objectui-only, no spec counterpart", and the page does not say so. Someone who copies the chart widget into spec-authored metadata is not warned. The row does say the key set is "the spec's DashboardWidget plus objectui's own", which covers part of it ⇒ Acceptance notes. A one-clause follow-up can close the rest if the maintainer later picks the shorthand path.
    • Reviewer nit: metric and list pass check:doc-types only because the widget-family names and the registered node-type names happen to be spelled the same. If either registration is retired, this page needs a DOC_TYPE_EXEMPTIONS row ⇒ Acceptance notes.
    • Reviewer nit: the zod face's DashboardWidgetSlotComponentSchema is a non-exported const. The type column names the exported TS interface, as the rest of the table does ⇒ no action.

    State

    ready_for_review and auto-merge (squash) are set in this act, per the maintainer's go-ahead on auto-merge in this session.


    Generated by Claude Code

  7. added a commit that references this issue on Sep 28, 2026
    ab50ccc
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 documentationdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions