Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .changeset/self-descriptions-answerable-to-reality-ii.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
"@objectstack/spec": patch
---

docs(spec): two self-descriptions re-anchored to measured reality — the `chartConfig` liveness evidence and `allowAddTab`'s `.describe()` (#7017, #6961)

Sweep card #7056. Both members are prose that stopped being answerable to the
code it describes: one **overstated** what a reader delivers, one **understated**
what a renderer now delivers. Zero acceptance-face change — no schema key, no
enum member, no strictness posture moves, and the only generated artifact that
shifts is the `.describe()` row in `content/docs/references/ui/view.mdx`.

**#7017 — `widgets.children.chartConfig`'s evidence said "chart-config bag
forwarded", which was never measured.** The cited lines took the bag out of the
widget and the line after them read exactly one key (`showLegend`, #3135); the
rest of `ChartConfigSchema` stayed unforwarded. #5175 measured that (1 of 14
keys reaching the renderer) and recorded that the wording had already mis-steered
the #5022 measurement for half its length. Since then #7016 landed the enforce
half in objectui, so the row is re-anchored to today's reality rather than to
either older state: at objectui `@230ffd875`, `chartConfigPresentation` lowers
**nine** keys one `if` per key — `showLegend`, `showDataLabels`, `title`,
`subtitle`, `description`, `height`, `annotations`, `interaction` and `colors`
(split into the positional palette and `categoryColors`) — and the caller spreads
the result onto the chart schema handed to the renderer, with a DOM test pinning
each forwarded key. The remaining **five** are named as unforwarded and why:
`xAxis` / `yAxis` / `series` are derived from the dataset selection, `type` is
answered by the widget's own `type` through `CHART_TYPE_MAP`, and `aria` has no
reader on that path. The entry also gains `verifiedAt: 2026-08-09` and
`evidenceScope: "cross-repo"` (the objectui realm was walked, at a pinned
commit), the two fields #7024 added for exactly this.

The verdict is unchanged — `live`, as it was, now for a reason the evidence
actually supports. No `children` are opened and no key is classified: the
narrowing half for the five unforwarded keys is still an open maintainer decision
on #5175, and the note says so, so this row cannot be read as pre-empting it.

