Skip to content

docs(spec): globalFilters[].field describes where the name resolves — the dataset object, not its dimensions - #16968

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16256-global-filter-field-resolves-against-object
Sep 8, 2026
Merged

docs(spec): globalFilters[].field describes where the name resolves — the dataset object, not its dimensions#16968
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16256-global-filter-field-resolves-against-object

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #16256

Clause-②: no
One .describe() string is rewritten so it says where the name resolves. The key stays
z.string(): no accept set moves, no arm is added, no bound changes, no validation
behaviour changes. The published description text is the whole diff — a 拉回已声明契约
restatement, 常规档. The fence held: saying it correctly needed no type change and no
new arm, so there was nothing to stop and report.

What

Dashboard.globalFilters[].field described itself only as Field name to filter on. True,
but silent on the one thing authors get wrong: the name resolves against the object behind
each bound widget's dataset (dataset.object), not against that dataset's declared
dimensions. The generated reference row is the only place on the reference page that
explains this key, so it now carries the resolution target.

The reference page is a build product — its line 6 says AUTO-GENERATED — DO NOT EDIT — so
the source edit is the .describe() string in packages/spec, and the page reaches its new
text by regeneration. Nothing under content/docs/references/ was hand-edited.

The wording, and why it is shaped this way

Triage made one nuance binding. Quoted verbatim, unmodified (per AGENTS.md, a Chinese ruling
is not translated in place):

⇒ 那句话要说的是「在授权层,field 是对 widget 的数据集对象解析的,不是对数据集声明的
dimensions 解析的」。⛔ 不能写成「维度和字段是两个互不相干的世界」——因为在更底层的分析查询层,
一个对象字段确实可以当维度用而 cube 没声明它。一句话既要挡住误解,又不能把下层的真相说反。

The one-line .describe() blocks the misreading by scoping the claim rather than by
denying the lower layer: it opens with at the authoring layer. It therefore never asserts
anything about the analytics query layer, so it cannot state that layer's truth backwards.
The mirrored TSDoc then carries the half a table cell cannot, in as many words: the
separation is a statement about the authorable surface only, and it is NOT a claim that
an object field can never serve as a dimension.

Aligned with PR #16266, not a second phrasing

