Uh oh!
There was an error while loading. Please reload this page.
feat(spec): field-level relatedListFilter — declarative default filter for auto-derived related lists - #8955
Conversation
…r for auto-derived related lists (#8704) Adds the fourth member of the field-level related-list family, reusing the canonical Query-DSL FilterConditionSchema (no new dialect) so the FILTER-axis doors apply automatically: the #8793 bare-preset schema door is pinned at the new position, and the engine doors judge the composed query at run time. Contract (maintainer ruling 2026-08-15, item 3): AND-composed with { [referenceField]: parentId }, never user-editable; tab badge count honors the same composed filter (parity pinned as normative contract text). Lint: FILTER_KEYS gains relatedListFilter so the token / empty-combinator / preset-comparand rules reach the new position. Ledger: planned + authorWarn until objectui#4664 (consumption half) lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
…lter (#8704) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8704
Spec half only, implementing the maintainer ruling (2026-08-15, comment
5299772379, 「接受全部建议。」 item 3) under its four binding conditions. The objectui consumption half (RecordDetailView auto-derivation + tab badge) is objectstack-ai/objectui#4664,Blocked-by:this card — untouched here by design.What changed
packages/spec/src/data/field.zod.ts—relatedListFilter: FilterConditionSchema.optional()joins the three-key related-list family (relatedList/relatedListTitle/relatedListColumns). JSDoc +describe()carry the normative contract: AND-composed with{ [referenceField]: parentId }, an authored constraint (never a user-editable suggestion), and badge-count parity — the tab badge count is computed over the same composed predicate.packages/spec/src/data/field.test.ts— accept/round-trip pins, full-shape ($and/$or/$not, nested) pin, the Refuse the declared relative-date preset vocabulary as a bare temporal comparand at publish time — the ruled C half of #8690, carved out for the spec seat #8793 bare-preset refusal pinned at the new position (path['relatedListFilter','created_at','$gte']) with positive controls, a [finding] The FILTER axis has no DOTTED-path verdict —where: { project_id.name: 'x' }rides its head segment past both doors, where SORT refuses the same spelling (#4256) #8371 dotted-head door-placement pin, and a contract-text pin holding both normative clauses in the publisheddescribe().packages/lint/src/filter-walk.ts—FILTER_KEYSgainsrelatedListFilter;validate-preset-comparands.test.tspins the new position (finding pathobjects[0].fields.task.relatedListFilter.created_at.$gte+ clean positive control). This one set-element extends all three walking rules (tokens, empty combinators, preset comparands) at once.packages/spec/liveness/field.json—planned+authorWarn+authorHint(the appnavigation.runAction[决策] SDUI 深链「导航即运行动作」(?runAction=<actionName>)要不要升格为 spec 声明的正式契约 #4848 shape: an author who sets this today IS misled — the rows the filter exists to hide keep rendering until the consumer lands). Flip toliveand dropauthorWarnwhen objectui#4664 lands.state-counts.mdregenerated (field planned 1 → 2).authorable-surface/data.json(+data/Field:relatedListFilter),content/docs/references/data/field.mdx. Changeset:@objectstack/specminor,@objectstack/lintminor. No ADR-0087 entry — pure accept-set widening, nothing renamed/retired/narrowed (the changeset carries theadr-0087: not-requiredmarker); the migrations registry is untouched in this diff.Ruling conditions — how each is discharged (measured, not assumed)
① Reuse the existing declarative filter schema — no new dialect. Two existing dialects were candidates; measured both:
FilterConditionSchema(canonical Query-DSL,data/filter.zod.ts) — chosen. It is the authoring face of a querywhere, dataset scope filters, and — decisive in-file precedent —summaryOperations.filteron this sameFieldSchema, whose documented semantics are already "the engine ANDs it with the parent-FK match": exactly this key's composition rule. The FILTER-axis doors follow automatically:FilterConditionSchema's ownsuperRefine, so the new position is covered with zero new wiring — pinned at the key's position infield.test.ts.whereon a virtual formula field returns 0 rows silently, while sort and search refuse the same field with a 400 #8296 formula/virtual keys and [finding] The FILTER axis has no DOTTED-path verdict —where: { project_id.name: 'x' }rides its head segment past both doors, where SORT refuses the same spelling (#4256) #8371/feat(objectql,metadata-protocol): refuse a dotted filter key whose head is a relation, a formula, or a plain scalar — at both doors (#8371) #8936 dotted heads (landed under this branch;mainmerged in before opening): both are field-typed doors at the ingress (assertFilterFieldsExist) and the engine seam (assertFilterIsMaterializable), judging every caller-suppliedwherewhichever verb it arrives by. The composed related-list query reaches them like any other query — free by construction of reuse. The spec-side schema is deliberately field-agnostic there (it cannot see the CHILD object's field types), and the door placement is pinned so a blind schema-side refusal does not creep in.ViewFilterRuleSchema[]({ field, operator, value }rules) — what the hand-authoredrecord:related_listcomponent'sfilterinput takes. Rejected for this key: it carries none of the named refusal doors at parse, and the AND-composition consumer needswhere-clause material, whichFilterConditionis directly.② Semantics in the contract. Measured where the auto-derivation contract lives: wholly objectui-side (
packages/app-shell/src/utils/deriveRelatedLists.ts+RecordDetailView.tsx, per the liveness ledger's own evidence for the existing three keys). Spec carries no related-list descriptor schema (component.zod.ts'sRecordRelatedListPropsis the hand-authored component;view.zod.ts'ssubformsis the write-side form). So the spec-enforceable artifacts are: the normativedescribe()/JSDoc (both clauses stated as contract, binding on every consumer of the derived descriptor) plus a contract-text pin keeping them there. The behavioural badge-parity enforcement lands with objectui#4664's acceptance criteria — stated explicitly here per the dispatch's contingency.③ objectui half not this card — confirmed filed as objectui#4664; nothing in
../objectuitouched.④ 17.x minor — pure accept-set widening, Feature class, ordinary minor changeset, no BREAKING annotation, no ADR-0087 registry entry (widening has no conversion/retirement obligation — checked against the registry precedents: entries exist for removals/renames/behavioural refusals, none for additive optional keys).
Additional measurements
lookup/master_detail(optional keys on the flatFieldSchema, scope stated in JSDoc only — nosuperRefine). The fourth is gated identically (i.e. not), keeping the family symmetric.filter/filters/runtimeFilter);relatedListFilteris the first member not spelledfilter(family naming wins on the field surface), so the walk misses it without theFILTER_KEYSaddition — measured, then closed class-wide via the shared set rather than per-rule.relatedListFilteryet (new key); example apps and platform-objects author only the existing three keys — no re-spelling needed.Verification — union run at head
3bc19f0(git rev-parse --short HEADfrom the run itself)packages/spec: 406 test files / 10725 tests passed;typecheckgreen (incl.check:test-typecheckself-test).packages/lint: 73 files / 2047 tests passed (incl. the 2 new position pins);typecheckgreen.pnpm --filter @objectstack/spec check:generated: all 13 artifact gates green at head (authorable-surface, api-surface, export-origins, docs, liveness, migration-registry, spec-changes, upgrade-guide, skill-docs/refs, react-blocks, strictness-ledger, test-typecheck).@objectstack/metadata603,metadata-protocol1512,metadata-core162,metadata-fs51 — all passed. Example appsvalidate×3 (app-crm,app-showcase,app-todo): exit 0 (pre-existing warnings only).node scripts/pm/dispatch-gates.mjs …): all 15 derived path gates PASS (check:changeset-gate-self-tests,check:cross-package-test-inputs×2 spellings,check:doc-formula-expressions,check:docs-audit-scope,check:merge-driver,check:objectui-changeset,check:quick-reference-counts,check:role-word,check:spec-parsed-alias,check:type-source-resolution,check-adr-0087-registration,check-changeset-no-major,check-dev-prereqsafter full workspace build,check-empty-changeset) plus convention gates for new test files (check:query-options-erasure,check:type-check-coverage,check:type-check-debt --re-measureon the built closure) andcheck:nul-bytes. The derivation added nothing beyond the dispatch prompt's named families except the docs/changeset path gates listed above, all green.Generated by Claude Code