**#6961 — `UserFiltersSchema.allowAddTab` still described the interim,
deliberately-narrowed contract.** #5073 promoted the key with a `.describe()`
that promised only that the tab bar *renders* an add-tab affordance, because the
button objectui shipped had no click handler — narrowing on purpose rather than
advertising a capability the runtime did not deliver (PD#10). The maintainer then
ruled **A1 — implement, session-scoped** (#5236, 2026-08-06) and objectui#3926
delivered it. The describe now states the semantics that are actually shipped:
the affordance asks for a name and snapshots the filters currently applied as a
new tab; the tab is **session-scoped** — it lives only for the current mount and
is never written back as metadata (ADR-0047, which scopes an end user's filter
choices to the session) — and it carries a remove control the authored presets do
not. The applicability sentence #5073 wrote is kept verbatim: page lists only,
object views use `listViews` for named presets. The `⚠️ Scope of the promotion`
TSDoc above the schema, which recorded the dead-button state as current, now
records how it was resolved instead.

The ruling confirmed renderers do not read this text, so this is documentation
semantics only: no runtime change, and the JSON-Schema baselines and the docs
site are regenerated with the describe.
2 changes: 1 addition & 1 deletion content/docs/references/ui/view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ End-user quick-filter configuration (Airtable "User filters" parity)
| **fields** | `{ field: string; label?: string \| Record<string, string>; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: object[]; … }[]` | optional | Fields exposed as quick filters (dropdown/toggle elements) |
| **tabs** | `{ name: string; label?: string \| Record<string, string>; icon?: string; view?: string; … }[]` | optional | Named filter presets rendered as tabs (tabs element). Reuses ViewTabSchema |
| **showAllRecords** | `boolean` | optional | Show an "All records" tab before the presets (tabs element) |
| **allowAddTab** | `boolean` | optional | Render an "add tab" affordance after the presets (tabs element). Page lists only — object views use `listViews` for named presets |
| **allowAddTab** | `boolean` | optional | Let end users add their own tab after the presets (tabs element): the affordance asks for a name and snapshots the filters currently applied as a new tab. SESSION-SCOPED — an added tab lives only for the current mount, is never written back as metadata (ADR-0047), and carries a remove control the authored presets do not. Page lists only — object views use `listViews` for named presets |


---
Expand Down
7 changes: 4 additions & 3 deletions packages/spec/liveness/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@
},
"chartConfig": {
"status": "live",
"verifiedAt": "2026-08-03",
"evidence": "objectui @91757a7: packages/plugin-dashboard/src/DatasetWidget.tsx:602-603 (chart-config bag forwarded to the chart renderer)",
"note": "chart presentation config; read on the dataset-bound chart path."
"verifiedAt": "2026-08-09",
"evidenceScope": "cross-repo",
"evidence": "objectui @230ffd875: packages/plugin-dashboard/src/DatasetWidget.tsx:380-429 — chartConfigPresentation lowers NINE of the declared keys, one `if` per key (:390 showLegend, :391 showDataLabels, :393 title, :395 subtitle, :397 description, :401 height, :403 annotations, :405 interaction, :419 and :425 colors, split into the positional palette and categoryColors); objectui: packages/plugin-dashboard/src/DatasetWidget.tsx:1091 calls it and :1118 spreads the result onto the chart schema handed to the renderer; objectui: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx pins the DOM effect of each forwarded key",
"note": "chart presentation config on the dataset-bound chart path — read PER KEY, not forwarded as a bag. Of the 14 keys ChartConfigSchema declares, 9 reach the renderer (above) and 5 do not: xAxis/yAxis/series are derived from the dataset selection, so an authored value would shadow the derived binding; type is answered by the widget's own `type` through CHART_TYPE_MAP; aria has no reader on this path (AdvancedChartImpl has no `aria` prop and the ARIA injection reads the flat ariaLabel/ariaDescribedBy/role). This entry previously read 'chart-config bag forwarded to the chart renderer', which was never measured: at that time exactly one key (showLegend, #3135) was lowered, and the overstatement mis-steered the #5022 measurement for half its length. #5175 measured the real 1-of-14 state, #7016 forwarded the keys the chart block actually delivers, and the narrowing half for the 5 unforwarded keys is still open on #5175 — do not read this row as classifying them."
},
"colorVariant": {
"status": "dead",
Expand Down
21 changes: 14 additions & 7 deletions packages/spec/src/ui/view.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,12 +775,19 @@ export const UserFilterFieldSchema = lazySchema(() => strictObject({
* `packages/spec` and objectui two sources of truth for one contract — the
* fork #2231's derive-by-reference unification exists to prevent (PD#12).
*
* ⚠️ Scope of the promotion: `allowAddTab` declares that the tab bar RENDERS an
* add-tab affordance. The button objectui renders today carries no click
* handler, so it is presentational — filed against the renderer as #5236, and
* deliberately NOT written into the `.describe()`, because a contract that
* promises "end users can add presets" would be advertising a capability the
* runtime does not deliver (PD#10).
* ⚠️ Scope of the promotion, and its RESOLUTION (#5236 → #6961). At promotion
* time `allowAddTab` was described as declaring only that the tab bar RENDERS
* an add-tab affordance: the button objectui rendered carried no click handler,
* so it was presentational, and the narrower wording was deliberate — a
* contract promising "end users can add presets" would have advertised a
* capability the runtime did not deliver (PD#10). The maintainer then ruled
* **A1 — implement, session-scoped** (#5236, 2026-08-06), objectui#3926
* delivered it (`packages/plugin-list/src/UserFilters.tsx` — naming popover,
* snapshot of the applied filters, component state only, remove affordance on
* the added tab), and the `.describe()` below was upgraded back to the real
* semantics. The narrowing was the interim state the ruling closed, not the
* contract: the key now promises the behaviour, and PD#10 is satisfied by the
* delivery rather than by the hedge.
*
* ## What closing flips, and why that flip is wanted (批 6e's question)
*
Expand Down Expand Up @@ -814,7 +821,7 @@ export const UserFiltersSchema = lazySchema(() => strictObject({
showAllRecords: z.boolean().optional()
.describe('Show an "All records" tab before the presets (tabs element)'),
allowAddTab: z.boolean().optional()
.describe('Render an "add tab" affordance after the presets (tabs element). Page lists only — object views use `listViews` for named presets'),
.describe('Let end users add their own tab after the presets (tabs element): the affordance asks for a name and snapshots the filters currently applied as a new tab. SESSION-SCOPED — an added tab lives only for the current mount, is never written back as metadata (ADR-0047), and carries a remove control the authored presets do not. Page lists only — object views use `listViews` for named presets'),
}).describe('End-user quick-filter configuration (Airtable "User filters" parity)'));

/**
Expand Down
Loading