Three carriers state this one rule (#16167's hand-written page via PR #16266, #16268's
example on that page, and this card's generated row). #16266 has landed; its sentence on
content/docs/ui/dashboards.mdx under Where a Filter's field Resolves reads:

A global filter's field resolves against the object behind each bound widget's
dataset
(dataset.object) — never against that dataset's declared dimensions.

and its lower-layer disclaimer reads:

This describes the authorable surface — what a dashboard may declare, and what
author-time validation accepts. It is not a claim about the analytics query API, which does
accept an object's own field as an ad-hoc dimension without the dataset declaring it;
widget-dimension-unknown is what holds the line for authored dashboards.

The new describe reuses that noun phrase — the object behind each bound widget's dataset
(dataset.object)
— verbatim, and the TSDoc reuses the disclaimer's substance. No second
spelling was invented.

Before / after

Source, packages/spec/src/ui/dashboard.zod.ts (:803-:804 on origin/main 97adce2fa):

// before
/** Field name to filter on */
field: z.string().describe('Field name to filter on'),

after: a 13-line TSDoc plus

field: z.string().describe('Field name to filter on — at the authoring layer it resolves against the object behind each bound widget\'s dataset (`dataset.object`), not against that dataset\'s declared `dimensions`; enforced by the lint rule `dashboard-filter-field-unknown` (severity error)'),

Which generated rows moved

The card cites content/docs/references/ui/dashboard.mdx:97. That number has moved, and on
today's main the old string appears in that page twice. Both occurrences render the
same GlobalFilterSchema, so both moved — and only those two:

Row on origin/main Section it sits under Moved
:100 ### Nested Shape: Dashboard.globalFilters[number] — the row the card names yes
:278 ## GlobalFilter### Properties — the same schema rendered as its own top-level section yes

pnpm --filter @objectstack/spec gen:docs regenerated 228 files and git status reported
exactly one modified path. Notably not swept in: packages/spec/src/ui/view.zod.ts:657
carries the identical .describe('Field name to filter on') on a different schema, and its
generated rows in view.mdx, component.mdx and page.mdx are byte-unchanged. A tree-wide
search-and-replace on that string would have been the wrong instrument; the edit was anchored
to one exact source block whose occurrence count was asserted to be 1 before writing.

The enforcement, located by rule id

Both rules were found by id, not by the line numbers the card cites (triage said it had
not verified them). They happen to still hold on 97adce2fa:

Rule id Constant Emission site(s) severity
dashboard-filter-field-unknown packages/lint/src/validate-widget-bindings.ts:430 :901 and :918 'error' at :900 and :917
widget-dimension-unknown packages/lint/src/validate-widget-bindings.ts:401 :1073 'error' at :1072

Both are severity: 'error', which is the point: the new sentence states a rule that is
already enforced, not a suggestion. packages/lint was read only — it is untouched by
this diff.

Changeset — decided with both halves

  1. Path inside files[]? Yes. packages/spec's files[] is ['dist','json-schema','liveness','prompts','llms.txt','README.md','src/**/*.zod.ts','CHANGELOG.md','api-surface','spec-changes.json'], and src/ui/dashboard.zod.ts matches src/**/*.zod.ts.
  2. Changed text present in published output? Yes — measured after the build, with controls.
Probe dist json-schema
the new describe text 18 files 3 files
positive control — an existing sibling .describe() (Stable filter name (variable key); defaults to field) 18 files 3 files
negative control — an ordinary // comment in the same object literal (The binding runs the other way…) 18 files 0 files

The dist column does not discriminate: those bundles are unminified and retain source
comments, so the negative control appears there too. json-schema is the discriminating
reading, and it is a clean pair — only .describe() text reaches it. The new sentence lands
in json-schema/ui/GlobalFilter.json, json-schema/ui/Dashboard.json and
json-schema/objectstack.json, all inside files[], plus the runtime .description on the
Zod schema in dist. ⇒ patch on @objectstack/spec.

Verification

Run in the dedicated worktree /home/user/objectstack-issue-16256, off origin/main
97adce2fa. Heavy runs went through scripts/pm/os-verify-lock.sh; every exit code was
captured before any pipe.

Check Result
pnpm --filter @objectstack/spec build VERDICT command-exit 0
pnpm --filter @objectstack/spec gen:docs VERDICT command-exit 0Generated 228 files
pnpm --filter @objectstack/spec run check:docs exit 0 — the generated page is in sync
pnpm --filter @objectstack/spec run check:generated exit 0
pnpm --filter @objectstack/spec run check:authorable-surface exit 0
pnpm --filter @objectstack/spec typecheck VERDICT command-exit 0 — test layer compiles, 54 files / 261 errors / 145 pinned signatures held, shrink-only ledger unchanged
pnpm --filter @objectstack/spec test VERDICT command-exit 0Test Files 467 passed (467), Tests 13100 passed (13100)
pnpm lint (whole repo, eslint . --no-inline-config) VERDICT command-exit 0, re-run at final commit 6139ec910
pnpm check:nul-bytes exit 0
git status after the docs build clean

Gate families were derived mechanically rather than transcribed:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at
6139ec910 derived 94 families over the 3 changed paths; all 94 were run, and
--ran reconciliation reports 94 derived famil(ies) accounted for — 94 run, 0 NOT-MEASURED.

Two honest caveats on that sweep:

  • pnpm check:dual-build-cjs-loads exited 3 — PREREQUISITE NOT MET, naming 87 packages
    with no dist/. Nothing was measured; it needs a whole-repo pnpm build, which does not
    fit this seat's foreground budget. Declared to CI, and it reads no path in this diff.
  • node scripts/check-adr-0087-registration.mjs --self-test first exited 1 on an
    environment failure (the container's commit-signing server answered 503, so the
    temp-repo commit the self-test makes could not be written). Retried: exit 0,
    332 assertions over real temp git repos. Not a diff finding.

Four other families first exited on PREREQUISITE NOT MET for unbuilt packages
(check:doc-formula-expressions, check:doc-security-posture, check:skill-examples,
check:docs-transcript-drift); after
turbo run build --filter=@objectstack/lint --filter=@objectstack/formula --filter=@objectstack/client-react --filter=@objectstack/client
(VERDICT command-exit 0, 34/34 tasks) all four re-ran green.

Not governed: the register prints docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md,
and this diff touches none of them.

验收备注

  • packages/spec/src/ui/view.zod.ts:657 carries the same Field name to filter on describe
    on the list-view filter schema. Whether that surface wants the same treatment is a separate
    reading on a separate card — noted, deliberately not filed and deliberately not touched.
  • docs/notes/airtable-dashboard-analysis.mdx:347 quotes the old describe line inside a
    design-note code block. It is a historical note, not a carrier of the rule — left alone.

Generated by Claude Code

`globalFilters[].field` described itself only as "Field name to filter on",
which left the generated reference page silent on the one thing authors get
wrong: the name resolves against the object behind each bound widget's
dataset (`dataset.object`), not against that dataset's declared `dimensions`.

The rewritten `.describe()` states the resolution target, scopes the claim to
the authoring layer, and names the rule that already enforces it
(`dashboard-filter-field-unknown`, severity error). The mirrored TSDoc carries
the second half the one-line form cannot: the separation is a statement about
the authorable surface only, NOT a claim that an object field can never serve
as a dimension — the analytics query API does accept one ad hoc without the
dataset declaring it.

Wording aligned with the hand-written page's `## Global Filters` section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
`content/docs/references/ui/dashboard.mdx` is a build product of the spec's
docs build — its line 6 says so — so the new `globalFilters[].field`
description reaches it by regeneration, never by hand. Two rows carry the
sentence because two sections render the same `GlobalFilterSchema`:
`### Nested Shape: Dashboard.globalFilters[number]` and the top-level
`## GlobalFilter` → `### Properties`. No other reference page moved: the
identically-spelled `field` describe on the list-view schema is a different
surface and was deliberately left alone.

Changeset is `patch` on `@objectstack/spec`: the string is a published datum,
not a comment — it is the `description` of the property in the shipped JSON
Schema and the runtime `.description` on the Zod schema in `dist`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@github-actions github-actions Bot added the size/s label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s).

2 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via GlobalFilterSchema (symbol, a top-level const))
  • content/docs/releases/v17.mdx (via GlobalFilterSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ee4a59b6c22a3a894b4cfd73755fe26a2a08922apackageMentionDocs.

Which tree this was computed on

This run read content/docs from 3d13f56e4a0e23552bf8935ad606eedbe5b65413 — the merge of head 95663cfc11ee5fd17d81885c14aff70150adbee4 into base ee4a59b6c22a3a894b4cfd73755fe26a2a08922a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3d13f56e4a0e23552bf8935ad606eedbe5b65413 && git checkout 3d13f56e4a0e23552bf8935ad606eedbe5b65413
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ee4a59b6c22a3a894b4cfd73755fe26a2a08922a 95663cfc11ee5fd17d81885c14aff70150adbee4 && git checkout -B drift-repro ee4a59b6c22a3a894b4cfd73755fe26a2a08922a && git merge --no-ff 95663cfc11ee5fd17d81885c14aff70150adbee4

node scripts/docs-audit/affected-docs.mjs --json ee4a59b6c22a3a894b4cfd73755fe26a2a08922a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ee4a59b6c22a3a894b4cfd73755fe26a2a08922a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Two adjacent docblocks in `GlobalFilterSchema` each say "the object the field
lives on" about a different resolution: `field` resolves a COLUMN against the
bound widget's `dataset.object`, while the sibling `object` key resolves a
translation-bundle KEY. They do not collide, but a reader who meets both may
infer that setting `object` re-points the filter — the same reader-facing
shape this docblock was just rewritten to fix.

One clause in the `field` TSDoc closes it at the point of confusion. The
`object` docblock is untouched, and the `.describe()` string is byte-unchanged,
so the generated reference page and the changeset need no edit.

The citation is anchored by SYMBOL, not by line: the exported rule-id constant
`DASHBOARD_FILTER_FIELD_UNKNOWN`, the enclosing `validateWidgetBindings`, and a
verbatim snippet measured to grep to exactly one hit in that file. A `path:NNN`
prose anchor would have started rotting on the next edit to the file it names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 8, 2026 21:22
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 50dc214 Sep 8, 2026
36 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-16256-global-filter-field-resolves-against-object branch September 8, 2026 21:48
baozhoutao pushed a commit that referenced this pull request Sep 9, 2026
…d line stops reading as an addition

The two tells the card reproduced fire on added LINES and could not see that a
line replaced an equivalent one: PR #16941 (T2, a form `description:` prose
rewrite on a file with no closed set in it) and PR #16968 (T1, a Zod key whose
`.describe()` text grew, keys 32 -> 32). Neither moved an accept set, and the
C5 row neither raised could be cleared except by declaring `Clause-②: yes` on a
change that does not widen.

`tellsInFile` now reads a REPLACEMENT BUDGET per change block, per tell kind:
each removed line carrying a member or key of kind K buys one added line of
kind K the right not to be reported, spent in patch order, so a block that adds
more than it removed still reports the surplus with its own file:line. The unit
is the change block, never the hunk, so an unrelated removal three context
lines away cannot pay for a real addition. Openers and prose fragments pay for
nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tooling

Projects

None yet

2 participants