Repository navigation
spec(ui): DashboardWidgetOptionsSchema declares none of the renderer extras real producers write through its passthrough — app-crm's metric options { format, currency } is spec-valid only by catchall and red on objectui's strict face #20960
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·area:reports·pm:queue(domain:specas filed). Census first, then enforce-or-remove per keyTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T02:55Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. A real producer, app-crm's
pipeline.dashboard.ts, is spec-valid only through a catchall, and objectui's strict face refuses it by name. The maintainer's standing rule (「本项目以协议为基准……协议有问题应该立卡修改协议」) puts the fix in the protocol, and objectui#11228's ruling C keeps the strict face's closure.Direction. It is the card's own direction, confirmed:
- Census: every key objectstack's examples and templates write through
DashboardWidgetOptionsSchema's passthrough (and any other passthrough on the widget). One row per key: writer, reader, verdict. - Per key, under ADR-0049:
- written and read ⇒ declared and typed (
currencyas an ISO 4217 code); - written but unread ⇒ removed from the producer;
- neither ⇒ nothing.
- ⛔ No key is declared without a reader.
- written and read ⇒ declared and typed (
- One spec PR with a changeset. objectui's strict face then reads the declared keys by reference, as objectui's own follow-up.
- Out of scope, as the card says: the inline dashboard dialect (objectui#11228, ruled C).
Generated by Claude Code
- Census: every key objectstack's examples and templates write through
- addedarea:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-01T04:51Z
Session:session_017VaLJnYwhPsanVCe9dMCJU
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-20960-widget-options-census
Worktree:objectstack-issue-20960
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface:packages/spec/src/ui/dashboard.zod.ts, limited toDashboardWidgetOptionsSchema(about:233-:288) and any other passthrough on the dashboard widget the census finds, plus its tests inpackages/spec/src/ui/.- Producers under
examples/**and the packaged templates, only where the census verdict is "written but unread ⇒ removed from the producer". - An ADR-0087 entry and the regenerated
migrations/registry.ts, only if the registration gate asks for them. - The regenerated spec artefacts the declarations move.
- One
.changeset/20960-*.md.
⛔ Not the inline dashboard dialect: objectui#11228 ruled it C, and it is executed there. ⛔ Not objectui's strict authoring face; that is objectui's follow-up. Stop on breach and explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates--tier: Clause-② suspect surfacepackages/spec/src/**; build at the default tier, at-tier contract review owed before enqueue).
Clause-②: yes (the census decides the arm). Declaring a key enlarges the declared public surface, and typing its value (currencyas an ISO 4217 code) narrows what the catchall accepted. The PR carries the measured arm.
Thread-read: 5923800404
Serial constraints cleared: read at this stamp againstorigin/main0c5a71b094. Of the 31 open PRs, only this seat's PR #21053 (#20958, in the merge queue) touchesdashboard.zod.ts, and only in other regions: the import line, the new check aftercheckDashboardWidgetMetricMeasureArity(:675on), andDashboardWidgetSchema(:1005,:1162). This card's region isDashboardWidgetOptionsSchema. No fleetpm:dispatchedclaim namesdashboard.zod.tsor app-crm's dashboards. The dev mergesmainafter PR #21053 lands, before opening the PR.
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsPointer for this card's catchall census: one more consumed key that the strict face refuses. From
domain:devxseat 2 (objectui#10917),session_01TdiauJaVCHuj45EzZGUxHh, 2026-10-01T05:26Z. ⛔ Not a claim.- The key:
widgets[].options.description.- objectui#11228's ruling C kept it refused by name, as one of the nine inline-dialect keys.
- Yet objectui's
DatasetWidgetreads it on the dataset-bound path, as a metric tile's sub-caption (CONSUMED_WIDGET_OPTION_KEYSin@object-ui/sdui-parser).
- Who writes it: no producer in either repository, as measured by objectui#11070 round 6's dev (report
5925279994, PR objectui#11333). - What to do with it: add one census row: written by none, read by objectui. Your step 2 then decides between declaring it and having the reader drop it. ADR-0049 points to the reader dropping it unless a producer appears.
- The key:
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20960,
"status": "done",
"branch": "claude/issue-20960-widget-options-census",
"pr": "#21130",
"session": "session_017VaLJnYwhPsanVCe9dMCJU",
"premise_still_valid": true,
"summary": "Census first (H1), and it decided no declaration. Two examples write keys no renderer reads: app-crm pipeline.dashboard.ts writes options.format and options.currency on 3 metric tiles, and app-todo task.dashboard.ts writes color x4, suffix, showLegend x2, horizontal and showDataLabels. objectui DatasetWidget.tsx (main 5262f7dd; pin e420df310 has the same read set) reads only dateGranularity, sortBy, sortOrder, limit, stageOrder and description, and every widget with a dataset routes there. So each written key is written but unread and was removed from its producer. The tiles take their currency from the dataset measure, which already declares format 0,0 and currency USD. The seat-note row: options.description is read (DatasetWidget :1118 sub-caption) but has no authored writer (0 hits; translateDashboard only overlays it from a subCaption bundle entry, and this repo ships none), so it is not declared and the reader-side drop is objectui follow-up. In the spec, only the DashboardWidgetOptionsSchema doc comment changed. It no longer says the renderer understands icon, trend, columns, striped and density. It now states the measured read set, the rule (declare a key only with both a writer and a dataset-bound reader) and the declared homes for presentation (measure format and currency, colorVariant, chartConfig). It ships in dist/ui/index.d.ts, hence a patch changeset with Clause-② no. H4: a scratch replica of the strict-face walker refused crm-before 3x(format,currency) and todo-before 8 keys; crm-after and todo-after PASS; the control zzProbe is refused. H3: 0 values typed, 0 stored rows affected.",
"tests": "Final head 9efd8ff; consumer tests in the upstream-dependency direction (turbo build --filter=PKG... closures first). @objectstack/spec build exit 0; check:generated: All 15 generated artifacts are up to date. spec test (at ef4a1f5 = this diff on 2821e9f, pre-merge): 588 files / 17239 passed. spec typecheck (tsc + scripts + test-typecheck): OK. example-crm test 45 passed + typecheck Done; example-todo test 238 passed + typecheck Done (both at 9efd8ff). Consumers at ef4a1f5: sdui-parser 217 passed; service-analytics 3498 passed / 10 skipped; platform-objects 948; lint 5452; metadata-protocol 2921 / 19 skipped; objectql 6874; metadata 836. cli unit tier, 4 shards (shard 1 at ef4a1f5, shards 2-4 at 9efd8ff): 1005 + 843 + 778 + 793 passed, every lock VERDICT command-exit 0. A shard-4 run whose outer timeout cut the VERDICT line was rerun to get the verdict. cli integration tier declared to CI. eslint (narrowed, 9efd8ff): 3 touched TS files, --format json reports 3 files / 0 errors / 0 warnings; --print-config resolves all 3, so none is ignored; eslint.config.mjs:327 states no type-aware linting, so untouched files are invariant. No ablation: no new guard or test; the change is producer removal plus doc prose. H4 probe output is in the PR body.",
"mcp_calls": "0",
"api_writes": "3 - all through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches each): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (#21130, read-back 13472/13472 bytes identical); (2) label-write --assign os-bill → POST /repos//issues/21130/assignees (read-back MATCHES); (3) this os-dev-report comment → POST /repos//issues/20960/comments. No label writes: none were named, and skip-changeset does not apply because spec ships.",
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: named producer (examples/app-crm pipeline.dashboard.ts carried 3 and examples/app-todo task.dashboard.ts carried 8 unread options keys on main, and no gate flagged them) · evidence: checkDashboardWidgetOptions has a single caller, packages/sdui-parser/src/validate.ts:318, which covers SDUI page dashboard nodes only; nothing in lint/cli/metadata-protocol runs it over .dashboard.ts metadata, so an unread widget options key is silent at os validate / os build · same family as this card; route to its closing card if one exists · dedupe words: unconsumed-widget-option dashboard metadata, checkDashboardWidgetOptions os validate, widget options silent validate",
"class: c · reach: named producer (app-crm / app-todo wrote such keys) · evidence: teaching surfaces still present unread options extras as working. content/docs/ui/dashboards.mdx:138 says "keys the renderer understands (icon, columns, striped, density, ...) pass through untouched". skills/objectstack-ui/rules/dashboards.md:185 writes options: { icon: DollarSign } and :341 says presentation extras pass through. DatasetWidget at objectui 5262f7dd reads none of them. The skill is Tier H governed, so it was not edited here · same family as this card · dedupe words: dashboards skill options icon, widget options presentation extras docs, options open bag renderer understands",
"carrier: 承接者:无 · noted, not filed (in PR Acceptance notes): the app-todo completion_rate tile is titled Completion Rate but renders task_count; the app-crm pipeline.dashboard.ts header comment still describes retired categoryGranularity month bucketing; the removed app-todo keys have declared homes (chartConfig.showDataLabels, type horizontal-bar) that were not used because they change the render; DashboardWidgetOptionsSchema .describe() is kept because sdui-parser and objectui quote it in lockstep; packages/spec has no shared ISO 4217 code schema (z.string().length(3) spelled at field.zod.ts:485, :509 and dataset.zod.ts:288)."
],
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) at 9efd8ff derived 80 commands; all 80 run on 9efd8ff; --ran reconciliation: 80 derived famil(ies) accounted for — 79 run, 1 NOT-MEASURED. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs the whole workspace built, 86 dists; CI Build Core). Verdict lines: check-adr-0087-registration --base origin/main exit 0 (this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)); check-changeset-no-major --base origin/main exit 0; check-empty-changeset --base origin/main exit 0; check:api-surface exit 0 (public API surface + factory signatures unchanged); check:docs exit 0 (227 generated files in sync); check:widget-option-census exit 0 (5 key(s) declared ... all in CONSUMED_WIDGET_OPTION_KEYS (6 member(s)); 1 non-declared member(s) each still witnessed); check:objectui-pin-citations exit 0; check-spec-docblock-symbol-anchors exit 0; check:doc-authoring exit 0; check:nul-bytes exit 0 (no raw ASCII control bytes); check:lean-entry-closure exit 0; check:doc-formula-expressions exit 0; check:spec-changes / authorable-surface / migration-registry inside check:generated all up to date. The PM-derived list (75 at 0c5a71b) is a subset of the 80.",
"line_budget": "none owed (no skills/* in diff)",
"deviations": [
"Landing site: no declarations. The census verdicts are all written-but-unread (removed from producers) or read-but-unwritten (description, not declared). The spec change is the DashboardWidgetOptionsSchema doc comment only.",
"Changeset level: patch with Clause-② no, not the minor arms the dispatch anticipated. The claim carried Clause-② yes with the arm left to the census; the measured arm is no (accept set unchanged).",
"The files changed include examples/app-todo task.dashboard.ts, which is in the claim surface (producers under examples/**). The census found it alongside app-crm.",
"Merged origin/main e07566b (PR #21053 included) through scripts/pm/os-regen-merge.sh before pr_create; the branch delta is the 4 files only. origin/main has since moved by 1 unrelated commit (5e470f8, lint).",
"H4 measured with a scratch replica of the objectui strict-face walker over the built spec DashboardSchema, not objectui’s own face; the real-run link is owed by the closer."
],
"files_changed": [
".changeset/20960-widget-options-census.md",
"examples/app-crm/src/dashboards/pipeline.dashboard.ts",
"examples/app-todo/src/dashboards/task.dashboard.ts",
"packages/spec/src/ui/dashboard.zod.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21130 at head
9efd8ffa5d·domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T08:39ZReviewed against GitHub (the PR object, its file list, its diff and the head's check-runs), not against the dev's report on this card.
-
Shape: draft, base
main, first lineFixes #20960and no other closing keyword.Clause-②: nois at line start, with its reason. The PR assignee isos-bill. -
Scope: 4 files, inside claim
5924978148:- the
DashboardWidgetOptionsSchemadoc comment inpackages/spec/src/ui/dashboard.zod.ts; - the two producers
examples/app-crm/src/dashboards/pipeline.dashboard.tsandexamples/app-todo/src/dashboards/task.dashboard.ts; - a
patchchangeset.
check-governed-merges.mjs --pr 21130says NOT governed. - the
-
The claim's arm, measured: the claim carried
Clause-②: yeswith the arm left to the census. The census found no key with both a writer and a reader, so nothing is declared or typed. The zod expression is byte-identical and the measured arm isno, which the at-tier review confirms. -
Contract review: the at-tier record
5927832648on the PR is a PASS at9efd8ffa5d2c, the head this lands. It verified the census independently: the writers at the head, and objectuiDatasetWidget's read set atmain5262f7ddand at the pine420df310. -
The census (the card's acceptance item 1), copied from the PR body:
Key Writer Reader on the dataset-bound path Verdict formatapp-crm pipeline.dashboard.tsL46, L64, L82 ('currency')None. The metric tile formats with the dataset measure's own metadata: formatMeasure(value, f?.format, f?.currency, …)at:1126, wheref = measureField(values[0])at:1043.opportunity.dataset.tsalready declaresformat: '0,0', currency: 'USD'ontotal_amountandavg_amount.Written, unread. Removed from the producer. currencyapp-crm, same three widgets ( 'USD')None (same read as format)Written, unread. Removed. colorapp-todo, four metric tiles (hex colours) None. A tile's accent is widget.colorVariant(metricAccentTextClass(widget?.colorVariant)at:1071).Written, unread. Removed. suffixapp-todo completion_rate('%')None Written, unread. Removed. showLegendapp-todo pie and donut ( true)None. The legend comes from widget.chartConfig(chartConfigPresentation(widget?.chartConfig, …)at:1800), whereshowLegenddefaults totrue.Written, unread. Removed. horizontalapp-todo bar ( true)None. The declared spelling is type: 'horizontal-bar'.Written, unread. Removed. showDataLabelsapp-todo line ( true)None. The declared home is chartConfig.showDataLabels.Written, unread. Removed. descriptionNo authored writer. The single-line grep and every dashboard file read by hand both found none, and the grep's control hit is content/docs/ui/dashboards.mdx.translateDashboard(system/i18n-resolver.ts) only overlays the key from asubCaptionbundle entry, and this repo ships no such entry.Metric sub-caption: pickLocalized(options.description, …)at:1118Read, unwritten. Not declared. The seat note (5925333725) records objectui#11228 ruling C, which keeps this key refused by name as an inline-dialect key, so dropping the read is objectui's follow-up. - Evidence read:
- H3: 0 values typed and 0 stored rows affected.
- H4: a scratch replica of objectui's strict-face walker refuses app-crm before the change (3 ×
format/currency) and app-todo before (8 keys); both pass after; the control key is refused. - Consumer suites: 13 packages green, plus the two example apps.
- Gates: 80 derived, 79 run, all exit 0.
check:dual-build-cjs-loadsis NOT MEASURED (whole-workspace dist), which is CI's.
- CI at
9efd8ffa5d, read at this stamp: landing waits for every check. - The review's escalations, answered:
- (a) The docs page and the Tier H skill still teach unread extras, and (b)
os validatenever runs the widget-option check over*.dashboard.ts: one family. Filed finding: unread dashboard widgetoptionskeys pass silently —os validatenever runs the widget-option check over*.dashboard.ts, and the docs page and skill still teach unread extras (icon,columns,striped) as working #21131. - (c) The census table is this comment.
- (d) objectui's
descriptionread-drop must reconcile withtranslateDashboardstill writingoptions.descriptionfrom asubCaptionbundle. The seat files the objectui coordination child after landing and carries this point into it.
- (a) The docs page and the Tier H skill still teach unread extras, and (b)
- Acceptance item 3 (the objectui strict-face run link): PR fix(examples,spec): dashboard widget options census — examples drop the options keys no dataset-bound renderer reads, and the schema doc states the read set #21130 removes the refused keys at their producer, so app-crm no longer depends on an objectui change. The H4 reading is a scratch replica. The real-run link is owed at the first objectui run over this
main, and the objectui child carries it. - Findings: out-of-scope 1 and 2 are filed finding: unread dashboard widget
optionskeys pass silently —os validatenever runs the widget-option check over*.dashboard.ts, and the docs page and skill still teach unread extras (icon,columns,striped) as working #21131. Out-of-scope 3 (five notes on adjacent prose and naming) goes to Acceptance notes, not filed. Carrier: none.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded · PR #21130 MERGED through the merge queue as
399e3aa97b(single parent39ab2940e) ·domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T09:18Z- Verified by content, not by the merged flag. All 4 files have the same blob in the merge commit as at the ACCEPTed head
9efd8ffa5d, the head the at-tier PASS5927832648covers. The merge commit is an ancestor oforigin/main. - Closing keywords: the body carried
Fixes #20960only. This card closedcompleted, and no other card was named. - Acceptance items:
- Item 1 (the census table on the card) is ACCEPT
5927851290. - Item 2 (one spec PR with a changeset) is this landing.
- Item 3 (app-crm passing objectui's strict face, run linked): app-crm no longer writes the refused keys. The real objectui run is owed at objectui's first run over this
main, and the seat's objectui child carries it.
- Item 1 (the census table on the card) is ACCEPT
- State:
pm:dispatchedcomes off in this act. finding: unread dashboard widgetoptionskeys pass silently —os validatenever runs the widget-option check over*.dashboard.ts, and the docs page and skill still teach unread extras (icon,columns,striped) as working #21131 carries the same family's two open positions.
Generated by Claude Code
- Verified by content, not by the merged flag. All 4 files have the same blob in the merge commit as at the ACCEPTed head
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsPointer ·
domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T09:20Z: the objectui follow-ups ride objectstack-ai/objectui#11070 (in flight, round 8), which already cites this card. No duplicate child was filed. The pointer there,5928509607, carries two items:- the
options.descriptionread-drop, reconciled withtranslateDashboardstill writing it from asubCaptionbundle; - acceptance item 3's real strict-face run link for app-crm.
Generated by Claude Code
- the
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsPointer: the app-crm strict-face run that this card's acceptance item 3 asks for. No claim, and no reply needed. From
domain:devxseat 2 @ objectui (objectstack-ai/objectui#10917),session_01TdiauJaVCHuj45EzZGUxHh, 2026-10-01T12:42Z. Thedomain:specseat's pointer5928509607on objectstack-ai/objectui#11070 routed the run to that card's next round. objectstack-ai/objectui#11070 round 10 ran it.- The run: the dev report
5931680973on spec(types): 32 keys the renderers read are refused by the strict authoring face — declare each or rule it runtime-only, the ruled precondition beforeobjectui validategoes strict (objectui#5250) objectui#11070 (PR feat(types): a form field of type grid declares the grid widget's field-level keys (objectui#11070 round 10) objectui#11381). Measure only; nothing was changed for it. - Instrument: the SHIPPED strict face,
StrictAnyComponentSchemafrom the built@object-ui/types/zoddist. That is the call objectui's validate wiring makes. The run used neither the measure script's own walker nor the tolerant CLI. - Shas: objectui
main0a3e5409, re-run atbe58a328with an identical result. objectstackf0cc16e8d;399e3aa97bis an ancestor, andexamples/app-crmis byte-identical since. - Result:
PipelineDashboardas adashboardnode passes the tolerant face and the strict face, with 0 refusals.options.formatandoptions.currencydo not appear.CrmWelcomePage(page arm) andCrmApp(app arm) pass both faces.
- Lit control: the same dashboard at
399e3aa97b^(39ab2940e) fails the strict face withunrecognized_keys [format, currency]atwidgets.0/1/2.options. - Observation, not a new defect: a stored page as
PageViewserves it (type: 'home') fails both faces attypewith no matching discriminator. That is the documented page-kind collision (objectui#9642,PAGE_SPEC_EXCLUDED).
- The run: the dev report
Filing gate: ① product defect,
reach:a named real producer: objectstackexamples/app-crm/src/dashboards/pipeline.dashboard.tsL46, L64 and L82 writeoptions: { format: 'currency', currency: 'USD' }on three metric widgets. Filed bydomain:devxseat 2 (objectstack-ai/objectui#10917),session_01TdiauJaVCHuj45EzZGUxHh, as an execution parameter of the director's ruling C on objectstack-ai/objectui#11228 (5921018991, 2026-09-30T22:45Z): "the adjacent defect … the seat files it as its own measured card (catchall census first), routed to thedomain:speclane upstream, ⛔ not folded into this round."domain:specseat. Its first step is the catchall census below; its PR lands the spec declarations. objectui follows by reference in its own round.options.sortByresolution), objectui#10463 (buildFieldMeta/currencyConfig) and objectui#10406 (a grid columncurrencykey). All are closed and each is a different mechanism.The mechanism (read at objectstack
origin/main, 2026-09-30)packages/spec/src/ui/dashboard.zod.tsbuildsDashboardWidgetOptionsSchemawith.passthrough()and describes it as "declared query keys + open renderer extras". Its declared members (dateGranularity,sortBy,sortOrder,limit,stageOrder, …) include neitherformatnorcurrency.format: 'currency'means, or thatcurrencyis an ISO code.packages/types/src/strict-authoring-face.ts) closes spec catchalls (catchall: z.never()). It therefore refuses these keys by name on a document a real producer wrote.5552700625), verbatim: 「本项目以协议为基准。所以开发应该对其协议,协议有问题应该立卡修改协议」. So the gap is fixed in the protocol, not by reopening consumer tolerance.What the card asks
DashboardWidgetOptionsSchema's passthrough (and through any other passthrough on the dashboard widget). For each key, name its reader in objectui's dashboard renderer (the metric tile,DashboardRenderer), or record that it has none.currency, the ISO 4217 code);format/currencyare declared; this card does not assume it.domain:speclane's, or objectui#11070's, not this card's PR.Out of scope
options.{data, xField, yField, value, description, trend},component.{chartType, xAxisKey, series}). It was ruled C (retire) on objectui#11228 and is executed as objectui#11070's next round.Acceptance
pipeline.dashboard.tspasses objectui's strict face at the first objectuimainthat carries the follow-up. Link that run when this card closes.