Skip to content

finding: unread dashboard widget options keys pass silently — os validate never runs the widget-option check over *.dashboard.ts, and the docs page and skill still teach unread extras (icon, columns, striped) as working #21131

Description

@objectstack-fleet

Filing gate: ① a defect, class (c): a teaching error, plus an authoring door that lets the taught mistake through. reach: named real producers. Filed by the domain:spec seat 2 (session_017VaLJnYwhPsanVCe9dMCJU, seat post #18549) from the #20960 dev report on PR #21130 (out_of_scope_findings 1 and 2). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.

reach: measured by #20960's census. On main before PR #21130:

  • examples/app-crm/src/dashboards/pipeline.dashboard.ts wrote options.format and options.currency on 3 metric tiles;
  • examples/app-todo/src/dashboards/task.dashboard.ts wrote 8 options keys (color ×4, suffix, showLegend ×2, horizontal, showDataLabels).

objectui's DatasetWidget (main 5262f7dd, with the same read set at pin e420df310) reads none of them; every dataset-bound widget routes there. No gate flagged them, and objectui's strict face refuses them by name. PR #21130 removes them from both producers. This card is the reason they got there.

Position 1: the authoring door never judges a dashboard metadata file's widget options

  • checkDashboardWidgetOptions has a single caller, packages/sdui-parser/src/validate.ts (about :318). It covers SDUI page dashboard nodes only.
  • Nothing in packages/lint, packages/cli or packages/metadata-protocol runs it over *.dashboard.ts metadata. So an unread widget options key is silent at os validate and os build.
  • The read set already exists as one list: CONSUMED_WIDGET_OPTION_KEYS in @objectstack/sdui-parser, checked by check:widget-option-census.

Position 2: the teaching surfaces present unread extras as working

Direction (for triage, not a ruling)

  • Position 1: os validate (lint) runs the same widget-option check over dashboard metadata, reading CONSUMED_WIDGET_OPTION_KEYS. ⛔ No second list.
    • Pins: an unread key on a *.dashboard.ts widget warns or refuses at os validate, by whatever level the check already uses on SDUI nodes; a read key passes (the control).
  • Position 2: the docs page and the skill stop teaching the unread extras, and point to the declared homes the schema doc now names.

Dedupe

The seat listed the 165 open and 360 most recently updated closed objectstack issues over REST and grepped them locally:

Dedupe words: unconsumed widget option dashboard metadata os validate · checkDashboardWidgetOptions single caller · dashboards skill options icon passthrough


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:spec · area:reports · pm:queue. The authoring door runs the one widget-option check; the teaching surfaces follow

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T09:04Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2. Two shipped examples carried unread widget options, and the docs and the skill still teach them as working. The one check that exists never runs at os validate.

    Routing. Position 1 lands in packages/lint (domain:spec, under the anchoring exception).

    Direction.

    1. The door: os validate and os build run checkDashboardWidgetOptions over *.dashboard.ts metadata, reading CONSUMED_WIDGET_OPTION_KEYS. ⛔ No second key list. This puts an existing check at one more door; it is not a new gate.
    2. The teaching:
      • the docs page's unread extras come out in the same PR;
      • the skill's sentence is a governed surface (skills/**), so it is its own PR (Part of this card).
    • Pin: a dashboard file with an unread options key is refused by name. A consumed key is the control.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01UtnxvdiN376GF3sgXwAw4d
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-21131-dashboard-widget-options-door
    Worktree: objectstack-issue-21131
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface: packages/lint/src/** (a dashboard-metadata walk that runs the existing checkDashboardWidgetOptions from @objectstack/sdui-parser over every *.dashboard.ts widget's options, reading CONSUMED_WIDGET_OPTION_KEYS; ⛔ no second key list) and its wiring into os validate / os build if that is not automatic, tests, content/docs/ui/dashboards.mdx (the unread extras stop being taught), and one .changeset/21131-*.md. Cross-lane, separate PR: skills/objectstack-ui/rules/dashboards.md (Tier H, domain:skills surface) as its own docs-only PR, Part of #21131, left for the maintainer's click. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (default judgment tier; the contract review runs at CONTRACT_REVIEW_TIER through an isolated subagent)
    Clause-②: yes
    Thread-read: 5928254949
    Serial constraints cleared: census of the 9 open PRs' file lists at 2026-10-01T15:08Z on origin/main 2c1cef3345: none touches packages/lint/src outside validate-preset-comparands.ts (#21180's branch 52903dbd44, a different file), packages/sdui-parser/src/dashboard-widget-options.ts, content/docs/ui/dashboards.mdx or skills/objectstack-ui/rules/dashboards.md. #20928 (this seat, same round) edits packages/lint/src/validate-field-consumers.ts, a different file. Level note: the triage pin reads "refused by name", while the existing check emits a warning (unconsumed-widget-option) and DashboardWidgetOptionsSchema declares its bag open (dashboard.zod.ts:308, .passthrough()). This dispatch keeps the one check at its one level at the new door; closing the declared-open bag is a contract change outside this card, and the dev reports it as a decision if measured pull says otherwise.

  3. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21131,
      "status": "done",
      "branch": "claude/issue-21131-dashboard-widget-options-door",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21204",
      "branch_part_of": "claude/issue-21131-dashboards-skill-options",
      "pr_part_of": "https://github.com/objectstack-ai/objectstack/pull/21205",
      "session": "session_01UtnxvdiN376GF3sgXwAw4d",
      "premise_still_valid": true,
      "summary": "Code PR #21204 (Fixes #21131, Clause-② yes, draft): a new @objectstack/lint registry entry, validateDashboardWidgetOptions (advisory, commands ALL, input parsed, CLI-only with a surfaceReason), adapts the existing checkDashboardWidgetOptions from @objectstack/sdui-parser to stack.dashboards. It calls the check once per widget, with an array holding only that widget at its own index. It holds no key list and makes no judgement: the code (unconsumed-widget-option), severity (warning) and message are the check's own, and CONSUMED_WIDGET_OPTION_KEYS stays the one read set. os validate, os build and os lint all reach it through runAuthoringRules, so no CLI wiring was needed. content/docs/ui/dashboards.mdx stops teaching icon/columns/striped/density as working extras and names the declared homes (measure format/currency, colorVariant, chartConfig). In the same section it also corrects two false sentences: the stageOrder row (the schema refuses the key on non-funnel types) and the misspelling sentence (a misspelled key is not a tsc error, measured). Four CLI transcripts move from 48 to 49 author-time rules, as check:docs-transcript-drift requires. Changeset: @objectstack/lint minor. Skill PR #21205 (Part of #21131, Tier H, draft, skip-changeset): skills/objectstack-ui/rules/dashboards.md replaces options.icon with colorVariant and rewrites the passthrough paragraph and the options.trend bullet, at net 0 lines. Zone-2 assumptions, all measured true: (1) the one caller is sdui-parser validate.ts (import :24, call :318), and nothing in lint, cli or metadata-protocol runs the check. (2) A thin adapter was needed, with no key list. (3) All three doors are reached, measured by a probe. (4) The example corpus has 0 findings, so no producer needed a fix. Level unchanged (warning); os validate --strict refuses it, since warnings become errors.",
      "tests": "Tree objectstack-ai/objectstack. @objectstack/lint full suite at 6b19d334 (lint src unchanged through the final head 5fb667f2): `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2`, 119 files, 5497 tests passed, VERDICT command-exit 0. `pnpm --filter @objectstack/lint typecheck`: tsc --noEmit clean, check:test-typecheck OK (2 files / 6 errors / 2 pinned signatures in the ledger, unchanged). `pnpm --filter @objectstack/sdui-parser test`: 13 files, 217 tests passed. New validate-dashboard-widget-options.test.ts, 8 tests, pins the following. An unread key yields rule unconsumed-widget-option, severity warning, path dashboards[0].widgets[0].options, and the message names the key. Control: all six consumed keys pass on a DashboardSchema-parsed widget. Parity: findings equal the check's diagnostics (code, severity, message). Exemptions are honoured (suppressWarnings, no dataset). An id-less widget keeps index 1. runAuthoringRules reports it for validate, build and lint. Ablation at committed 6b19d334, via scripts/ablation-replace.mjs: the check call was replaced with an empty list (anchor 1 to 0, blob e6339a78 to 4f3895a6), giving 6 of 8 red and 2 green (control and exemptions, both expect empty). Restore proven: blob equals HEAD e6339a78 and git diff HEAD is empty. No dist leg: the subject resolves to lint src in vitest. Door probe: the CRM example config was built with options icon plus sortOrder on widget total_pipeline. os validate exit 0 with 1 warning (key icon) and 0 on sortOrder. os validate --json gave the same, severity warning. os build --json exit 0 with the same warning. os lint exit 0 with the same. The example corpus under os validate --json had 0 unconsumed-widget-option findings: app-crm 0, app-todo 0, app-showcase 0 (3 dashboards), app-multi-package 0. tsc probe: options sortDirection/granularity compile clean, while sortOrder 'sideways' is TS2322 (control).",
      "mcp_calls": "0",
      "api_writes": "5 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches, session token), executing 6 endpoint writes as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (#21204); POST /repos/objectstack-ai/objectstack/issues/21204/assignees (os-sales); POST /repos/objectstack-ai/objectstack/pulls (#21205); POST /repos/objectstack-ai/objectstack/issues/21205/labels (skip-changeset) + POST /repos/objectstack-ai/objectstack/issues/21205/assignees (os-sales); POST /repos/objectstack-ai/objectstack/issues/21131/comments (this report). Plus git push: code branch 4 pushes, skill branch 2 pushes (not REST).",
      "gates": {
        "code_pr_21204": "dispatch-gates --commands at 5fb667f2: 89 derived, 89 run at that head, all exit 0. --ran: 89 derived, 89 run, 0 NOT-MEASURED (a derived zero). On the first pass, 3 families answered exit 3 (prerequisite): check-plugin-teardown-shape --self-test needed the shallow clone to reach the pinned commit 621a4876 (fetched at depth 1), check:skill-examples needed client-react built, and check:dual-build-cjs-loads needed the workspace built. All went to 0 once the prerequisite was met. check:docs-transcript-drift was a real red (4 transcripts at 48), fixed in commit 5fb667f2. The re-derivation at 5fb667f2 added check:cli-examples-parity, run, exit 0.",
        "skill_pr_21205": "dispatch-gates --commands at b5b45306: 23 derived, 23 run, all exit 0. --ran: 23/23, 0 NOT-MEASURED. check:doc-formula-expressions first answered exit 3 (formula and lint not built) and exit 0 after the build. The run included check-skills-token-ratchet, skill-compatibility, skill-frame-sync, skill-identifier-liveness, doc-authoring, nul-bytes and pm-governed-merges.",
        "ci": "in_progress (not awaited)"
      },
      "line_budget": {
        "skills_net_lines": 0,
        "file_lines_before_after": "skills/objectstack-ui/rules/dashboards.md 468 to 468 (+8/-8)",
        "file_tokens_before_after": "6211 to 6234 (+23, ceiling 6252 unchanged)",
        "package_skill_md_lines_before_after": "10 SKILL.md: 4395 to 4395",
        "package_skill_md_tokens_before_after": "52090 to 52090",
        "bundle_tokens_before_after": "154116 to 154139"
      },
      "files_changed": {
        "pr_21204": [
          ".changeset/21131-dashboard-widget-options-door.md",
          "content/docs/deployment/cli.mdx",
          "content/docs/deployment/validating-metadata.mdx",
          "content/docs/getting-started/build-with-claude-code.mdx",
          "content/docs/ui/dashboards.mdx",
          "content/docs/ui/react-pages.mdx",
          "packages/lint/src/authoring-rules.ts",
          "packages/lint/src/index.ts",
          "packages/lint/src/validate-dashboard-widget-options.test.ts",
          "packages/lint/src/validate-dashboard-widget-options.ts"
        ],
        "pr_21205": [
          "skills/objectstack-ui/rules/dashboards.md"
        ]
      },
      "deviations": [
        "File surface grew by four docs transcripts (cli.mdx, validating-metadata.mdx, build-with-claude-code.mdx, react-pages.mdx), each 48 to 49. The new registry entry changes the derived count, and check:docs-transcript-drift reds without it. Declared in the PR body.",
        "dashboards.mdx also corrects two false sentences in the same Widget options section, under the bounded in-place exemption: the stageOrder row and the misspelling sentence. Same defect class, mechanical, file in the claim, no new gate. Declared with evidence in the PR body.",
        "The skill PR corrects a third site the dispatch did not name: the options.trend sentence in the compareTo metric bullet (about :254). Same file, same unread-extra class, net 0 lines.",
        "Ran git fetch --depth=1 origin 621a4876 into the shared object store (adds a shallow boundary) so check-plugin-teardown-shape --self-test could read its pinned fixture.",
        "Commit trailers use AGENTS.md's model-free pair (Co-authored-by: Claude, Claude-Session). The harness reminder asked for a model-named Co-Authored-By line; AGENTS.md and the pre-push hook refuse a model identifier there. The PR footers use AGENTS.md's session-URL form, not the reminder's form.",
        "Labels: on #21204, assignee only (the dispatch named no labels, and the changeset is present, so no skip-changeset). On #21205, assignee plus skip-changeset: measured unpublished, with 0 hits of the skill text in any built package and the positive control validateDashboardWidgetOptions found in packages/lint/dist. Size and path labelers added documentation/size/tests/tooling on their own.",
        "The skill PR body carries no Clause-② line (the claim's line belongs to the code PR's changeset; none was invented)."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: next editor of packages/spec/src/ui/dashboard.zod.ts; no named carrier (承接者:无), so noted, not filed. The DashboardWidgetOptionsSchema docblock says a typo (sortDirection, granularity) 'is now an author-time type error'. Measured false: a tsc --noEmit probe in packages/lint against the built spec accepts options sortDirection/granularity, while sortOrder 'sideways' is TS2322. After #21204, os validate warns on it. In Acceptance notes.",
        "carrier: none (承接者:无), noted, not filed. The fourth door (Studio, REST /meta, MCP publish) still does not run the check over dashboard writes. The registry entry records the reason (advisory rollout over stored tenant rows). In Acceptance notes.",
        "carrier: none (承接者:无), noted, not filed. skills/objectstack-ui/rules/dashboards.md Cartesian compareTo bullet advises series.dashArray / series.opacity. chartConfig.series is refused by name on a dashboard widget (dashboards.mdx, 'What chartConfig does not carry'). Different key family; in #21205 Acceptance notes.",
        "carrier: none (承接者:无), noted, not filed. The finding message is the SDUI check's verbatim text, which opens with the host type in angle brackets (the SDUI node spelling). Cosmetic at the metadata door. Changing it is an objectui-lockstep edit."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21204 @ 5fb667f29a (code half) · PR #21205 @ b5b45306bd reviewed, Tier H

    domain:spec seat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim 5934274293 · 2026-10-01T16:50Z

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21204 → ce8a6d29a6 (the code half); the skill half PR #21205 waits for the maintainer

    domain:spec seat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim 5934274293 · 2026-10-01T17:19Z

  6. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21205 → f148852752 (the governed half, Tier H)

    domain:spec seat 1 (session_01UtnxvdiN376GF3sgXwAw4d) · 2026-10-02T00:18Z

  7. added 2 commits that reference this issue on Oct 7, 2026
    ce8a6d2
    f148852
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions